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

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!).

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.

How to shutdown the computer after closing the powershell window?

I am new to powershell. I have a powershell script I've been using to backup my files. After it runs, I would like to shutdown the computer and close the powershell window. It seems I can do one or the other, but not both. So when I restart the computer, powershell complains that it was not closed properly.
How to shutdown the computer after closing the powershell window?
TIA
p.s. Contrary to popular belief, I have read the manual. However, as mentioned below, if I put EXIT before Stop-Computer, the script exits before executing Stop-Computer. If I put EXIT after Stop-Computer, powershell complains that the file was not closed properly on reboot. Either way, I lose. :(
PowerShell does provid and 'Exit', as noted in my comment. As for stopping, just put the 'Stop-Computer' cmdlet at the end of your script to shut down the computer.
Get-Help -Name Stop-Computer -examples
# Results
<#
NAME
Stop-Computer
SYNOPSIS
Stops (shuts down) local and remote computers.
----------- Example 1: Shut down the local computer -----------
Stop-Computer -ComputerName localhost
Example 2: Shut down two remote computers and the local computer
Stop-Computer -ComputerName "Server01", "Server02", "localhost"
`Stop-Computer` uses the ComputerName parameter to specify two remote computers and the local computer. Each computer is shut down.
-- Example 3: Shut down remote computers as a background job --
$j = Stop-Computer -ComputerName "Server01", "Server02" -AsJob
$results = $j | Receive-Job
$results
`Stop-Computer` uses the ComputerName parameter to specify two remote computers. The AsJob parameter runs the command as a background job. The job objects are stored in the `$j` variable.
The job objects in the `$j` variable are sent down the pipeline to `Receive-Job`, which gets the job results. The objects are stored in the `$results` variable. The `$results` variable displays the job information
in the PowerShell console.
Because AsJob creates the job on the local computer and automatically returns the results to the local computer, you can run `Receive-Job` as a local command.
------------ Example 4: Shut down a remote computer ------------
Stop-Computer -ComputerName "Server01" -Impersonation Anonymous -DcomAuthentication PacketIntegrity
`Stop-Computer` uses the ComputerName parameter to specify the remote computer. The Impersonation parameter specifies a customized impersonation and the DcomAuthentication parameter specifies authentication-level
settings.
---------- Example 5: Shut down computers in a domain ----------
$s = Get-Content -Path ./Domain01.txt
$c = Get-Credential -Credential Domain01\Admin01
Stop-Computer -ComputerName $s -Force -ThrottleLimit 10 -Credential $c
`Get-Content` uses the Path parameter to get a file in the current directory with the list of domain computers. The objects are stored in the `$s` variable.
`Get-Credential` uses the Credential parameter to specify the credentials of a domain administrator. The credentials are stored in the `$c` variable.
`Stop-Computer` shuts down the computers specified with the ComputerName parameter's list of computers in the `$s` variable. The Force parameter forces an immediate shutdown. The ThrottleLimit parameter limits the
command to 10 concurrent connections. The Credential parameter submits the credentials saved in the `$c` variable.
#>
Or use the Restart-Computer cmdlet, if that is your goal instead.
Update
Use two scripts, main and child.
# Start-Main.ps1
0..4 |
ForEach{
"Inside function... $PSItem"
Start-Sleep -Seconds 1
}
.\Start-Child
Exit
# Start-Child.ps1
'Preparing to shutdown in 10 seconds'
Start-Sleep -Seconds 10
Stop-Computer
or Using PS Jobs is another option as noted in my comment:
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/start-job?view=powershell-7.2

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.

How to get return value from remote ps script?

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
}

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 }