Mere Dynamics NAV ODATA PowerShell integration

Jeg har tidligere skrevet lidt om at kalde NAVs WebServices fra PowerShell her, men nu kan vi tage det et skridt videre med noget benarbejde som en af mine gode venner har lavet.

Helt gratis – fra os til dig – nogle generiske PowerShell funktioner og et eksempel du kan bruge når du skal læse eller skrive data direkte fra Dynamics NAV via ODATA WebServices med PowerShell. Der benyttes i øvrigt “kun” standard PowerShell, så du behøver ikke engang at have Dynamic NAVs medfølgende PowerShell biblioteker tilgængeligt på den maskine du bruger det fra.

Her har du de generiske funktioner efterfulgt af eksempler:

# Dynamics NAV OData Sample
# 
# Created:  23-04-2017
# Version:  17-07-2017
# by:       Gert Lynge - http://blog.systemconnect.dk/
#           Graves Kilsgaard 
#
# Setup in NAV:
# - Create Page 21 webservice called "Customer"

function New-NAVODataRestMethodHeader
{
    [CmdletBinding()]
    param (
        [parameter(ValueFromPipelineByPropertyName=$true)]
            [String]$ETag )
    PROCESS
    {
        $Headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $Headers.Add("accept","application/json")
        if(-not [string]::IsNullOrEmpty($ETag)) {
            if($ETag = "*") {
                $Headers.Add("If-Match","*")
            }
            else {
                $Headers.Add("If-Match","W/`"'" + [uri]::EscapeDataString($ETag) + "'`"")
            }
        }
        $Headers.Add("Content-Type","application/json; charset=utf-8")
        return $Headers
    }
}

function Get-NAVODataRecord()
{
    [CmdletBinding()]
    param (
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVUri,
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVWebService,
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVSearchParm,
        [parameter(ValueFromPipelineByPropertyName=$true)]
            [PSCredential]$NAVCredentials )
    PROCESS
    {
        try {
            if([string]::IsNullOrEmpty($NAVCredentials)) {
                $NAVResult = Invoke-RestMethod -Uri "$NAVUri$NavWebService($NavSearchParm)?`$format=json" -Method Get -UseDefaultCredentials
            }
            else {
                $NAVResult = Invoke-RestMethod -Uri "$NAVUri$NavWebService($NavSearchParm)?`$format=json" -Method Get -Credential $NAVCredentials 
            }
            
            return $NAVResult
        }
        Catch {
            write-host ($_ |Convertfrom-json|select -ExpandProperty "odata.error").message.value -ForegroundColor Red
            return $false
        }
    }
}

function Test-NAVODataRecord()
{
    [CmdletBinding()]
    param (
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVUri,
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVWebService,
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVSearchParm,
        [parameter(ValueFromPipelineByPropertyName=$true)]
            [PSCredential]$NAVCredentials )
    PROCESS
    {
        if([string]::IsNullOrEmpty($NAVCredentials)) {
            $NAVResult = Get-NAVODataRecord -NAVUri $NAVUri -NAVWebService $NAVWebService -NAVSearchParm $NAVSearchParm
        }
        else {
            $NAVResult = Get-NAVODataRecord -NAVUri $NAVUri -NAVWebService $NAVWebService -NAVSearchParm $NAVSearchParm -NAVCredentials $NAVCredentials 
        }
        if($NAVResult -eq $false) {
            return($false)
        }
        else {
            return($true)
        }
    }
}

function New-NAVODataRecord()
{
    [CmdletBinding()]
    param (
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVUri,
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVWebService,
        [parameter(ValueFromPipelineByPropertyName=$true)]
            [PSCredential]$NAVCredentials,
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVJSONFields )
    PROCESS
    {
        try {
            if([string]::IsNullOrEmpty($NAVCredentials)) {
                $NAVResult = Invoke-RestMethod -Uri "$NAVUri$NavWebService()?`$format=json" -Method Post -UseDefaultCredentials `
                                               -ContentType "application/json" -Body $NAVJSONFields -Headers $(New-NAVODataRestMethodHeader)
            }
            else {
                $NAVResult = Invoke-RestMethod -Uri "$NAVUri$NavWebService()?`$format=json" -Method Post -Credential $NAVCredentials `
                                               -ContentType "application/json" -Body $NAVJSONFields -Headers $(New-NAVODataRestMethodHeader)
            }
            return $NAVResult
        }
        Catch {
            write-host ($_ |Convertfrom-json|select -ExpandProperty "odata.error").message.value -ForegroundColor Red
            return $false
        }
    }
}

function Update-NAVODataRecord()
{
    [CmdletBinding()]
    param (
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVUri,
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVWebService,
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVSearchParm,
        [parameter(ValueFromPipelineByPropertyName=$true)]
            [PSCredential]$NAVCredentials,
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVJSONFields )
    PROCESS
    {
        try {
            $ETag = (Get-NAVODataRecord -NAVUri $NAVUri -NAVWebService $NAVWebService -NAVSearchParm $NAVSearchParm -NAVCredentials $NAVCredentials).ETag
            if(-not [string]::IsNullOrEmpty($ETag)) {
                if([string]::IsNullOrEmpty($NAVCredentials)) {
                    $NAVResult = Invoke-RestMethod -Uri "$NAVUri$NavWebService($NavSearchParm)?`$format=json" -Method Patch -UseDefaultCredentials `
                                                   -ContentType "application/json" -Body $NAVJSONFields -Headers $(New-NAVODataRestMethodHeader -ETag $ETag)
                }
                else {
                    $NAVResult = Invoke-RestMethod -Uri "$NAVUri$NavWebService($NavSearchParm)?`$format=json" -Method Patch -Credential $NAVCredentials `
                                                   -ContentType "application/json" -Body $NAVJSONFields -Headers $(New-NAVODataRestMethodHeader -ETag $ETag)
                }
                return $(Get-NAVODataRecord -NAVUri $NAVUri -NAVWebService $NAVWebService -NAVSearchParm $NAVSearchParm -NAVCredentials $NAVCredentials)
            }
            else {
                return $false
            }
        }
        Catch {
            write-host ($_ |Convertfrom-json|select -ExpandProperty "odata.error").message.value -ForegroundColor Red
            return $false
        }
    }
}

function Remove-NAVODataRecord()
{
    [CmdletBinding()]
    param (
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVUri,
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVWebService,
        [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
            [String]$NAVSearchParm,
        [parameter(ValueFromPipelineByPropertyName=$true)]
            [PSCredential]$NAVCredentials )
    PROCESS
    {
        try {
            $ETag = (Get-NAVODataRecord -NAVUri $NAVUri -NAVWebService $NAVWebService -NAVSearchParm $NAVSearchParm -NAVCredentials $NAVCredentials).ETag
            if(-not [string]::IsNullOrEmpty($ETag)) {
                if([string]::IsNullOrEmpty($NAVCredentials)) {
                    $NAVResult = Invoke-RestMethod -Uri "$NAVUri$NavWebService($NavSearchParm)?`$format=json" -Method Delete -UseDefaultCredentials `
                                                   -ContentType "application/json" -Headers $(New-NAVODataRestMethodHeader -ETag $ETag)
                }
                else {
                    $NAVResult = Invoke-RestMethod -Uri "$NAVUri$NavWebService($NavSearchParm)?`$format=json" -Method Delete -Credential $NAVCredentials `
                                                   -ContentType "application/json" -Headers $(New-NAVODataRestMethodHeader -ETag $ETag)
                }
                return $true
            }
            else {
                return $false
            }
        }
        Catch {
            write-host ($_ |Convertfrom-json|select -ExpandProperty "odata.error").message.value -ForegroundColor Red
            return $false
        }
    }
}

##############
## EXAMPLES ##
##############

# Required setup in NAV:
# - Create Page 21 webservice called "Customer"


CLS

$NAVUserName = "NAVDemo1"
$NAVUserPassword = "NAVDemo1"
$NAVServerName = "navdemo.schost.dk"
$NAVServerPort = 7052
$NAVServiceInstance = "NAV_PublicDemo"
$NAVCompany = "CRONUS Danmark A/S 1"
$NAVWebService = "Customer"

$NAVCredentials = New-Object System.Management.Automation.PSCredential($NAVUserName, (ConvertTo-SecureString $NAVUserPassword -AsPlainText -Force))
$NAVUri = "https`://$NAVServerName`:$NAVServerPort/$NAVServiceInstance/OData/Company('"+[uri]::EscapeDataString($NAVCompany)+"')/"

#Test-NAVODataRecord
write-host "Test-NAVODataRecord" -ForegroundColor Yellow
Test-NAVODataRecord -NAVUri $NAVUri -NAVWebService $NAVWebService -NAVSearchParm "No='10000'" -NAVCredentials $NAVCredentials

#Get-NAVODataRecord
write-host "Get-NAVODataRecord" -ForegroundColor Yellow
$NAVCustomer = Get-NAVODataRecord -NAVUri $NAVUri -NAVWebService $NAVWebService -NAVSearchParm "No='10000'" -NAVCredentials $NAVCredentials 
$NAVCustomer

#New-NAVODataRecord
write-host "New-NAVODataRecord" -ForegroundColor Yellow
$NewCustomerNo = "11111"
$NewCustomerName = "PowerShell test"
$NewCustomer = "{""No"" : """ + $NewCustomerNo + """,
                 ""Name"" : """ + $NewCustomerName + """
                }"

New-NAVODataRecord -NAVUri $NAVUri -NAVWebService $NAVWebService -NAVCredentials $NAVCredentials -NAVJSONFields $NewCustomer


#Update-NAVODataRecord
write-host "Update-NAVODataRecord" -ForegroundColor Yellow
$UpdateCustomerNo = "11111"
$UpdateCustomerName = "Updated PowerShell test"
$UpdateCustomerAddress = "PowerShell Street"
$UpdateCustomer = "{""Name"" : """ + $UpdateCustomerName + """,
                    ""Address"" : """ + $UpdateCustomerAddress + """
                   }" 
Update-NAVODataRecord -NAVUri $NAVUri -NAVWebService $NAVWebService -NAVSearchParm "No='11111'" -NAVCredentials $NAVCredentials -NAVJSONFields $UpdateCustomer

#Remove-NAVODataRecord
write-host "Remove-NAVODataRecord" -ForegroundColor Yellow
Remove-NAVODataRecord -NAVUri $NAVUri -NAVWebService $NAVWebService -NAVSearchParm "No='11111'" -NAVCredentials $NAVCredentials

Bemærk følgende omkring eksemplerne:

  1. URL, brugernavn, kodeord, regnskabsnavn, port osv. er opsat til at benytte SystemConnects online Dynamics NAV 2017 demosystem. Det kræver ikke registrering og der er 20 regnskaber/logins til rådighed – så det kan være en fordel lige at skifte væk fra nr. 1 som er opsat. Du kan læse mere om demosystemet ved at klikke her!
    Demosystemet bliver formentligt ændret til Dynamics NAV 2018 når den frigives, så, det kan være nødvendigt at rette oplysningerne – det kan du i givet fald læse mere om ved at følge ovenstående link.
  2. De generiske funktioner burde virke mod alle NAV WebService instanser – og justerer du oplysningerne i eksemplet kan du sikkert også få det til at virke mod dine egen NAV server.
  3. Eksemplet kræver at du logger ind i demosystemet og opsætter en WebService vi kan teste – fx via webklienten ved at klikke her! Login med NAVDemoX og kode NAVDemoX hvor X er et nummer fra 1 til 20. Husk at rette ovenstående eksempel til at bruge samme nummer (Login, kodeord og firmanavn skal ændres). Når du er logget ind skal du opsætte en webservice kaldet “Customer” til page 21, Customer Card.
  4. Microsoft Excel og ældre versioner af PowerShell kan IKKE håndtere at du har skråstreg i NAV regnskabsnavne. Så undgå “A/S” mv. i firmanavne i NAV. Du er velkommen til at omdøbe firmanavnet på vores testsystemer.
  5. Ovenstående er lavet til en NAV-server der kører NAVUserPassword authentication. Jeg vil prøve at lave et senere blogindlæg om hvordan man får liv i ODATA med de øvrige autorisationsmetoder i NAV.

God fornøjelse!

Credits går til Graves Kilsgaard fra KMC som har lavet et kæmpe benarbejde for at lave ovenstående funktioner. Jeg har nemlig kun hjulpet en smule :-).

Skriv et svar