PSRP.ps1

# PowerShell Remoting Protocol functions

# Creates a PowerShell remote shell
# Apr 24th 2019
function Create-PSRPShell
{
    [cmdletbinding()]
    Param(
        [Parameter()]
        [System.Management.Automation.PSCredential]$Credentials,
        [Parameter()]
        [Bool]$Oauth=$false,
        [Parameter(Mandatory=$False)]
        [String]$SessionId=((New-Guid).ToString())
    )
    Process
    {

        $Body = @"
        <rsp:Shell xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" Name="WinRM$(Get-Random -Maximum 1000)" ShellId="$((New-Guid).ToString().ToUpper())">
            <rsp:InputStreams>stdin pr</rsp:InputStreams>
            <rsp:OutputStreams>stdout</rsp:OutputStreams>
            <creationXml xmlns="http://schemas.microsoft.com/powershell">AAAAAAAAAAcAAAAAAAAAAAMAAALwAgAAAAIAAQAGWlblyniqQabvSrnLG9XKAAAAAAAAAAAAAAAAAAAAAO+7vzxPYmogUmVmSWQ9IjAiPjxNUz48VmVyc2lvbiBOPSJwcm90b2NvbHZlcnNpb24iPjIuMzwvVmVyc2lvbj48VmVyc2lvbiBOPSJQU1ZlcnNpb24iPjIuMDwvVmVyc2lvbj48VmVyc2lvbiBOPSJTZXJpYWxpemF0aW9uVmVyc2lvbiI+MS4xLjAuMTwvVmVyc2lvbj48QkEgTj0iVGltZVpvbmUiPkFBRUFBQUQvLy8vL0FRQUFBQUFBQUFBRUFRQUFBQnhUZVhOMFpXMHVRM1Z5Y21WdWRGTjVjM1JsYlZScGJXVmFiMjVsQkFBQUFCZHRYME5oWTJobFpFUmhlV3hwWjJoMFEyaGhibWRsY3cxdFgzUnBZMnR6VDJabWMyVjBEbTFmYzNSaGJtUmhjbVJPWVcxbERtMWZaR0Y1YkdsbmFIUk9ZVzFsQXdBQkFSeFRlWE4wWlcwdVEyOXNiR1ZqZEdsdmJuTXVTR0Z6YUhSaFlteGxDUWtDQUFBQUFOQ0l3eEFBQUFBS0NnUUNBQUFBSEZONWMzUmxiUzVEYjJ4c1pXTjBhVzl1Y3k1SVlYTm9kR0ZpYkdVSEFBQUFDa3h2WVdSR1lXTjBiM0lIVm1WeWMybHZiZ2hEYjIxd1lYSmxjaEJJWVhOb1EyOWtaVkJ5YjNacFpHVnlDRWhoYzJoVGFYcGxCRXRsZVhNR1ZtRnNkV1Z6QUFBREF3QUZCUXNJSEZONWMzUmxiUzVEYjJ4c1pXTjBhVzl1Y3k1SlEyOXRjR0Z5WlhJa1UzbHpkR1Z0TGtOdmJHeGxZM1JwYjI1ekxrbElZWE5vUTI5a1pWQnliM1pwWkdWeUNPeFJPRDhBQUFBQUNnb0RBQUFBQ1FNQUFBQUpCQUFBQUJBREFBQUFBQUFBQUJBRUFBQUFBQUFBQUFzPTwvQkE+PC9NUz48L09iaj4AAAAAAAAACAAAAAAAAAAAAwAACtwCAAAABAABAAZaVuXKeKpBpu9Kucsb1coAAAAAAAAAAAAAAAAAAAAA77u/PE9iaiBSZWZJZD0iMCI+PE1TPjxJMzIgTj0iTWluUnVuc3BhY2VzIj4xPC9JMzI+PEkzMiBOPSJNYXhSdW5zcGFjZXMiPjE8L0kzMj48T2JqIE49IlBTVGhyZWFkT3B0aW9ucyIgUmVmSWQ9IjEiPjxUTiBSZWZJZD0iMCI+PFQ+U3lzdGVtLk1hbmFnZW1lbnQuQXV0b21hdGlvbi5SdW5zcGFjZXMuUFNUaHJlYWRPcHRpb25zPC9UPjxUPlN5c3RlbS5FbnVtPC9UPjxUPlN5c3RlbS5WYWx1ZVR5cGU8L1Q+PFQ+U3lzdGVtLk9iamVjdDwvVD48L1ROPjxUb1N0cmluZz5EZWZhdWx0PC9Ub1N0cmluZz48STMyPjA8L0kzMj48L09iaj48T2JqIE49IkFwYXJ0bWVudFN0YXRlIiBSZWZJZD0iMiI+PFROIFJlZklkPSIxIj48VD5TeXN0ZW0uVGhyZWFkaW5nLkFwYXJ0bWVudFN0YXRlPC9UPjxUPlN5c3RlbS5FbnVtPC9UPjxUPlN5c3RlbS5WYWx1ZVR5cGU8L1Q+PFQ+U3lzdGVtLk9iamVjdDwvVD48L1ROPjxUb1N0cmluZz5Vbmtub3duPC9Ub1N0cmluZz48STMyPjI8L0kzMj48L09iaj48T2JqIE49IkFwcGxpY2F0aW9uQXJndW1lbnRzIiBSZWZJZD0iMyI+PFROIFJlZklkPSIyIj48VD5TeXN0ZW0uTWFuYWdlbWVudC5BdXRvbWF0aW9uLlBTUHJpbWl0aXZlRGljdGlvbmFyeTwvVD48VD5TeXN0ZW0uQ29sbGVjdGlvbnMuSGFzaHRhYmxlPC9UPjxUPlN5c3RlbS5PYmplY3Q8L1Q+PC9UTj48RENUPjxFbj48UyBOPSJLZXkiPlBTVmVyc2lvblRhYmxlPC9TPjxPYmogTj0iVmFsdWUiIFJlZklkPSI0Ij48VE5SZWYgUmVmSWQ9IjIiIC8+PERDVD48RW4+PFMgTj0iS2V5Ij5QU1ZlcnNpb248L1M+PFZlcnNpb24gTj0iVmFsdWUiPjUuMS4xNzEzNC41OTA8L1ZlcnNpb24+PC9Fbj48RW4+PFMgTj0iS2V5Ij5QU0VkaXRpb248L1M+PFMgTj0iVmFsdWUiPkRlc2t0b3A8L1M+PC9Fbj48RW4+PFMgTj0iS2V5Ij5QU0NvbXBhdGlibGVWZXJzaW9uczwvUz48T2JqIE49IlZhbHVlIiBSZWZJZD0iNSI+PFROIFJlZklkPSIzIj48VD5TeXN0ZW0uVmVyc2lvbltdPC9UPjxUPlN5c3RlbS5BcnJheTwvVD48VD5TeXN0ZW0uT2JqZWN0PC9UPjwvVE4+PExTVD48VmVyc2lvbj4xLjA8L1ZlcnNpb24+PFZlcnNpb24+Mi4wPC9WZXJzaW9uPjxWZXJzaW9uPjMuMDwvVmVyc2lvbj48VmVyc2lvbj40LjA8L1ZlcnNpb24+PFZlcnNpb24+NS4wPC9WZXJzaW9uPjxWZXJzaW9uPjUuMS4xNzEzNC41OTA8L1ZlcnNpb24+PC9MU1Q+PC9PYmo+PC9Fbj48RW4+PFMgTj0iS2V5Ij5DTFJWZXJzaW9uPC9TPjxWZXJzaW9uIE49IlZhbHVlIj40LjAuMzAzMTkuNDIwMDA8L1ZlcnNpb24+PC9Fbj48RW4+PFMgTj0iS2V5Ij5CdWlsZFZlcnNpb248L1M+PFZlcnNpb24gTj0iVmFsdWUiPjEwLjAuMTcxMzQuNTkwPC9WZXJzaW9uPjwvRW4+PEVuPjxTIE49IktleSI+V1NNYW5TdGFja1ZlcnNpb248L1M+PFZlcnNpb24gTj0iVmFsdWUiPjMuMDwvVmVyc2lvbj48L0VuPjxFbj48UyBOPSJLZXkiPlBTUmVtb3RpbmdQcm90b2NvbFZlcnNpb248L1M+PFZlcnNpb24gTj0iVmFsdWUiPjIuMzwvVmVyc2lvbj48L0VuPjxFbj48UyBOPSJLZXkiPlNlcmlhbGl6YXRpb25WZXJzaW9uPC9TPjxWZXJzaW9uIE49IlZhbHVlIj4xLjEuMC4xPC9WZXJzaW9uPjwvRW4+PC9EQ1Q+PC9PYmo+PC9Fbj48L0RDVD48L09iaj48T2JqIE49Ikhvc3RJbmZvIiBSZWZJZD0iNiI+PE1TPjxPYmogTj0iX2hvc3REZWZhdWx0RGF0YSIgUmVmSWQ9IjciPjxNUz48T2JqIE49ImRhdGEiIFJlZklkPSI4Ij48VE4gUmVmSWQ9IjQiPjxUPlN5c3RlbS5Db2xsZWN0aW9ucy5IYXNodGFibGU8L1Q+PFQ+U3lzdGVtLk9iamVjdDwvVD48L1ROPjxEQ1Q+PEVuPjxJMzIgTj0iS2V5Ij45PC9JMzI+PE9iaiBOPSJWYWx1ZSIgUmVmSWQ9IjkiPjxNUz48UyBOPSJUIj5TeXN0ZW0uU3RyaW5nPC9TPjxTIE49IlYiPldpbmRvd3MgUG93ZXJTaGVsbCBJU0U8L1M+PC9NUz48L09iaj48L0VuPjxFbj48STMyIE49IktleSI+NTwvSTMyPjxPYmogTj0iVmFsdWUiIFJlZklkPSIxMCI+PE1TPjxTIE49IlQiPlN5c3RlbS5NYW5hZ2VtZW50LkF1dG9tYXRpb24uSG9zdC5TaXplPC9TPjxPYmogTj0iViIgUmVmSWQ9IjExIj48TVM+PEkzMiBOPSJ3aWR0aCI+ODg8L0kzMj48STMyIE49ImhlaWdodCI+MDwvSTMyPjwvTVM+PC9PYmo+PC9NUz48L09iaj48L0VuPjxFbj48STMyIE49IktleSI+MjwvSTMyPjxPYmogTj0iVmFsdWUiIFJlZklkPSIxMiI+PE1TPjxTIE49IlQiPlN5c3RlbS5NYW5hZ2VtZW50LkF1dG9tYXRpb24uSG9zdC5Db29yZGluYXRlczwvUz48T2JqIE49IlYiIFJlZklkPSIxMyI+PE1TPjxJMzIgTj0ieCI+MDwvSTMyPjxJMzIgTj0ieSI+MDwvSTMyPjwvTVM+PC9PYmo+PC9NUz48L09iaj48L0VuPjxFbj48STMyIE49IktleSI+MTwvSTMyPjxPYmogTj0iVmFsdWUiIFJlZklkPSIxNCI+PE1TPjxTIE49IlQiPlN5c3RlbS5Db25zb2xlQ29sb3I8L1M+PEkzMiBOPSJWIj4tMTwvSTMyPjwvTVM+PC9PYmo+PC9Fbj48RW4+PEkzMiBOPSJLZXkiPjA8L0kzMj48T2JqIE49IlZhbHVlIiBSZWZJZD0iMTUiPjxNUz48UyBOPSJUIj5TeXN0ZW0uQ29uc29sZUNvbG9yPC9TPjxJMzIgTj0iViI+LTE8L0kzMj48L01TPjwvT2JqPjwvRW4+PC9EQ1Q+PC9PYmo+PC9NUz48L09iaj48QiBOPSJfaXNIb3N0TnVsbCI+ZmFsc2U8L0I+PEIgTj0iX2lzSG9zdFVJTnVsbCI+ZmFsc2U8L0I+PEIgTj0iX2lzSG9zdFJhd1VJTnVsbCI+ZmFsc2U8L0I+PEIgTj0iX3VzZVJ1bnNwYWNlSG9zdCI+ZmFsc2U8L0I+PC9NUz48L09iaj48L01TPjwvT2JqPg==</creationXml>
        </rsp:Shell>
"@


        Write-Verbose "Session Id: $SessionId"
        $Envelope = Create-PSRPEnvelope -SessionId $SessionId -Body $Body -Action Create -Option @("protocolversion","2.3")
        
        # Create the Shell
        try
        {
            $shell_response = Call-PSRP -Envelope $Envelope -Credentials $Credentials -Oauth $Oauth
        }
        catch
        {
            # Probably wrong credentials or access token
            Write-error $_.ToString().split("]")[1].trim()
            return
        }

        [xml]$response = $shell_response
        
        # Save the shell id for the later use
        $Shell_Id = $response.Envelope.Body.Shell.ShellId
        Write-Verbose "ShellId: $Shell_Id"

        # Get the output to read session capabilities etc.
        $response = Receive-PSRP -Credentials $Credentials -SessionId $SessionId -Shell_Id $Shell_Id -Oauth $Oauth
        foreach($message in $response.Envelope.Body.ReceiveResponse.Stream)
        {
            $parsed_message = Parse-PSRPMessage -Base64Value $message.'#text'
        }
        
        # Read the rest of the output
        while($parsed_message.'Message type' -ne "Runspool state")
        {
            try
            {
                $response = Receive-PSRP -Credentials $Credentials -SessionId $SessionId -Shell_Id $Shell_Id -Oauth $Oauth
                $message = $response.Envelope.Body.ReceiveResponse.Stream.'#text'
                $parsed_message = Parse-PSRPMessage -Base64Value $message
            }
            catch
            {
                break
            }
            
        }

        return $Shell_Id
       
    }
        
}

# Gets other domains of the given tenant
# Apr 24th 2019
function Get-TenantDomains2
{
<#
    .SYNOPSIS
    Gets other domains from the tenant of the given domain
 
    .DESCRIPTION
    Uses Exchange Online "feature" that allows Get-FederationInformation cmdlet to retrive other
    domains from the tenant of the given domain. The tenant used to retrieve information can
    be any tenant having Exchange Online, including trial tenants.
    The given user MUST have GlobalAdmin / CompanyAdministrator role in the tenant running the function,
    but no rights to the target tenant is needed.
 
    Due to Exchange Online, this function is extremely slow, can take 10 seconds or more to complete.
 
    The given domain SHOULD be Managed, federated domains are not always found for some reason.
    If nothing is found, try to use <domain>.onmicrosoft.com
 
    .Example
    Get-AADIntTenantDomains -Credentials $Cred -Domain company.com
 
    company.com
    company.fi
    company.co.uk
    company.onmicrosoft.com
    company.mail.onmicrosoft.com
 
    .Example
    $at = Get-AADIntAccessTokenForEXO
    PS C:\>Get-AADIntTenantDomains -AccessToken $at -Domain company.com
 
    company.com
    company.fi
    company.co.uk
    company.onmicrosoft.com
    company.mail.onmicrosoft.com
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$False)]
        [System.Management.Automation.PSCredential]$Credentials,
        [Parameter(Mandatory=$False)]
        [String]$AccessToken,
        [Parameter(Mandatory=$True)]
        [String]$Domain
    )
    Process
    {
        # First, check if the domain is registered to Azure AD
        $tenantId = Get-TenantID -Domain $Domain
        if([string]::IsNullOrEmpty($tenantId))
        {
            Write-Error "Domain $domain is not registered to any Azure AD tenant"
            return
        }

        # A fixed runspacel pool ID, used in PSRP messages
        $runspacePoolId = [guid]"e5565a06-78ca-41aa-a6ef-4ab9cb1bd5ca"

        # Counter for Object IDs
        $ObjectId=10

        $Oauth=$false

        # If Credentials is null, create the credentials object from AccessToken manually
        if($Credentials -eq $null)
        {
            # Get from cache if not provided
            $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://outlook.office365.com" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
            
            $upn = (Read-Accesstoken $AccessToken).upn
            $password = ConvertTo-SecureString -String "Bearer $AccessToken" -AsPlainText -Force
            $Credentials = [System.Management.Automation.PSCredential]::new($upn,$password)
            $Oauth=$True
        }

        # Create a shell
        $SessionId = (New-Guid).ToString().ToUpper()
        
        $Shell_Id = Create-PSRPShell -Credentials $Credentials -SessionId $SessionId -Oauth $Oauth
        if([string]::IsNullOrEmpty($Shell_Id))
        {
            # Something went wrong, exit
            return
        }

        # Create an arguments message (uses the fixed runspace pool ID)
        $arguments = @"
<Obj RefId="0"><MS><Obj N="PowerShell" RefId="1"><MS><Obj N="Cmds" RefId="2"><TN RefId="0"><T>System.Collections.Generic.List``1[[System.Management.Automation.PSObject, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>System.Object</T></TN><LST><Obj RefId="3"><MS><S N="Cmd">Get-FederationInformation</S><B N="IsScript">false</B><Nil N="UseLocalScope" /><Obj N="MergeMyResult" RefId="4"><TN RefId="1"><T>System.Management.Automation.Runspaces.PipelineResultTypes</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeToResult" RefId="5"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergePreviousResults" RefId="6"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeError" RefId="7"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeWarning" RefId="8"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeVerbose" RefId="9"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeDebug" RefId="10"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeInformation" RefId="11"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="Args" RefId="12"><TNRef RefId="0" /><LST><Obj RefId="13"><MS><S N="N">-DomainName:</S><S N="V">$Domain</S></MS></Obj><Obj RefId="14"><MS><S N="N">-BypassAdditionalDomainValidation:</S><Obj N="V" RefId="15"><TN RefId="2"><T>System.Management.Automation.SwitchParameter</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>True</ToString><Props><B N="IsPresent">true</B></Props></Obj></MS></Obj></LST></Obj></MS></Obj></LST></Obj><B N="IsNested">false</B><Nil N="History" /><B N="RedirectShellErrorOutputPipe">true</B></MS></Obj><B N="NoInput">true</B><Obj N="ApartmentState" RefId="14"><TN RefId="2"><T>System.Threading.ApartmentState</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>Unknown</ToString><I32>2</I32></Obj><Obj N="RemoteStreamOptions" RefId="15"><TN RefId="3"><T>System.Management.Automation.RemoteStreamOptions</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>0</ToString><I32>0</I32></Obj><B N="AddToHistory">true</B><Obj N="HostInfo" RefId="16"><MS><B N="_isHostNull">true</B><B N="_isHostUINull">true</B><B N="_isHostRawUINull">true</B><B N="_useRunspaceHost">true</B></MS></Obj><B N="IsNested">false</B></MS></Obj>
"@

        $message = Create-PSRPMessage -Data $arguments -Type Create_pipeline -ObjectId ($ObjectId++) -MSG_RPID $runspacePoolId
            
        $commandId = (New-Guid).ToString().ToUpper()
        
        $Body = @"
        <rsp:CommandLine xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" CommandId="$commandId">
            <rsp:Command>Get-FederationInformation</rsp:Command>
            <rsp:Arguments>$message</rsp:Arguments>
        </rsp:CommandLine>
"@

        # Create the envelope for Get-FederationInfo -cmdlet
        $Envelope = Create-PSRPEnvelope -Shell_Id $Shell_Id -SessionId $SessionId -Body $Body -Action Command
        
        $Domains = @()

        
        try
        {
            # Make the command call
            $response = Call-PSRP -Envelope $Envelope -Credentials $Credentials -Oauth $Oauth
 
            $get_output = $true

            # Get the output
            while($get_output)
            {
                try
                {
                    [xml]$response = Receive-PSRP -Credentials $Credentials -SessionId $SessionId -Shell_Id $Shell_Id -CommandId $commandId -Oauth $Oauth

                    # Update the progress so we know that something is going on..
                    Write-Progress -Activity "Retrieving domains from tenant ($tenantId)" -Status $(Get-WaitingMessage) -PercentComplete 50

                    # Loop through streams
                    foreach($message in $response.Envelope.Body.ReceiveResponse.Stream)
                    {
                        $parsed_message = Parse-PSRPMessage -Base64Value $message.'#text'
                        [xml]$xmlData = $parsed_message.Data

                        if($parsed_message.'Message type' -eq "Progress record")
                        {
                            # Extract the StatusDescription and PercentComlete from the message
                            $StatusDescription = (Select-Xml -Xml $xmlData -XPath "//*[@N='StatusDescription']").Node.'#text'
                            [int]$PercentComlete = (Select-Xml -Xml $xmlData -XPath "//*[@N='PercentComplete']").Node.'#text'
                        }
                        elseif($parsed_message.'Message type' -eq "Pipeline output")
                        {
                            $DomainNames = (Select-Xml -Xml $xmlData -XPath "//*[@N='DomainNames']")

                            $Domains = $DomainNames.node.lst.S
                        }
                        elseif($parsed_message.'Message type' -eq "Pipeline state")
                        {
                            $errorRecord = (Select-Xml -Xml $xmlData -XPath "//*[@N='ErrorRecord']").Node.'#text'
                            if(![string]::IsNullOrEmpty($errorRecord))
                            {
                                # Something went wrong, probably not an admin user
                                Write-Error "Got an error! May be not an admin user?"
                                Write-Verbose "ERROR: $errorRecord"
                            }
                        }
                    }

                    # Loop thru the CommandStates
                    foreach($state in $response.Envelope.Body.ReceiveResponse.CommandState)
                    {
                        # Okay, we're done!
                        $exitCode = $state.ExitCode
                        if(![string]::IsNullOrEmpty($exitCode))
                        {
                            Write-Progress -Activity "Retrieving domains" -Completed
                            $get_output = $false
                        }
                    }
                }
                catch
                {
                    # Something wen't wrong so exit the loop
                    break
                }
            
            }
        }
        catch
        {
            # Do nothing
        }

        
        # Finally remove the shell
        Remove-PSRPShell -Credentials $Credentials -Shell_Id $Shell_Id -SessionId $SessionId -Oauth $Oauth
       
        # Return domain names
        return $Domains | sort

    }
        
}


# Removes the shell, a.k.a. disconnects from the ps host
# Apr 24th 2019
function Remove-PSRPShell
{
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [System.Management.Automation.PSCredential]$Credentials,
        [Parameter()]
        [Bool]$Oauth=$False,
        [Parameter(Mandatory=$True)]
        [String]$Shell_Id,
        [Parameter(Mandatory=$True)]
        [String]$SessionId
    )
    Process
    {
               
        $Envelope = Create-PSRPEnvelope -SessionId $SessionId -Body " " -Action Delete -Shell_Id $Shell_Id
        
        $response = Call-PSRP -Envelope $Envelope -Credentials $Credentials -Oauth $Oauth

        # Nothing to return..
    }
        
}

# Get Mobile Devices
# May 9th 2019
function Get-MobileDevices
{
<#
    .SYNOPSIS
    Gets mobile devices for the current user or all devices (if admin)
 
    .DESCRIPTION
    Retrieves a list of mobile devices using Remote Exchange Online PowerShell
 
    .Example
    Get-AADIntMobileDevices -Credentials $Cred
 
    .Example
    $at = Get-AADIntAccessTokenForEXO
    PS C:\>Get-AADIntTenantDomains -AccessToken $at
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$False)]
        [System.Management.Automation.PSCredential]$Credentials,
        [Parameter(Mandatory=$False)]
        [String]$AccessToken
    )
    Process
    {
        # A fixed runspacel pool ID, used in PSRP messages
        $runspacePoolId = [guid]"e5565a06-78ca-41aa-a6ef-4ab9cb1bd5ca"

        # Counter for Object IDs
        $ObjectId=10

        $Oauth=$false

        # If Credentials is null, create the credentials object from AccessToken manually
        if($Credentials -eq $null)
        {
            # Get from cache if not provided
            $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://outlook.office365.com" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
            
            $upn = (Read-Accesstoken $AccessToken).upn
            $password = ConvertTo-SecureString -String "Bearer $AccessToken" -AsPlainText -Force
            $Credentials = [System.Management.Automation.PSCredential]::new($upn,$password)
            $Oauth=$True
        }

        # Create a shell
        $SessionId = (New-Guid).ToString().ToUpper()
        
        $Shell_Id = Create-PSRPShell -Credentials $Credentials -SessionId $SessionId -Oauth $Oauth
        if([string]::IsNullOrEmpty($Shell_Id))
        {
            # Something went wrong, exit
            return
        }

        # Create an arguments message
        $arguments = @"
<Obj RefId="0"><MS><Obj N="PowerShell" RefId="1"><MS><Obj N="Cmds" RefId="2"><TN RefId="0"><T>System.Collections.Generic.List`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>System.Object</T></TN><LST><Obj RefId="3"><MS><S N="Cmd">Get-MobileDevice</S><B N="IsScript">false</B><Nil N="UseLocalScope" /><Obj N="MergeMyResult" RefId="4"><TN RefId="1"><T>System.Management.Automation.Runspaces.PipelineResultTypes</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeToResult" RefId="5"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergePreviousResults" RefId="6"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeError" RefId="7"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeWarning" RefId="8"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeVerbose" RefId="9"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeDebug" RefId="10"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeInformation" RefId="11"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="Args" RefId="12"><TNRef RefId="0" /><LST /></Obj></MS></Obj></LST></Obj><B N="IsNested">false</B><Nil N="History"/><B N="RedirectShellErrorOutputPipe">true</B></MS></Obj><B N="NoInput">true</B><Obj N="ApartmentState" RefId="13"><TN RefId="2"><T>System.Threading.ApartmentState</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>Unknown</ToString><I32>2</I32></Obj><Obj N="RemoteStreamOptions" RefId="14"><TN RefId="3"><T>System.Management.Automation.RemoteStreamOptions</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>0</ToString><I32>0</I32></Obj><B N="AddToHistory">true</B><Obj N="HostInfo" RefId="15"><MS><B N="_isHostNull">true</B><B N="_isHostUINull">true</B><B N="_isHostRawUINull">true</B><B N="_useRunspaceHost">true</B></MS></Obj><B N="IsNested">false</B></MS></Obj>
"@

        $message = Create-PSRPMessage -Data $arguments -Type Create_pipeline -ObjectId ($ObjectId++) -MSG_RPID $runspacePoolId
            
        $commandId = (New-Guid).ToString().ToUpper()
        
        $Body = @"
        <rsp:CommandLine xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" CommandId="$commandId">
            <rsp:Command>Get-MobileDevice</rsp:Command>
            <rsp:Arguments>$message</rsp:Arguments>
        </rsp:CommandLine>
"@

        # Create the envelope for Get-FederationInfo -cmdlet
        $Envelope = Create-PSRPEnvelope -Shell_Id $Shell_Id -SessionId $SessionId -Body $Body -Action Command
        
        $mobileDevices = @()
        
        try
        {
            # Make the command call
            $response = Call-PSRP -Envelope $Envelope -Credentials $Credentials -Oauth $Oauth
 
            $get_output = $true

            # Get the output
            while($get_output)
            {
                try
                {
                    [xml]$response = Receive-PSRP -Credentials $Credentials -SessionId $SessionId -Shell_Id $Shell_Id -CommandId $commandId -Oauth $Oauth

                    # Loop through streams
                    foreach($message in $response.Envelope.Body.ReceiveResponse.Stream)
                    {
                        $parsed_message = Parse-PSRPMessage -Base64Value $message.'#text'
                        [xml]$xmlData = $parsed_message.Data

                        if($parsed_message.'Message type' -eq "Pipeline output")
                        {
                            # Loop thru the attributes
                            $attributes = [ordered]@{}
                            foreach($node in $xmlData.Obj.Props.ChildNodes)
                            {
                                $name = $node.N
                                $value = $node.InnerText
                                if($name -eq "ObjectClass")
                                {
                                    # Special attribute..
                                    $value=$node.LST.s[1]
                                }
                                $attributes[$name]=$value
                            }
                            $mobileDevices+=(New-Object psobject -Property $attributes)
                        }
                        elseif($parsed_message.'Message type' -eq "Pipeline state")
                        {
                            $errorRecord = (Select-Xml -Xml $xmlData -XPath "//*[@N='ErrorRecord']").Node.'#text'
                            if(![string]::IsNullOrEmpty($errorRecord))
                            {
                                # Something went wrong, probably not an admin user
                                Write-Error "Got an error! May be not an admin user?"
                                Write-Verbose "ERROR: $errorRecord"
                            }
                        }
                    }

                    # Loop thru the CommandStates
                    foreach($state in $response.Envelope.Body.ReceiveResponse.CommandState)
                    {
                        # Okay, we're done!
                        $exitCode = $state.ExitCode
                        if(![string]::IsNullOrEmpty($exitCode))
                        {
                            Write-Progress -Activity "Retrieving domains" -Completed
                            $get_output = $false
                        }
                    }
                }
                catch
                {
                    # Something wen't wrong so exit the loop
                    break
                }
            
            }
        }
        catch
        {
            # Do nothing
        }

        
        # Finally remove the shell
        Remove-PSRPShell -Credentials $Credentials -Shell_Id $Shell_Id -SessionId $SessionId -Oauth $Oauth
       
        
        return $MobileDevices

    }
        
}