AzureADConnectAPI_utils.ps1

# Initial AADSync server name
$aadsync_server=        "adminwebservice.microsoftonline.com"
$aadsync_client_version="8.0"
$aadsync_client_build=  "1.5.29.0"

# Checks whether the response has redirect
function IsRedirectResponse
{
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [xml]$xml_doc

    )
    Process
    {
        try
        {
            $url=$xml_doc.Envelope.Body.Fault.Detail.BindingRedirectionFault.Url        
            if([string]::IsNullOrEmpty($url))
            {
                $message=$xml_doc.Envelope.Body.Fault.Reason.Text.'#text'
                if(![string]::IsNullOrEmpty($url))
                {
                    $Script:aadsync_server=$url.Split('/')[2]
                    Write-Verbose "ISREDIRECTRESPONSE: Changed server to $Script:aadsync_server"
                    return $True
                }
            }
            else
            {
                $Script:aadsync_server=$url.Split('/')[2]
                Write-Verbose "ISREDIRECTRESPONSE: Changed server to $Script:aadsync_server"
                return $True
            }

            return IsErrorResponse($xml_doc)
            
        }
        catch
        {
            throw $_
        }
    }
}

# Checks whether the response has redirect
function IsErrorResponse
{
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [xml]$xml_doc

    )
    Process
    {
        $error=Select-Xml -Xml $xml_doc -XPath "//*[local-name()='ErrorDescription']"
        if([string]::IsNullOrEmpty($error))
        {
            # All good
            return $False
        }
        else
        {
            # Got error, so throw an exception
            throw $error.Node.'#text'
        }
        
    }
}




# Create SOAP envelope for ADSync
function Create-SyncEnvelope
{
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [String]$AccessToken,

        [Parameter(Mandatory=$True)]
        [String]$Command,

        [Parameter(Mandatory=$True)]
        [String]$Body,

        [Parameter(Mandatory=$True)]
        [String]$Message_id,

        [Parameter()]
        [String]$Server="adminwebservice.microsoftonline.com",

        [Parameter()]
        [switch]$Binary,

        [Parameter()]
        [bool]$IsInstalledOnDc=$False,

        [Parameter()]
        [bool]$RichCoexistenceEnabled=$False,
        
        [Parameter()]
        [int]$Version=1
    )
    Process
    {
        # Set the client ID
        if($Version -eq 2)
        {
            $applicationClient= "6eb59a73-39b2-4c23-a70f-e2e3ce8965b1"
        }
        else
        {
            $applicationClient = "1651564e-7ce4-4d99-88be-0a65050d8dc3"
        }

        # Create the envelope
        $envelope=@"
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
            <s:Header>
                <a:Action s:mustUnderstand="1">http://schemas.microsoft.com/online/aws/change/2010/01/IProvisioningWebService/$Command</a:Action>
                <SyncToken s:role="urn:microsoft.online.administrativeservice" xmlns="urn:microsoft.online.administrativeservice" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
                    <ApplicationId xmlns="http://schemas.microsoft.com/online/aws/change/2010/01">$applicationClient</ApplicationId>
                    <BearerToken xmlns="http://schemas.microsoft.com/online/aws/change/2010/01">$AccessToken</BearerToken>
                    <ClientVersion xmlns="http://schemas.microsoft.com/online/aws/change/2010/01">$aadsync_client_version</ClientVersion>
                    <DirSyncBuildNumber xmlns="http://schemas.microsoft.com/online/aws/change/2010/01">$aadsync_client_build</DirSyncBuildNumber>
                    <FIMBuildNumber xmlns="http://schemas.microsoft.com/online/aws/change/2010/01">$aadsync_client_build</FIMBuildNumber>
                    <IsInstalledOnDC xmlns="http://schemas.microsoft.com/online/aws/change/2010/01">$IsInstalledOnDc</IsInstalledOnDC>
                    <IssueDateTime xmlns="http://schemas.microsoft.com/online/aws/change/2010/01">0001-01-01T00:00:00</IssueDateTime>
                    <LanguageId xmlns="http://schemas.microsoft.com/online/aws/change/2010/01">en-US</LanguageId>
                    <LiveToken xmlns="http://schemas.microsoft.com/online/aws/change/2010/01"/>
                    <ProtocolVersion xmlns="http://schemas.microsoft.com/online/aws/change/2010/01">2.0</ProtocolVersion>
                    <RichCoexistenceEnabled xmlns="http://schemas.microsoft.com/online/aws/change/2010/01">$RichCoexistenceEnabled</RichCoexistenceEnabled>
                    <TrackingId xmlns="http://schemas.microsoft.com/online/aws/change/2010/01">$Message_id</TrackingId>
                </SyncToken>
                <a:MessageID>urn:uuid:$message_id</a:MessageID>
                <a:ReplyTo>
                    <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
                </a:ReplyTo>
                <a:To s:mustUnderstand="1">https://$Server/provisioningservice.svc</a:To>
            </s:Header>
            <s:Body>
                $Body
            </s:Body>
        </s:Envelope>
"@

        # Debug
        Write-Debug "ENVELOPE ($Command): $envelope"

        # Return the envelope as binary if requested
        if($Binary)
        {
            return XmlToBinary $envelope -Dictionary (Get-XmlDictionary -Type WCF)
        }
        else
        {
            $envelope
        }
    }
}

# Calls the ADSync SOAP API
function Call-ADSyncAPI
{
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [byte[]]$Envelope,
        [Parameter(Mandatory=$True)]
        [string]$Command,
        [Parameter(Mandatory=$True)]
        [string]$Tenant_id,
        [Parameter(Mandatory=$True)]
        [string]$Message_id,
        [Parameter(Mandatory=$False)]
        [string]$Server="adminwebservice.microsoftonline.com"
    )
    Process
    {
        $headers=@{
            "Host" =                           $Server
            "x-ms-aadmsods-appid"=             "1651564e-7ce4-4d99-88be-0a65050d8dc3"
            "x-ms-aadmsods-apiaction"=         $Command
            "client-request-id"=               $Message_id
            "x-ms-aadmsods-clientversion"=     $aadsync_client_version
            "x-ms-aadmsods-dirsyncbuildnumber"=$aadsync_client_build
            "x-ms-aadmsods-fimbuildnumber"=    $aadsync_client_build
            "x-ms-aadmsods-tenantid"=          $Tenant_id
            "User-Agent"=""
                                                                                     
            
        }
        # Verbose
        Write-Debug "CALL-ADSYNCAPI HEADERS: $($headers | Out-String)"

        $stream=$null

        # Call the API
        try
        {
            # Sometimes no error at all..?
            $response=Invoke-WebRequest -UseBasicParsing -Uri "https://$Server/provisioningservice.svc" -ContentType "application/soap+msbin1" -Method POST -Body $envelope -Headers $headers
            $stream=$response.RawContentStream
        }
        catch
        {
            # Should give error 500
            $Exception = $_.Exception
            if($Exception.Message -like "*500*")
            {
                $stream=$Exception.Response.GetResponseStream()
            }
            else
            {
                Throw $Exception
            }
        }
        
        $bytes=$stream.toArray()
        $bytes
    }
}

# Utility function for Provision-AzureADSyncObject to add property value
function Add-PropertyValue
{
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [String]$Key,
        [Parameter(Mandatory=$False)]
        [PSobject]$Value,
        [ValidateSet('string','bool','base64','long','ArrayOfstring','ArrayOfbase64')]
        [String]$Type="string"
    )
    Process
    {
        
        if(![string]::IsNullOrEmpty($Value))
        {
            $PropBlock="<c:KeyValueOfstringanyType><c:Key>$Key</c:Key>"
            switch($Type)
            {
                'long' { $PropBlock += "<c:Value i:type=""d:long"" xmlns:d=""http://www.w3.org/2001/XMLSchema"">$Value</c:Value>" }
                'bool' { $PropBlock += "<c:Value i:type=""d:boolean"" xmlns:d=""http://www.w3.org/2001/XMLSchema"">$($Value.toString().toLower())</c:Value>" }
                'base64'{ $PropBlock += "<c:Value i:type=""d:base64Binary"" xmlns:d=""http://www.w3.org/2001/XMLSchema"">$Value</c:Value>" }
                'ArrayOfstring'{ 
                    $PropBlock += "<c:Value i:type=""c:ArrayOfstring"">"
                    foreach($stringValue in $Value)
                    {
                        $PropBlock += "<c:string>$stringValue</c:string>"
                    }

                    $PropBlock += "</c:Value>" 
                    }
                'ArrayOfbase64'{ 
                    $PropBlock += "<c:Value i:type=""c:ArrayOfbase64Binary"">"
                    foreach($stringValue in $Value)
                    {
                        $PropBlock += "<c:base64Binary>$stringValue</c:base64Binary>"
                    }

                    $PropBlock += "</c:Value>" 
                    }
                default { $PropBlock += "<c:Value i:type=""d:string"" xmlns:d=""http://www.w3.org/2001/XMLSchema"">$Value</c:Value>" }
            }

            $PropBlock+="</c:KeyValueOfstringanyType>"

            return $PropBlock
        }
    }
}

# Creates a AADHash for given password
Function Create-AADHash {

    [cmdletbinding()]

    param(
        [parameter(Mandatory=$false)]
        [String]$Password,
        [parameter(Mandatory=$false)]
        [String]$Hash,
        [parameter(Mandatory=$false)]
        [int]$Iterations=1000
    )
    Process
    {
        if([string]::IsNullOrEmpty($Hash))
        {
            # Calculate MD4 from the password (Unicode)
            $md4 = (Get-MD4 -bArray ([System.Text.UnicodeEncoding]::Unicode.GetBytes($password))).ToUpper()
            
        }
        elseif($Hash.Length -ne 32)
        {
            Throw "Invalid hash length!"
        }
        else
        {
            $md4=$Hash
        }

        $md4bytes = ([System.Text.UnicodeEncoding]::Unicode.GetBytes($md4))
        

        # Generate random 10-byte salt
        $salt=@()
        for($count = 0; $count -lt 10 ; $count++)
        {
            $salt += Get-Random -Minimum 0 -Maximum 0xFF
        }

        # Calculate hash using 1000 iterations and SHA256
        $pbkdf2 = New-Object System.Security.Cryptography.Rfc2898DeriveBytes($md4bytes,[byte[]]$salt,$Iterations,"SHA256")
        $bytes = $pbkdf2.GetBytes(32)

        # Convert to hex strings
        $hexbytes=Convert-ByteArrayToHex $bytes
        $hexsalt=Convert-ByteArrayToHex $salt

        # Create the return value
        $retVal = "v1;PPH1_MD4,$hexsalt,$Iterations,$hexbytes;"

        # Verbose
        Write-Debug $retVal
        
        # Return
        return $retVal
    }
    
}