How to get return value from remote ps script? - powershell

I am using invoke-expression to call a remote script on another server. I was wondering how I can get the status code from that remote script to tell whether the script completes successfully or fails.
In fact, I am not sure whether invoke-expression will work for this return value, if it doesn't , is there any other way to work it around?

Invoke-Expression is not recommended. PSScriptAnalyzer will tell you not to use this if you can avoid this.
The typical way of getting a value from a remote system I'll demonstrate below. The value must be serializable.
$session = New-PSSession # the rest of the command to establish the session
$value = Invoke-Command -session $session -ScriptBlock {
# Do work to get value in say variable $returnValue
return $returnValue
}

Related

[System.IO.Path]::GetTempPath() outputs local temp directory when called through Invoke-Command on a remote machine

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?
Santiago Squarzon has found the relevant bug report:
GitHub issue #14511
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.

PowerShell Get-AppVServerPackage behaves differently when executed in a remote session

Good Morning all,
i have a slight problem with the remote execution of the "Get-AppVServerPackage" PowerShell Applet.
We try to receive all the information available for all packages from a remote location.
When executing "Get-AppVServerPackage" on the AppV server, i get:
the information about the contained applications.
[1] https://i.stack.imgur.com/pIx4m.png
When i user Enter-PSSession from a PowerShell command line, i get
[2] https://i.stack.imgur.com/GvEtc.png
When i try to use it in a script supposed to run unattended, i get
[3] https://i.stack.imgur.com/m8Ddf.png
So i tried to get more information about "Microsoft.AppV.Server.AppVMgmtDataTypes.Application"
$s = New-PSSession -computerName myAppVServer -Authentication Kerberos
$test = Invoke-Command -Session $s -ScriptBlock { Get-AppVServerPackage }
$test.Applications[0] |gm
will give me a long list of Methods, and only one Property "Length".
Can anyone tell me how i can get the information about the application? It works with the
$test.Entitlements
will give me a result i can work with.
Thanks a lot!
Holger
Sometimes you get lucky,
replacing
{ Get-AppVServerPackage }
with
{ Get-AppVServerPackage | Select ID, Name, Description, PackageGuid, VersionGuid, Applications, Entitlements }
solved the problem.

PowerShell - Getting Return Value From Script Run on Remote Machine?

I'm just trying to get a script on a local machine run on a remote machine and have a result returned back to the local caller. What am I doing wrong? This is almost verbatim one of several examples I've seen, except they used -ScriptBlock {} to do it. Is this just not possible to achieve using the -FilePath option? I double-checked the local path. Both of the scripts are in the same folder on the same drive on the local machine.
This Caller gives me a blank result.
Local Caller:
Write-Output "Retrieving results from server call."
$returned=Invoke-Command -ComputerName $server -Credential $MyCredential -FilePath D:\scripts\runOnRemote.ps1
Write-Output $returned
The following produces 'DummyMessage' as expected
Alternate Local Caller:
$returned=Invoke-Command -ComputerName $server -Credential $MyCredential -ScriptBlock {
$toReturn = 'DummyMessage'
return $toReturn
}
But this doesn't.
Script trying to run on remote:
$returnMessage = 'DummyMessage'
return $returnMessage
Output:
Retrieving results from server call.
<nothing>
Vs.
Retrieving results from server call.
DummyMessage
The issue was resolved after returning from lunch. It was an ISE bug that just wasn't interpreting new code revisions after so many runs (hence my confusion!).

Powershell delete MSMQ remotely

I was wondering if it was possible to delete queues remotely via PowerShell? I have the following script:
cls
[Reflection.Assembly]::LoadWithPartialName("System.Messaging")
$computers = #("comp1","comp2","comp3");
foreach($computer in $computers) {
$messageQueues = [System.Messaging.MessageQueue]::GetPrivateQueuesByMachine($computer);
foreach ($queue in $messageQueues) {
$endpoint = [string]::Format("FormatName:DIRECT=OS:{0}\{1}", $computer, $queue.QueueName);
Write-Host $endpoint
[System.Messaging.MessageQueue]::Delete($endpoint);
}
}
This works fine, if I was running it on the machine whose queues I want to delete however when I run this remotely I get the error:
The specified format name does not support the requested operation. For example, a direct queue format name cannot be deleted.
Any ideas if this can be done?
EDIT
Oddly, I have figured I can remote onto the machine via PowerShell and execute a script block. However, I don't understand the difference between doing:
THIS:
$endpoint = [string]::Format("FormatName:DIRECT=OS:{0}\{1}", $computer, $queue.QueueName);
Invoke-Command -ComputerName $computer -ScriptBlock { [Reflection.Assembly]::LoadWithPartialName("System.Messaging"); [System.Messaging.MessageQueue]::Delete($endpoint) };
AND THIS:
Invoke-Command -ComputerName $computer -ScriptBlock { [Reflection.Assembly]::LoadWithPartialName("System.Messaging"); [System.Messaging.MessageQueue]::Delete("FormatName:DIRECT=OS:MY_SERVER\some.endpoint") };
The value of $endpoint is the same however, for some odd reason it doesn't like the variable approach though both values are identical. I tested this by setting $endpoint then calling delete. I get the error:
Exception calling "Delete" with "1" argument(s): "Invalid value for parameter path."
What I'm trying to say is if I hard code the value as part of the argument it works but assign it to a variable then invoke the method I get an error
For historic purposes if anyone else is experiencing this issue or is wondering how to delete queues remotely then please see below.
How do I delete private queues on a remote computer? It is possible to delete queues remotely. This can be achieved using the command Enable-PSRemoting -Force. Without this, you encounter the issue #JohnBreakWell indicated (see his link to MSDN).
The scope of variables when using Invoke-Command? The problem I found was the variables I declared were simply out of scope (script block was unable to see it). To rectify this, I simply did the following:
The important bit being the argument list and the use of param.
$computers = #("comp1","comp2");
foreach($computer in $computers) {
[Reflection.Assembly]::LoadWithPartialName("System.Messaging");
$messageQueues = [System.Messaging.MessageQueue]::GetPrivateQueuesByMachine($computer);
foreach ($queue in $messageQueues) {
$endpoint = [string]::Format("FormatName:DIRECT=OS:{0}\{1}", $computer, $queue.QueueName);
Enable-PSRemoting -Force
Invoke-Command -ComputerName $computer -ScriptBlock {
param ($computer, $endpoint)
[Reflection.Assembly]::LoadWithPartialName("System.Messaging");
[System.Messaging.MessageQueue]::Delete($endpoint)
}
} -ArgumentList $computer, $endpoint
}
You cannot delete a remote private queue.
You need to perform the operation locally to the queue.
From MQDeleteQueue:
Remarks
(2nd paragraph)
"Private queues registered on a remote computer ... cannot be deleted."
As Dr. Schizo mentioned, you'll need to execute
Enable-PSRemoting -Force
on the remote machine, but then, assuming you're using Server 2012 r2, it's as simple as:
Invoke-Command -ComputerName COMPUTERNAME { Get-MsmqQueue -Name QUEUENAME | Remove-MsmqQueue }

create a function with proper scope

I need to create a script to authenticate to a remote PC and access to PS console. It actually works, but I need to execute a number of commands when I need to terminate this session.
So I create a function STOP to be called to terminate the session, but it can't be used.
Is this a problem of scope (because it's inside an if statement)? If I manually create the same function and then I try to call it... It works!
# get connection parameters
$credential = Get-Credential -Credential xxx
$remote_PC = Read-Host "Insert the server name or IP (Q to abort): "
# connect
if (someKindOfCheck) {
Enter-PSSession -ComputerName $remote_PC -Credential $credential
# some commands
function STOP {
Exit-PSSession;
# some commands
}
}
I've come across a similar issue before, and I resolved it by creating a New-PSSession and assigning it to a varialbe, I then use the Invoke-Command with -Session option to execute the required commands.
Enter-PSSession is only for interactive use (once you've entered that session it only takes input from the console).
The only way I can think of that would make this work "automagically" would be to create a custom remoting session on the target machine that implements a proxy function for Exit-PSSession, and put your commands in that function.