execute a script block on a remote server - powershell

I have written a script which works fine on the local server. However I would like to run the script block on a remote server. Here's the script block that's that runs fine locally. Can I use Invoke-Command to embed the below script block and run it on a remote server?
$Folder = Read-Host "Enter the folder name"
$FilePath = "E:\$Folder\capture.bat"
$Session = New-PSSession -ComputerName "qtestwest01"
Invoke-Command -Session $Session -ScriptBlock {$pt = New-Object System.Diagnostics.ProcessStartInfo;}
Invoke-Command -Session $Session -ScriptBlock {$pt.FileName = $using:FilePath;}
Invoke-Command -Session $Session -ScriptBlock {$pt.UseShellExecute = $false;}
Invoke-Command -Session $Session -ScriptBlock {$pt.RedurectStandardInput = $true;}
Invoke-Command -Session $Session -ScriptBlock {$e = [System.Diagnostics.Process]::Start($pt);}
Invoke-Command -Session $Session -ScriptBlock {$e.StandardInput.WriteLie("`n")}

Yes, it's pretty straight forward:
$Session = New-PSSession -ComputerName "qtestwest01"
$SB =
{
$pt = New-Object System.Diagnostics.ProcessStartInfo;
$pt.FileName = "E:\testscripts\capture.bat";
$pt.UseShellExecute = $false;
$pt.RedirectStandardInput = $true;
$e = [System.Diagnostics.Process]::Start($pt);
$e.StandardInput.WriteLine("`n")
}
Invoke-Command -Session $Session -ScriptBlock $SB
An aside: You may want to look at Start-Process -PassThru. Though I'm not sure you can set UseShellExecute using that pattern. There are some details about that here , but I didn't give it a thorough reading.
Update
Responding to your implementation and the parameter question, repeatedly calling Invoke-Command is unnecessary. You're calling into the same session so it's functionally the same thing, but everything you need is available so you can run a single command. The $Using: modifier can be used in a prefabricated ScriptBlock so long as the script block is used with certain cmdlets, including and maybe primarily Invoke-Command.
A new example:
$FilePath = "C:\windows\System32\notepad.exe"
$Session = New-PSSession -ComputerName "Server1"
$SB =
{
$pt = New-Object System.Diagnostics.ProcessStartInfo;
$pt.FileName = $Using:FilePath;
$pt.UseShellExecute = $false;
$pt.RedirectStandardInput = $true;
$e = [System.Diagnostics.Process]::Start($pt);
$e.StandardInput.WriteLine("`n")
}
Invoke-Command -Session $Session -ScriptBlock $SB
A second method of passing parameters into a script block is to use the Invoke-Command -ArgumentList parameter:
Example:
$FilePath = "C:\windows\System32\notepad.exe"
$Session = New-PSSession -ComputerName "Server1"
$SB =
{
$pt = New-Object System.Diagnostics.ProcessStartInfo;
$pt.FileName = $args[0] ;
$pt.UseShellExecute = $false;
$pt.RedirectStandardInput = $true;
$e = [System.Diagnostics.Process]::Start($pt);
$e.StandardInput.WriteLine("`n")
}
Invoke-Command -Session $Session -ScriptBlock $SB -ArgumentList $FilePath
And, Either approach, $Using or $args[0] will work even if cite the script block inline with the command:
Example:
$FilePath = "C:\windows\System32\notepad.exe"
$Session = New-PSSession -ComputerName "Server1"
Invoke-Command -Session $Session -ArgumentList $FilePath -ScriptBlock {
$pt = New-Object System.Diagnostics.ProcessStartInfo;
$pt.FileName = $args[0] ;
$pt.UseShellExecute = $false;
$pt.RedirectStandardInput = $true;
$e = [System.Diagnostics.Process]::Start($pt);
$e.StandardInput.WriteLine("`n")
}
Notes:
-ComputerName argument name and $FilePath value were changed in these examples just so I could test in my environment.
The use of $FilePath instead of $Folder. So far as I can tell $pt.FileName property needs a the full path. This was either mis-typed or in error in your last sample. $FilePath because of the -FilePath parameter on Start-Process.

$folder = 'testscripts'
$Session = New-PSSession -ComputerName "qtestwest01"
Invoke-Command -Session $Session -ScriptBlock {$pt = New-Object System.Diagnostics.ProcessStartInfo;}
Invoke-Command -Session $Session -ScriptBlock {$pt.FileName = $using:folder;}
Invoke-Command -Session $Session -ScriptBlock {$pt.UseShellExecute = $false;}
Invoke-Command -Session $Session -ScriptBlock {$pt.RedurectStandardInput = $true;}
Invoke-Command -Session $Session -ScriptBlock {$e = [System.Diagnostics.Process]::Start($pt);}
Invoke-Command -Session $Session -ScriptBlock {$e.StandardInput.WriteLie("`n")}

Related

PowerShell cannot use New-PSSessions right after Invoke-CimMethod (The runspace state is not valid for this operation)

The two code below works independently, however, they cannot work in the same script. I really need help, there's got to be something incompatible.
The first part of my script uses Invoke-CimMethod to Enable-PSRemoting, and it works.
Variables
$hostname = 'PC1'
$Session = New-PSSession $hostname
$DestinationPath = "C:\windows\temp"
Part 1
$SessionArgs = #{
ComputerName = $hostname
Credential = $credential
SessionOption = New-CimSessionOption -Protocol Dcom
}
$MethodArgs = #{
ClassName = 'Win32_Process'
MethodName = 'Create'
CimSession = New-CimSession #SessionArgs
Arguments = #{
CommandLine = "powershell Start-Process powershell -ArgumentList 'Enable-PSRemoting -Force'"
}
}
Invoke-CimMethod #MethodArgs
The second part of my code works if the first part above is not present. It is to create a TEMP folder, and then copy an entire folder into TEMP.
Part 2
Invoke-Command -Session $Session -ScriptBlock { Param($Destination) New-Item -Path $Destination -ItemType Directory -ErrorAction SilentlyContinue} -ArgumentList $DestinationPath
Copy-Item -Path "\\shared\folder\foo\bar" -ToSession $Session -Destination "C:\windows\temp\" -recurse -force
Error
Copy-Item : The runspace state is not valid for this operation.
What's weird is I've inserted the Invoke-CimMethod to many other scripts that does similar things and it works fine, like for example
Example of it working
$env:hostname
$env:process
$SessionArgs = #{
ComputerName = $env:hostname
Credential = $credential
SessionOption = New-CimSessionOption -Protocol Dcom
}
$MethodArgs = #{
ClassName = 'Win32_Process'
MethodName = 'Create'
CimSession = New-CimSession #SessionArgs
Arguments = #{
CommandLine = "powershell Start-Process powershell -ArgumentList 'Enable-PSRemoting -Force'"
}
}
Invoke-CimMethod #MethodArgs
$session = New-PSSession $env:hostname
ipconfig
Invoke-Command -Session $session -ScriptBlock {param($process) Stop-Process -ProcessName $process -Force} -ArgumentList $env:process
$Session | Remove-PSSession
Please help! I've tried everything, I even tried Get-CimSession | Remove-CimSession but that didn't work. Why is it incompatible?
I was able to fix this issue by putting the variable
$Session = New-PSSession $hostname
Right before Invoke-Command because I think when I sent enable-pssession it resets the connection.

PowerShell - Why do I have to state the New-PSSession Variable again?

So I am running an Invoke-CimMethod to push an Enable-PSRemoting command to target computer. Then run an Invoke-Command using PS-Session as a parameter. The two scripts work separately, but if I run them together I keep getting this error:
Copy-Item : The runspace state is not valid for this operation.
I had to restate the $session variable like so in order for it to run. I have bolded and highlighted the line below. My question is why?
$env:hostname = 'PC1'
$Session = New-PSSession $env:hostname
$DestinationPath = "C:\windows\temp"
$SessionArgs = #{
ComputerName = $env:hostname
Credential = $credential
SessionOption = New-CimSessionOption -Protocol Dcom
}
$MethodArgs = #{
ClassName = 'Win32_Process'
MethodName = 'Create'
CimSession = New-CimSession #SessionArgs
Arguments = #{
CommandLine = "powershell Start-Process powershell -ArgumentList 'Enable-PSRemoting -Force'"
}
}
Invoke-CimMethod #MethodArgs
Invoke-Command -Session $Session -ScriptBlock { Param($Destination) New-Item -Path $Destination -ItemType Directory -ErrorAction SilentlyContinue} -ArgumentList $DestinationPath
Copy-Item -Path "\\shared\drive\foo\bar\" -Destination "C:\windows\temp\ZScaler" -Recurse -force
############Restated here#############
$Session = New-PSSession $env:hostname
############Restated here#############
Invoke-Command -Session $session -ScriptBlock {
$msbuild = "C:\Windows\Temp\Installer\Installer.msi"
$arguments = "/quiet"
Start-Process -FilePath $msbuild -ArgumentList $arguments -Wait -Verbose
}
$Session | Remove-PSSession

Local Variable to validate remote operation

I am trying to validate that a remote machine can connect to another machine on a specific port
Pseudo Code
$RemoteSession = New-PSSession -ComputerName MyRemoteVM
Invoke-Command -Session $RemoteSession -ScriptBlock {$RsTestResults = New-Object System.Net.Sockets.TcpClient -Argument 2ndRemoteVM , 2ndRemoteVMPort}
However, I can't seem to get the results of that test
I have tried adding another Invoke-Command like below, but it is not helping
$LocalResults = Invoke-Command -ScriptBlock {$RsTestResults}
any thoughts?
When you do:
Invoke-Command -Session $RemoteSession -ScriptBlock {
$RsTestResults = New-Object System.Net.Sockets.TcpClient -ArgumentList 2ndRemoteVM, 2ndRemoteVMPort
}
The variable $RsTestResults is being created on the remote host and its scope will be said host. If you want the results of System.Net.Sockets.TcpClient to be stored on your local host, you would need to store the results of Invoke-Command like below:
$RsTestResults = Invoke-Command -Session $RemoteSession -ScriptBlock {
New-Object System.Net.Sockets.TcpClient -ArgumentList 2ndRemoteVM, 2ndRemoteVMPort
}
Edit
To explain the error message you're getting:
PS > New-Object System.Net.Sockets.TcpClient -ArgumentList $null, $null
New-Object : Exception calling ".ctor" with "2" argument(s): "The requested address is not valid in its context xxx.xxx.xxx.xxx:0"
Which means, the variables for IPAddress and Port are never being passed to Invoke-Command.
You have 2 options to pass those variables to the cmdlet, one is with $using:variableName and the other is with -ArgumentList.
Assuming you have 2 local variables, for example:
$ipAddress = $csv.IPAddress
$port = $csv.Port
With $using:variableName
$RsTestResults = Invoke-Command -Session $RemoteSession -ScriptBlock {
New-Object System.Net.Sockets.TcpClient -ArgumentList $using:ipAddress, $using:port
}
With -ArgumentList
$RsTestResults = Invoke-Command -Session $RemoteSession -ScriptBlock {
param($ipAddress, $port)
New-Object System.Net.Sockets.TcpClient -ArgumentList $ipAddress, $port
} -ArgumentList $ipAddress, $port

Powershell: use invoke-command with custom function to execute at remote computer and display output in the host computer

I am struggling with invoke-Command to execute custom function remotely and display the output at the local host. I've read many posts regarding invoke-Command online but I haven't find a way to resolve this problem.
First the custom function is working. It's called get-openfiles. The code is provided below:
function get-openfiles{
param(
$computername=#($env:computername),
$verbose=$false)
$collection = #()
foreach ($computer in $computername){
$netfile = [ADSI]"WinNT://$computer/LanmanServer"
$netfile.Invoke("Resources") | foreach {
try{
$collection += New-Object PsObject -Property #{
Id = $_.GetType().InvokeMember("Name", ‘GetProperty’, $null, $_, $null)
itemPath = $_.GetType().InvokeMember("Path", ‘GetProperty’, $null, $_, $null)
UserName = $_.GetType().InvokeMember("User", ‘GetProperty’, $null, $_, $null)
LockCount = $_.GetType().InvokeMember("LockCount", ‘GetProperty’, $null, $_, $null)
Server = $computer
}
}
catch{
if ($verbose){write-warning $error[0]}
}
}
}
Return $collection
}
I have used following methods but none of them working. Some of example below will work for built-in function but my condition is that I have to use the custom function.
# NOT WORKING #1
$s = New-PSSession -ComputerName remote-STL
$CifServer = "fs-STL-01"
function get-openfiles{..}
invoke-command -Session $s -ScriptBlock ${function:get-openfiles} -ArgumentList $CifServer
# NOT WORKING #2
$s = New-PSSession -ComputerName remote-STL
$CifServer = "fs-STL-01"
invoke-command -Session $s -ScriptBlock {Import-Module C:\scripts\grp-functions.psm1}
invoke-command -Session $s -ScriptBlock {get-openfiles -computername $args[0]} -ArgumentList $CifServer
# Not working #3
$s = New-PSSession -ComputerName remote-STL
$CifServer = "fs-STL-01"
invoke-command -Session $s -ScriptBlock {Import-Module C:\scripts\grp-functions.psm1}
invoke-command -Session $s -ScriptBlock { param($CifServer) get-openfiles -computername $Cifserver } -ArgumentList $CifServer
# Not working #4
$CifServer = "fs-STL-01"
function get-openfiles{..}
invoke-command -ComputerName remote-STL -ScriptBlock ${function:get-openfiles} -ArgumentList $CifServe
# not working #5
$CifServer = "fs-STL-01"
invoke-command -ComputerName remote-STL -ScriptBlock {get-openfiles -computername $args[0]} -ArgumentList $CifServer
Thanks for look into it!
You should send entire function in your scriptblock.
invoke-command -ComputerName computername -ScriptBlock {function get-whatever {dir}; get-whatever } -Credential (get-credential youruser)
This example runs get-whatever function that is dir command only and is the proof of concept. Change it to your desired code.

Remote Registry using Enter-PSSession

I am trying to read strings in a remote registry. When I run the script I am working on, it connects to the workstation in the list, but it only reads the local computer when running, not the remote. any Ideas?
#create open dialog box
Function Get-FileName($initialDirectory)
{
[void] [Reflection.Assembly]::LoadWithPartialName( 'System.Windows.Forms' );
$d = New-Object Windows.Forms.OpenFileDialog;
$d.ShowHelp = $True;
$d.filter = "Comma Separated Value (*.csv)| *.csv";
$d.ShowDialog( ) | Out-Null;
$d.filename;
}
# Set Variables with arguments
$strFile = Get-FileName;
$strComputer = Get-Content $strFile;
$date = Get-Date -Format "MM-dd-yyyy";
$outputFile = "C:\PowerShell\Reports";
$cred = Get-Credential
foreach($computer in $strComputer)
{
Enter-PSSession $computer -Credential $cred
Set-Location HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability
$systemInfo = Get-Item -Name LastComputerName
Write-Host $systemInfo
}
foreach($computer in $strComputer)
{
Enter-PSSession $computer -Credential $cred
..
..
}
The above code won't work. Enter-PSSession is not for using in a script. Anything written after that in a script won't run.
Instead, use Invoke-Command and pass rest of the script block as a parameter value. For example,
foreach ($computer in $strComputer) {
Invoke-Command -ComputerName $computer -Credential $cred -ScriptBlock {
Set-Location HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability
$systemInfo = Get-Item -Name LastComputerName
Write-Host $systemInfo
}
}
As the comments already explained, Enter-PSSession is for interactive use. To read remote registry entries, there are several ways.
Use plain reg.exe, it works well enough. Like so,
foreach($computer in $strComputers) {
reg query \\$computer\hklm\software\Microsoft\Windows\CurrentVersion\Reliability /v LastComputerName
}
Use PSSessions. Create a session and Invoke-Command to read registry. Like so,
function GetRegistryValues {
param($rpath, $ivalue)
Set-Location $rpath
$systemInfo = (Get-ItemProperty .).$ivalue
Write-Host $systemInfo
}
$session = New-PSSession -ComputerName $computer
Invoke-Command -Session $session -Scriptblock ${function:GetRegistryValues} `
-argumentlist "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability",`
"LastComputerName"
Remove-PSSession $session
Use .Net classes, Microsoft.Win32.RegistryKey. Like so,
$sk = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $server)
$k = $sk.opensubkey("SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability", $false)
write-host $k.getvalue("LastComputerName")