I'm running PowerShell commands on a remote machine by the use of Invoke-Command -ComputerName
. I'm trying to obtain the path of the temporary directory of the remote machine.
Depending on where I call [System.IO.Path]::GetTempPath()
it either outputs the expected remote directory C:\Users\…\AppData\Local\Temp
or my local temporary directory C:\temp
.
This command is not working as expected:
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Write-Output ([System.IO.Path]::GetTempPath())
}
# Outputs local directory 'C:\temp'
# Expected remote directory 'C:\Users\…\AppData\Local\Temp'
The problem can be reproduced with other commands than Write-Output
, e. g. Join-Path
.
Contrary, the following code samples all give the expected output of C:\Users\…\AppData\Local\Temp
.
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
[System.IO.Path]::GetTempPath()
}
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
$tmp = [System.IO.Path]::GetTempPath(); Write-Output $tmp
}
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Start-Sleep 1
Write-Output ([System.IO.Path]::GetTempPath())
}
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Write-Output ([System.IO.Path]::GetTempPath())
Start-Sleep 1
}
Obviously Start-Sleep
isn't a solution, but it seems to indicate some kind of timing problem.
Suspecting that the problem isn't limited to GetTempPath()
I tried another user-related .NET API, which also unexpectedly outputs my local folder instead of the remote one:
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Write-Output ([System.Environment]::GetFolderPath([Environment SpecialFolder]::MyDocuments))
}
How can I use [System.IO.Path]::GetTempPath()
and other .NET API in a PowerShell remote session in a predictable way?
CodePudding user response:
Santiago Squarzon has found the relevant bug report:
The issue equally affects
Enter-PSSession
.While a decision was made to fix the problem, that fix hasn't yet been made as of PowerShell 7.3.1 - and given that the legacy PowerShell edition, Windows PowerShell (versions up to v5.1, the latest and final version) will see security-critical fixes only, the fix will likely never be implemented there.
While the linked bug report talks about the behavior originally having been by (questionable) design, the fact that it only surfaces in very narrow circumstances (see below) implies that at the very least that original design intent's implementation was faulty.
The problem seems to be specific to a script block with the following characteristics:
- containing a single statement
- that is a cmdlet call (possibly with additional pipeline segments)
- whose arguments involve .NET method calls, which are then unexpectedly performed on the caller's side.
Workaround:
- Make sure that your remotely executing script block contains more than one statement.
- A simple way to add a no-op dummy statement is to use
$null
:
# This makes [System.IO.Path]::GetTempPath() *locally* report
# 'C:\temp\'
# *Remotely*, the *original* value should be in effect, even when targeting the
# same machine (given that the env. var. modification is process-local).
$env:TMP = 'C:\temp'
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Write-Output ([System.IO.Path]::GetTempPath()); $null # <- dummy statement.
}
Other workarounds are possible too, such as enclosing the cmdlet call in (...)
or inserting a dummy variable assignment
(Write-Output ($unused = [System.IO.Path]::GetTempPath())
)
Your Start-Sleep
workaround happened to work because by definition it too added another statement; but what that statement is doesn't matter, and there's no timing component to the bug.