Write-Host is doubled after running as remote command - windows

I'm trying to run remote this code:
$servers = #("myserver.local")
Invoke-Command -ScriptBlock {
#############
Write-Host "Test1:" "xxx";
hostname
Write-Host "Test2:" "yyy";
#############
} -ComputerName $servers
And getting this output:
Test1: xxx
Test1: xxx
myserver
Test2: yyy
Test2: yyy
Expected output:
Test1: xxx
myserver
Test2: yyy
As you can see, Write-Host output is doubled, but hostname is okay.
Why?

It is not a problem. Your output is doubled because you are executing it in two of the servers. If you increase it , then the output will also increase.
The problem here is that the output is not coming as per each host respectively means there is a format/order issue.
Below is the sample as per the comment you made. I have taken one paramater to pass it inside the scriptblock of the remote system and captured the output in to the running PS session.
$hostname= 'RemoteComputer' #Input parameter
$results= Invoke-Command -ScriptBlock {param($hostname)Write-Host "Test1:" "xxx"; #taken the same input inside the script block using param
$out=hostname #Stored the result in the $out variable
Write-Host "Test2:" "yyy";
$out #printed the result
} -ComputerName $servers -ArgumentList $hostname #Passed the parameter in the scriptblock argument so that it can go inside as param
$results
I have explained the lines with comments on each line for easy understanding.

I don't know if you're still having this issue but I encountered same problem and the problem seems to be that one output comes from Write-Host and other from Invoke-Command as result of the command.
One of these two needs to be suppressed, it doesn't matter which one, following example code from your question suppresses output from Invoke-Command
$servers = #("myserver.local")
Invoke-Command -ScriptBlock {
#############
Write-Host "Test1:" "xxx";
hostname
Write-Host "Test2:" "yyy";
#############
} -ComputerName $servers 6>$null
Note the 6>$null given to Invoke-Command makes duplicate information stream to null, and only one is printed:
Test1: xxx
myserver
Test2: yyy

Related

Remote powershell on server says argument for parameter is empty string

I have a powershell script on machine A that uses PSSession to invoke a command on Machine B. Machine B has a powershell script which accepts 4 parameters. When I call this script with the 4 arguments as variables (which they MUST be), they are passed as empty strings/null. When I pass them as strings (For example -Argument1 "Hello"), that string will be passed as "Hello" and not as NULL/empty string.
Can anyone tell me why these are not passed correctly and how to fix it?
The powershell version on the client is 5.1.17134.112. The remote machine uses 5.1.14393.2248. These versions have been obtained by running $PSVersionTable.PSVersion.
The client is using Windows 10 Pro 10.0.17134. The server is using Windows 2016 Datacenter 10.0.14393 and is run as a VM on Azure.
I have tried using Script.ps1 -Argument1 $ClientArgument1 -Argument2 $ClientArgument2 ... to pass variables AND to use ArgumentList to pass the values comma separated to the script but both these attempts resulted in things not being printed.
I have noticed that when I use -Argument1 "Hello" -Argument2 $ClientArgument2 -Argument3 $ClientArgument3 -Argument4 $ClientArgument4, the Hello DOES get printed.
Code
Client that connects to the remote machine
$ErrorActionPreference = "Stop"
#Create credentials to log in
$URL = 'https://url.to.server:5986'
$Username = "username"
$pass = ConvertTo-SecureString -AsPlainText 'password' -Force
$SecureString = $pass
$MySecureCreds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username,$SecureString
$ClientArgument1 = "Argument 1"
$ClientArgument2 = "Argument 2"
$ClientArgument3 = "Argument 3"
$ClientArgument4 = "Argument 4"
#Create the remote PS session
$sessionOption = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
$session = New-PSSession -ConnectionUri $URL -Credential $MySecureCreds -SessionOption $sessionOption
#Call the remote script and pass variables
Invoke-Command -Session $session -Command {C:\Path\To\Script\On\Remote\Machine\Script.ps1 -Argument1 $ClientArgument1 -Argument2 $ClientArgument2 -Argument3 $ClientArgument3 -Argument4 $ClientArgument4}
#Note: Command is used because Command allows me to execute a script that is located on disk of the remote machine
The called script of the remote machine
param(
[String]$Argument1,
[String]$Argument2,
[String]$Argument3,
[String]$Argument4
)
Write-Host 'Results of the 4 parameters passed into this script:'
Write-Host $Argument1
Write-Host $Argument2
Write-Host $Argument3
Write-Host $Argument4
Write-Host "The results have been printed"
Expected and actual results
Expected results:
Results of the 4 parameters passed into this script:
Argument 1
Argument 2
Argument 3
Argument 4
The results have been printed
Actual results
Results of the 4 parameters passed into this script:
The results have been printed
Thank you very much for your time!
Since what inside the scriptblock in a different scope as the rest of your script, the $ClientArgument variables are undefined inside the scriptblock. The easiest solution if you are using PowerShell 3 or newer is to use the $using: scope. Otherwise an argumentlist for the Invoke-Command would be required.
Invoke-Command -Session $session -Command {C:\Path\To\Script\On\Remote\Machine\Script.ps1 -Argument1 $using:ClientArgument1 -Argument2 $using:ClientArgument2 -Argument3 $using:ClientArgument3 -Argument4 $using:ClientArgument4}
Try to execute it this way:
Invoke-Command -Session $session -Command {
C:\Path\To\Script\On\Remote\Machine\Script.ps1 `
-Argument1 $args[0] -Argument2 $args[1] `
-Argument3 $args[2] -Argument4 $args[3]
} -ArgumentList $ClientArgument1,$ClientArgument2,$ClientArgument3,$ClientArgument4

How to run PowerShell script in a different process and pass arguments to it?

Let's say I have a script:
write-host "Message.Status: Test Message Status";
I managed to run it in a separate process by doing:
powershell.exe -Command
{ write-host "Message.Status: Test Message Status"; }
The problem is I want to pass parameters to the script so that I can achieve something like this:
write-host "I am in main process"
powershell.exe -Command -ArgumentList "I","am","here"
{
write-host "I am in another process"
write-host "Message.Status: $($one) $($two) $($three)";
}
However -ArgumentList doesn't work here
I get:
powershell.exe : -ArgumentList : The term '-ArgumentList' is not recognized as the name of a cmdlet, function, script file, or operable
I need to run some part of PowerShell script file in a different process and I cannot use another file due to the fact that PowerShell script is uploaded to external system.
The -Command parameter is expecting a scriptblock in which you can define your parameters using a Param() block. Then use the -args parameter to pass in the arguments. Your only mistake was to put the -args after -command before you defined the scriptblock.
So this is how it works:
write-host "I am in main process $($pid)"
powershell.exe -Command {
Param(
$one,
$two,
$three
)
write-host "I am in process $($pid)"
write-host "Message.Status: $($one) $($two) $($three)";
} -args "I", "am", "here" | Out-Null
Output:
I am in main process 17900
I am in process 10284
Message.Status: I am here
You can use the -File parameter and follow it by the path to script. Any unnamed arguments which follows will be passed as script parameters. Something like below should do
powershell -File "C:\ScriptFolder\ScriptwithParameters.ps1" "ParameterOneValu" "valuetwo"
Ok so if you need another process entirely but not another file then your best bet is probably .NET runspaces. Basically wrap your code in a scriptblock
$SB = {
*Your Code*
}
Then set up a runspace like below, making sure to use the "UseNewThread" as the thread option. Note that $arg is whatever your argument to be passed to the script is
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "UseNewThread"
$newRunspace.Open()
$psCmd = [PowerShell]::Create().AddScript($SB).AddArgument($arg)
$psCmd.Runspace = $newRunspace
$data = $psCmd.BeginInvoke()
You'll likely need to tweak this if you need to get any data back from the runspace once it is complete but there are a few ways to do that(leave a comment if you need assistance). If you need synchronous execution rather than async then change .BeginInvoke() to .Invoke()
So should get you started, But it will require a few moving parts.
First we define a new function:
function Run-InNewProcess{
param([String] $code)
$code = "function Run{ $code }; Run $args"
$encoded = [Convert]::ToBase64String( [Text.Encoding]::Unicode.GetBytes($code))
start-process PowerShell.exe -argumentlist '-noExit','-encodedCommand',$encoded
}
This function will be what starts the new process. It uses the start-process cmdlet, The -Argumentlist is our arguments applied to the powershell.exe You can remove -noExit to make the new process close on completion or add other powershell flags, and flags on Start-Process to get the windows and behaviours tweaked to your requirements.
Next we define our script block:
$script = {
[CmdletBinding()]
Param (
[Parameter(Position=0)]
[string]$Arg1,
[Parameter(Position=1)]
[string]$Arg2)
write-host "I am in another process"
write-host "Message.Status: $($Arg1) $($Arg2)";
}
Here we define some parameters in the opening part of the block, They have a position and name, so for example any argument in position 0 will be in the variable $arg1 The rest of the code in the block is all executed in the new process.
Now we have defined the script block and the function to run it in a new process, All we have to do is call it:
Run-InNewProcess $script -Arg1 '"WHAT WHAT"' -Arg2 '"In the But"'
Copy past this code all in to your ISE and you will see it in action.
Start-Job will create a process for its scriptblock, and it's straightforward to pass arguments to it.
Write-Host "Process count before starting job: $((Get-Process |? { $_.ProcessName -imatch "powershell" }).Count)"
$job = Start-Job `
-ArgumentList "My argument!" `
-ScriptBlock {
param($arg)
Start-Sleep -Seconds 5;
Write-Host "All Done! Argument: $arg"
}
while ($job.State -ne "Completed")
{
Write-Host "Process count during job: $((Get-Process |? { $_.ProcessName -imatch "powershell" }).Count)"
Start-Sleep -Seconds 1
}
Receive-Job $job -AutoRemoveJob -Wait
Write-Host "Process count after job: $((Get-Process |? { $_.ProcessName -imatch "powershell" }).Count)"

Running Invoke-Command on remote machine

I am trying to run any command I want on a remote machine. Example: gpupdate /force or copy file1 to file2 etc... so I have this code:
$ComputerName = Read-Host "Enter a remote computer name"
$RemoteCommand = Read-Host "Enter a remote command to run: Example gpupdate /force"
$s = New-PSSession -ComputerName $ComputerName
Invoke-Command -Session $s -ScriptBlock {$RemoteCommand}
Invoke-Command -Session $s -ScriptBlock { $? }
It runs without error and in fact it returns TRUE. But the file I have in c:\temp never gets copied to c:\temp\tmp
why not?
The problem is that you are passing a string variable to Invoke-Command in the scriptblock, which just evaluates to the content of the string. You are not passing it a scriptblock with actual commands.
To illustrate the difference see this code:
# Output is just the content of the string
$commandString = "Get-Service spooler"
Invoke-Command {$commandString}
# Output is the result of the commandlet
$scriptBlock = {Get-Service spooler}
Invoke-Command -ScriptBlock $scriptBlock
To get the result you want you can use the [scritpblock] accelerator, like this:
# Output is the result of the commandlet invocation defined in the string
$commandString = "Get-Service spooler"
$scriptBlock = [scriptblock]::Create($commandString)
Invoke-Command -ScriptBlock $scriptBlock
Try running the script like this:
Invoke-Command -Session $s -ScriptBlock { powershell.exe -Command "$RemoteCommand"}
If you get problems with escaping characters, there is also the -encodedCommand switch. From the powershell help:
# To use the -EncodedCommand parameter:
$command = 'dir "c:\program files" '
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)
powershell.exe -encodedCommand $encodedCommand

Powershells write-verbose creates no outputvariable (-ov)

Not critical but gave me a headache...
I wanted the output of write-verbose into a variable for documentation/debugging.
Its nice, powershel has an own parameter for the output of commands (see help about_commonparameters).
But whats not stated in the help is: what write-* output goes to what variable
so i tried and tried and found out:
write-warning writes just to -warningVariable
write-error writes just to -errorVariable
write-output writes just to -outVariable
BUT where goes the write-verbose output?
The help says
This cmdlet supports the common parameters: -Verbose, -Debug, -ErrorAction, -ErrorVariable, -OutBuffer, and -OutVariable.
For Example:
write-verbose "test" -verbose -outvariable $a
Nothings in $a
(same for write-warning "test" -ev $b... nothing)
any ideas? thanks in advance
Write-Verbose has no "output" to write to an OutVariable. It does write things to the verbose stream, though.
OutVariable contains all objects that were output to the output stream.
One option:
$VerbosePreference = 'continue'
Write-Verbose ($a = 'foo')
$a
VERBOSE: foo
foo
If you want just the output of Write-Verbose in a variable you could use redirection:
$var = Write-Verbose 'something' 4>&1
That will merge the verbose stream with the success output stream, which can be captured in a variable.
This won't work if you need regular and verbose output in separate variables, though. As far as I'm aware you must redirect verbose output to a file and read the file into a variable for that.
PS C:\> function Foo { Write-Verbose 'foo'; Write-Output 'bar' }
PS C:\> $VerbosePreference = 'Continue'
PS C:\> Foo
VERBOSE: foo
bar
PS C:\> $o = Foo 4>'C:\temp\verbose.txt'
PS C:\> $v = Get-Content 'C:\temp\verbose.txt'
PS C:\> $o
bar
PS C:\> $v
foo
Same goes for warnings, only that warnings go to stream number 3.
PS C:\> function Bar { Write-Warning 'foo'; Write-Output 'bar' }
PS C:\> $WarningPreference = 'Continue'
PS C:\> Bar
WARNING: foo
bar
PS C:\> $o = Bar 3>'C:\temp\warning.txt'
PS C:\> $w = Get-Content 'C:\temp\warning.txt'
PS C:\> $o
bar
PS C:\> $w
foo
Redirection of the warning, verbose, and debug streams was introduced with PowerShell version 3.

Passing variable to several scriptblocks used in remote sessions Powershell

I have a script that runs from a domain server and 'does work' on a domain server.
The work is divided up into jobs that are in scriptblocks that are called with invoke-command using a stored PSsession.
Results are logged on the domain server in a log file that includes a datetime stamp in the logfile name.
Now, I need to add another log that needs to reside on the remote on which the work is done. The log name format also needs to include a date and time stamp.
My problem is passing the name of the log to each of the jobs so that they write to the same file. I've played around with -ArgumentList, #args, and $args which I can get to run without errors but do nothing so I am not passing the logfile name correctly.
Below is a super simplified version of how I've structured my script.
Is it a mistake to nest the Start-Job in another script block? How would I pass my unique log file name to a number of these scriptblocks to capture success/failure and specific points?
#log file names, ps session and other variables declared here
$DoDomainWorkScriptBlock = {
Try {
start-job -name DoDomainWorkjob -scriptblock{
$command = "C:\Program Files\someprogram\someprogram.exe"
& $command -f someargs
If ($? -ne "True") {Throw 'Do work failed’}
" Do non-domain work job completed. "
}
} Catch {$Error[0] | Out-File $ErrorLog -Append}
}
#other jobs nested in other scriptblocks like the one above here
Invoke-Command -session $RemoteSession -scriptblock $DoDomainWorkScriptblock | Out-File $DomainProgressLog -Append
Invoke-Command -session $RemoteSession -command{Wait-Job -name DoDomainWorkjob } | Out-File $DomainProgressLog -Append
Invoke-Command -session $RemoteSession -command{Receive-Job -Name DoDomainWorkjob } | Out-File $DomainProgressLog –Append
#invoke start, wait, and receive job commands for the other jobs
You can pass arguments to script blocks like this:
$code = {
param( $foo )
Write-Host $foo
}
$bar = "bar"
Invoke-Command -ScriptBlock $code -ArgumentList $bar

Resources