internal/functions/Invoke-Command2.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
function Invoke-Command2 {
    <#
        .SYNOPSIS
            Wrapper function that calls Invoke-Command and gracefully handles credentials.
 
        .DESCRIPTION
            Wrapper function that calls Invoke-Command and gracefully handles credentials.
 
        .PARAMETER ComputerName
            Default: $env:COMPUTERNAME
            The computer to invoke the scriptblock on.
 
        .PARAMETER Credential
            The credentials to use.
            Can accept $null on older PowerShell versions, since it expects type object, not PSCredential
 
        .PARAMETER ScriptBlock
            The code to run on the targeted system
 
        .PARAMETER InputObject
            Object that could be used in the ScriptBlock as $Input.
            NOTE:
            The object will be de-serialized once passed through the remote pipeline.
            Some objects (like hashtables) do not support de-serialization.
 
        .PARAMETER Authentication
            Choose an authentication to use for the connection
 
        .PARAMETER ConfigurationName
            Name of the remote PSSessionConfiguration to use.
            Should be registered already using Register-PSSessionConfiguration or internal Register-RemoteSessionConfiguration.
 
        .PARAMETER ArgumentList
            Any arguments to pass to the scriptblock being run
 
        .PARAMETER Raw
            Passes through the raw return data, rather than prettifying stuff.
 
        .PARAMETER RequiredPSVersion
            Verifies that remote Powershell version is meeting specified requirements.
 
        .EXAMPLE
            PS C:\> Invoke-Command2 -ComputerName sql2014 -Credential $Credential -ScriptBlock { dir }
 
            Executes the scriptblock '{ dir }' on the computer sql2014 using the credentials stored in $Credential.
            If $Credential is null, no harm done.
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUsePSCredentialType", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")]
    param (
        [DbaInstanceParameter]$ComputerName = $env:COMPUTERNAME,
        [object]$Credential,
        [scriptblock]$ScriptBlock,
        [object[]]$ArgumentList,
        [parameter(ValueFromPipeline)]
        [object[]]$InputObject,
        [ValidateSet('Default', 'Basic', 'Negotiate', 'NegotiateWithImplicitCredential', 'Credssp', 'Digest', 'Kerberos')]
        [string]$Authentication = 'Default',
        [string]$ConfigurationName,
        [switch]$Raw,
        [version]$RequiredPSVersion
    )
    <# Note: Credential stays as an object type for legacy reasons. #>

    $InvokeCommandSplat = @{}
    if ($ArgumentList) {
        $InvokeCommandSplat["ArgumentList"] = $ArgumentList
    }
    if ($InputObject) {
        $InvokeCommandSplat["InputObject"] = $InputObject
    }
    if (-not $ComputerName.IsLocalHost) {
        $runspaceId = [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace.InstanceId
        # sessions with different Authentication should have different session names
        if ($ConfigurationName) {
            $sessionName = "dbatools_$($Authentication)_$($ConfigurationName)_$runspaceId"
        } else {
            $sessionName = "dbatools_$($Authentication)_$runspaceId"
        }

        # Retrieve a session from the session cache, if available (it's unique per runspace)
        $currentSession = [Sqlcollaborative.Dbatools.Connection.ConnectionHost]::PSSessionGet($runspaceId, $ComputerName.ComputerName) | Where-Object { $_.State -Match "Opened|Disconnected" -and $_.Name -eq $sessionName }
        if (-not $currentSession) {
            Write-Message -Level Debug "Creating new $Authentication session [$sessionName] for $($ComputerName.ComputerName)"
            $psSessionSplat = @{
                ComputerName   = $ComputerName.ComputerName
                Authentication = $Authentication
                Name           = $sessionName
                ErrorAction    = 'Stop'
            }
            if (Test-Windows -NoWarn) {
                $timeout = New-PSSessionOption -IdleTimeout (New-TimeSpan -Minutes 10).TotalMilliSeconds
                $psSessionSplat += @{ SessionOption = $timeout }
            }
            if ($Credential) {
                $psSessionSplat += @{ Credential = $Credential }
            }
            if ($ConfigurationName) {
                $psSessionSplat += @{ ConfigurationName = $ConfigurationName }
            }
            $currentSession = New-PSSession @psSessionSplat
            $InvokeCommandSplat["Session"] = $currentSession
        } else {
            Write-Message -Level Debug "Found an existing session $sessionName, reusing it"
            if ($currentSession.State -eq "Disconnected") {
                $null = $currentSession | Connect-PSSession -ErrorAction Stop
            }
            $InvokeCommandSplat["Session"] = $currentSession

            # Refresh the session registration if registered, to reset countdown until purge
            [Sqlcollaborative.Dbatools.Connection.ConnectionHost]::PSSessionSet($runspaceId, $ComputerName.ComputerName, $currentSession)
        }
    }
    if ($RequiredPSVersion) {
        $remoteVersion = Invoke-Command @InvokeCommandSplat -ScriptBlock { $PSVersionTable }
        if ($remoteVersion.PSVersion -and $remoteVersion.PSVersion -lt $RequiredPSVersion) {
            throw "Remote PS version $($remoteVersion.PSVersion) is less than defined requirement ($RequiredPSVersion)"
        }
    }

    $InvokeCommandSplat.ScriptBlock = $ScriptBlock
    if ($Raw) {
        Invoke-Command @InvokeCommandSplat
    } else {
        Invoke-Command @InvokeCommandSplat | Select-Object -Property * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName
    }

    if (-not $ComputerName.IsLocalhost) {
        # Tell the system to clean up if the session expires
        [Sqlcollaborative.Dbatools.Connection.ConnectionHost]::PSSessionSet($runspaceId, $ComputerName.ComputerName, $currentSession)

        if (-not (Get-DbatoolsConfigValue -FullName 'PSRemoting.Sessions.Enable' -Fallback $true)) {
            $currentSession | Remove-PSSession
        }
    }
}