Invoke-RemoteScript.ps1

function Invoke-RemoteScript {
    <#
        .SYNOPSIS
            Run scripts in Sitecore PowerShell Extensions via web service calls.

        .DESCRIPTION
            When using commands such as Write-Verbose, be sure the preference settings are configured properly.

            Change each of these to "Continue" in order to see the message appear in the console.

            Example values:

            ConfirmPreference High
            DebugPreference SilentlyContinue
            ErrorActionPreference Continue
            InformationPreference SilentlyContinue
            ProgressPreference Continue
            VerbosePreference SilentlyContinue
            WarningPreference Continue
            WhatIfPreference False
    
        .EXAMPLE
            The following example remotely executes a script in Sitecore using a reusable session.
    
            $session = New-ScriptSession -Username admin -Password b -ConnectionUri http://remotesitecore
            Invoke-RemoteScript -Session $session -ScriptBlock { Get-User -id admin }
            Stop-ScriptSession -Session $session
    
            Name Domain IsAdministrator IsAuthenticated
            ---- ------ --------------- ---------------
            sitecore\admin sitecore True False
    
        .EXAMPLE
            The following remotely executes a script in Sitecore with the $Using variable.

            $date = [datetime]::Now
            $script = {
                $Using:date
            }
    
            Invoke-RemoteScript -ConnectionUri "http://remotesitecore" -Username "admin" -Password "b" -ScriptBlock $script
            Stop-ScriptSession -Session $session
    
            6/25/2015 11:09:17 AM
                    
        .EXAMPLE
            The following example runs a script as a ScriptSession job on the server (using Start-ScriptSession internally).
            The arguments are passed to the server with the help of the $Using convention.
            The results are finally returned and the job is removed.
            
            $session = New-ScriptSession -Username admin -Password b -ConnectionUri http://remotesitecore
            $identity = "admin"
            $date = [datetime]::Now
            $jobId = Invoke-RemoteScript -Session $session -ScriptBlock {
                [Sitecore.Security.Accounts.User]$user = Get-User -Identity $using:identity
                $user.Name
                $using:date
            } -AsJob
            Start-Sleep -Seconds 2

            Invoke-RemoteScript -Session $session -ScriptBlock {
                $ss = Get-ScriptSession -Id $using:JobId
                $ss | Receive-ScriptSession

                if($ss.LastErrors) {
                    $ss.LastErrors
                }
            }
            Stop-ScriptSession -Session $session
        
        .EXAMPLE
            The following remotely executes a script in Sitecore with arguments.
            
            $script = {
                [Sitecore.Security.Accounts.User]$user = Get-User -Identity admin
                $user
                $params.date.ToString()
            }
    
            $args = @{
                "date" = [datetime]::Now
            }
    
            Invoke-RemoteScript -ConnectionUri "http://remotesitecore" -Username "admin" -Password "b" -ScriptBlock $script -ArgumentList $args
            Stop-ScriptSession -Session $session
    
            Name Domain IsAdministrator IsAuthenticated
            ---- ------ --------------- ---------------
            sitecore\admin sitecore True False
            6/25/2015 11:09:17 AM
    
        .LINK
            Wait-RemoteScriptSession

        .LINK
            New-ScriptSession

        .LINK
                Stop-ScriptSession -Session $session


    #>

    
    [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName="InProcess")]
    param(
        
        [Parameter(ParameterSetName='InProcess')]
        [Parameter(ParameterSetName='Session')]
        [Parameter(ParameterSetName='Uri')]
        [scriptblock]$ScriptBlock,

        [Parameter(ParameterSetName='Session')]
        [ValidateNotNull()]
        [pscustomobject]$Session,

        [Parameter(ParameterSetName='Uri')]
        [Uri[]]$ConnectionUri,

        [Parameter(ParameterSetName='Uri')]
        [string]$SessionId,

        [Parameter(ParameterSetName='Uri')]
        [string]$Username,

        [Parameter(ParameterSetName='Uri')]
        [string]$Password,

        [Parameter(ParameterSetName='Uri')]
        [System.Management.Automation.PSCredential]
        $Credential,
        
        [Parameter()]
        [Alias("ArgumentList")]
        [hashtable]$Arguments,

        [Parameter(ParameterSetName='Session')]
        [switch]$AsJob
    )

    if($PSCmdlet.MyInvocation.BoundParameters["WhatIf"].IsPresent) {
        $functionScriptBlock = {
            $WhatIfPreference = $true
        }
        $ScriptBlock = [scriptblock]::Create($functionScriptBlock.ToString() + $ScriptBlock.ToString());
    }
    $hasRedirectedMessages = $false
    if($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -or $PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent) {
        $hasRedirectedMessages = $true
        $functionScriptBlock = {
            if($PSVersionTable.PSVersion.Major -ge 5) {
                function Write-Information {
                    param([string]$Message)
                    $InformationPreference = "Continue"
                    Microsoft.PowerShell.Utility\Write-Information -Message $Message 6>&1
                }
            }
            function Write-Debug {
                param([string]$Message)
                $DebugPreference = "Continue"
                Microsoft.PowerShell.Utility\Write-Debug -Message $Message 5>&1
            }
            function Write-Verbose {
                param([string]$Message)
                $VerbosePreference = "Continue"
                Microsoft.PowerShell.Utility\Write-Verbose -Message $Message 4>&1
            }
            function Write-Warning {
                param([string]$Message)
                $WarningPreference = "Continue"
                Microsoft.PowerShell.Utility\Write-Warning -Message $Message 3>&1
            }
            function Write-Error {
                param([string]$Message)
                $WarningPreference = "Continue"
                Microsoft.PowerShell.Utility\Write-Error -Message $Message 2>&1
            }
        }
        $ScriptBlock = [scriptblock]::Create($functionScriptBlock.ToString() + $ScriptBlock.ToString());
    }

    if($AsJob.IsPresent) {
        $nestedScript = $ScriptBlock.ToString()
        $ScriptBlock = [scriptblock]::Create("Start-ScriptSession -ScriptBlock { $($nestedScript) } -ArgumentList `$params | Select-Object -ExpandProperty ID")
    }

    $usingVariables = @(Get-UsingVariables -ScriptBlock $scriptBlock | 
        Group-Object -Property SubExpression | 
        ForEach {
        $_.Group | Select -First 1
    })
    
    $invokeWithArguments = $false        
    if ($usingVariables.count -gt 0) {
        $usingVar = $usingVariables | Group-Object -Property SubExpression | ForEach {$_.Group | Select -First 1}  
        Write-Debug "CommandOrigin: $($MyInvocation.CommandOrigin)"      
        $usingVariableValues = Get-UsingVariableValues -UsingVar $usingVar
        $invokeWithArguments = $true
    }
  
    if ($invokeWithArguments) {
        if(!$Arguments) { $Arguments = @{} }

        $paramsPrefix = "`$params."
        if($AsJob.IsPresent) {
            $paramsPrefix = "$"
        }
        $command = $ScriptBlock.ToString()
        foreach($usingVarValue in $usingVariableValues) {
            $Arguments[($usingVarValue.NewName.TrimStart('$'))] = $usingVarValue.Value
            $command = $command.Replace($usingVarValue.Name, "$($paramsPrefix)$($usingVarValue.NewName.TrimStart('$'))")
        }

        $newScriptBlock = $command
    } else {
        $newScriptBlock = $scriptBlock.ToString()
    }

    if($Arguments) {
        $parameters = ConvertTo-CliXml -InputObject $Arguments
    }

    if($PSCmdlet.ParameterSetName -eq "InProcess") {
        # TODO: This will likely fail for params.
        [scriptblock]::Create($newScriptBlock).Invoke()
    } else {
        if($PSCmdlet.ParameterSetName -eq "Session") {
            $Username = $Session.Username
            $Password = $Session.Password
            $SessionId = $Session.SessionId
            $Credential = $Session.Credential
            $Connection = $Session.Connection
        } else {
            $Connection = $ConnectionUri | ForEach-Object { [PSCustomObject]@{ Uri = [Uri]$_; Proxy = $null } }
        }
        
        foreach($singleConnection in $Connection) {
            if($singleConnection.Uri.AbsoluteUri -notmatch ".*\.asmx(\?wsdl)?") {
                $singleConnection.Uri = [Uri]"$($singleConnection.Uri.AbsoluteUri.TrimEnd('/'))/sitecore%20modules/PowerShell/Services/RemoteAutomation.asmx?wsdl"
            }
    
            if(!$singleConnection.Proxy) {
                $proxyProps = @{
                    Uri = $singleConnection.Uri
                }
    
                if($Credential) {
                    $proxyProps["Credential"] = $Credential
                }
    
                $singleConnection.Proxy = New-WebServiceProxy @proxyProps
                if($Credential) {
                    $singleConnection.Proxy.Credentials = $Credential
                }
            }
            if(-not $singleConnection.Proxy) { return $null }

            $response = $singleConnection.Proxy.ExecuteScriptBlock2($Username, $Password, $newScriptBlock, $parameters, $SessionId)
            if($response) {
                if($hasRedirectedMessages) {
                    foreach($record in ConvertFrom-CliXml -InputObject $response) {
                        if($record -is [PSObject] -and $record.PSObject.TypeNames -contains "Deserialized.System.Management.Automation.VerboseRecord") {
                            Write-Verbose $record.ToString()
                        } elseif($record -is [PSObject] -and $record.PSObject.TypeNames -contains "Deserialized.System.Management.Automation.InformationRecord") {
                            Write-Information $record.ToString()
                        } elseif($record -is [PSObject] -and $record.PSObject.TypeNames -contains "Deserialized.System.Management.Automation.DebugRecord") {
                            Write-Debug $record.ToString()
                        } elseif($record -is [PSObject] -and $record.PSObject.TypeNames -contains "Deserialized.System.Management.Automation.WarningRecord") {
                            Write-Warning $record.ToString()
                        } elseif($record -is [PSObject] -and $record.PSObject.TypeNames -contains "Deserialized.System.Management.Automation.ErrorRecord") {
                            Write-Error $record.ToString()
                        } else {
                            $record
                        }
                    }
                } else {
                    ConvertFrom-CliXml -InputObject $response
                }
            } elseif ($response -eq "login failed") {
                Write-Verbose "Login with the specified account failed."
                break            
            } else {
                Write-Verbose "No response returned by the service. If results were expected confirm that the service is enabled and the account has access."
            }
        }
    }
}