Configure Airwatch for REST API Connections - part II

Published on 09 March 2018

Here is the second part of the series on connecting to Airwatch via an API. Why? Because you can then do things without the GUI, and put scripts together to automate specific tasks. Even if you trigger then manually!

Part I looked at getting your cert, and importing it. You then need to use it to connect. The easiest way is to use a couple of functions. I don't take credit for these, they are freely available on the web, although I have adjusted them slightly to make them more like a function, and to meet my needs a little more.

Before that though, you need to make sure you load the System.Security assembly. Actually, I noted that it worked fine in Powershell ISE without it, but if you are using VSCODE, you will need to make this the first line in your module (or script):

Add-Type -AssemblyName System.Security

Once you have that in your script, you should find it works without throwing errors like this:

System.Management.Automation.PSArgumentException: Cannot find type [System.Security.Cryptography.Pkcs.ContentInfo]: verify that the assembly containing this type is loaded.

So, on to the functions. Here is the first:

function Get-CMSURLAuthorizationHeader {
        [CmdletBinding()]
        [OutputType([string])]
        Param
        (
            # Input the URL to be
            [Parameter(Mandatory=$true,
                    ValueFromPipelineByPropertyName=$true,
                    Position=0)]
            [uri]$URL,

            # Specify the Certificate to be used 
            [Parameter(Mandatory=$true,
                        ValueFromPipeline)]
            [System.Security.Cryptography.X509Certificates.X509Certificate2]
            $Certificate
        )

        Begin {            
        }
        Process {
        Try { 
                #Get the Absolute Path of the URL encoded in UTF8
                $bytes = [System.Text.Encoding]::UTF8.GetBytes(($Url.AbsolutePath))

                #Open Memory Stream passing the encoded bytes
                $MemStream = New-Object -TypeName System.Security.Cryptography.Pkcs.ContentInfo -ArgumentList (,$bytes) -ErrorAction Stop

                #Create the Signed CMS Object providing the ContentInfo (from Above) and True specifying that this is for a detached signature
                $SignedCMS = New-Object -TypeName System.Security.Cryptography.Pkcs.SignedCms -ArgumentList $MemStream,$true -ErrorAction Stop

                #Create an instance of the CMSigner class - this class object provide signing functionality
                $CMSigner = New-Object -TypeName System.Security.Cryptography.Pkcs.CmsSigner -ArgumentList $Certificate -Property @{IncludeOption = [System.Security.Cryptography.X509Certificates.X509IncludeOption]::EndCertOnly} -ErrorAction Stop

                #Add the current time as one of the signing attribute
                $null = $CMSigner.SignedAttributes.Add((New-Object -TypeName System.Security.Cryptography.Pkcs.Pkcs9SigningTime))

                #Compute the Signature
                $SignedCMS.ComputeSignature($CMSigner)

                #As per the documentation the authorization header needs to be in the format 'CMSURL `1 <Signed Content>'
                #One can change this value as per the format the Vendor's REST API documentation wants.
                $CMSHeader = '{0}{1}{2}' -f 'CMSURL','`1 ',$([System.Convert]::ToBase64String(($SignedCMS.Encode())))
                Write-Output -InputObject $CMSHeader
            }
            Catch { 
                Write-host $_.exception -ErrorAction stop
            }
        }
        End {
        }
    } #end function

That will get your authorisation header, and here is the second function you will need:

function Format-HTTPHeader {
        Param ([string]$URL,[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate)

    $Text  = @{
                'Authorization' = "$(Get-CMSURLAuthorizationHeader -URL $URL -Certificate $Certificate)";
                'aw-tenant-code' = "FNS2TF4z1GO/cVFhauwXr+MmpC9tZ7igY86JQZq7eL0=";
            }
            
    Return $Text
    } #end function

Actually in this one, you will need to change the aw-tenant-code. You could also turn it into a variable if you want and pass it to the function, but unless you have several AW tenants, it isn't something you are likely to need to change once it is set. If you are wondering where you get the aw-tenant-code, see Part I, as that will tell you.

So you should have now have the fundamentals. You have you account, certificate, and functions, so now to connect!

One of the simplest solutions is to use the following code:

function Get-AWUserID {
    <#
        .SYNOPSIS
            This function will return the Airwatch ID for users where attributes match the value provided.
        .DESCRIPTION
            To find a single user, use a unique field such as username. 
            Some attributes that can be used to search include:
                Firstname
                Lastname
                Email
                locationgroupID
                Role
                Username
        .EXAMPLE
                Get-AWUserID -Server "https://awc.cn999.awmdm.com" -Attribute "username" -Value "aaa1" -certificateSubjectName "CN=4610:APIMGMT-COMP"
    #>

    Param ([string]$server,[string]$attribute,[string]$value,[String]$CertificateSubjectName,[string]$proxyuse)
    
    Begin {
        #Build string for API connection
        $APIURL = "/API/system/users/search?$attribute=$value"
        [uri]$URI = $server+$APIURL
        $reply = $null
        $Certificate = Get-ChildItem -Path Cert:\CurrentUser\my | Where-Object Subject -eq $CertificateSubjectName
        $headers = Format-HTTPHeader $URI $Certificate

    }#end Begin
    Process {   
        Try {
            If (!$ProxyUse) {
                $reply = Invoke-restmethod -uri $URI -DisableKeepAlive -Method Get -Headers $headers -ContentType "application/json" 
            }
            else {
                $proxy = [system.Net.webrequest]::GetSystemWebProxy()
                $proxy.Credentials = [System.Net.Credentialcache]::DefaultNetworkCredentials
                $proxyuri = $proxy.GetProxy($server)
                $reply = Invoke-restmethod -uri $URI -DisableKeepAlive -Method Get -Headers $headers -ContentType "application/json" -ProxyUseDefaultCredentials -Proxy $proxyuri -Body $json
            }

        }#end Try
        Catch {
        Write-Error "$($_.Exception.Message) - Line Number $($_.InvocationInfo.ScriptLineNumber)"
        }#end Catch
        Return $reply
    }#end process
    End {}
    } #end function    

It should be fairly self-explanatory, the formatting isn't the best. So if you want to see the code with pretty formatting, go here.

comments powered by Disqus