Running PS Scripts Against Multiple Computers
Description:
In this post I will show you a way to run a test script against multiple machines. I would have a look at Setting up PS Remoting, PS Remoting GPO for setting up the environment to ensure PS Remoting is Enabled.
To Resolve:
-
On your machine, navigate to
c:\scriptsso we can create four files, three of which you can use against multiple scripts. Make sure that NTFS permissions are strict to this directory as we will be using a password file to store your credentials so that scripts can run if you are away from your computer.-
Should look like:
-
C:\scripts\create-file.ps1=> copy the following and save:
1 2
New-Item -Itemtype Dir -Path C:\Scripts2 New-Item -Itemtype File -Path C:\Scripts2\Test.Txt
-
C:\scripts\computers.txt=> just choose two remote machines on your network and enter them one line a piece: -
This assumes domain environment where you have admin access to the remote machines. All machines must have also ran the command
Enable-PSRemoting -Forceat sometime prior. All machines must have Network Discovery enabled as well.
1 2
pc01 pc02
C:\scripts\aes.key=> To create, we need to open PS as admin and type:
1 2 3 4
$KeyFile = "C:\Scripts\aes.key" $Key = New-Object Byte[] 32 [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($Key) $Key | Out-File $KeyFile
C:\scripts\aespassword.txt=> To create, we need to open PS as admin and type:
1 2 3 4 5 6
$PasswordFile = "C:\Scripts\aespassword.txt" $KeyFile = "C:\Scripts\aes.key" $Key = Get-Content $KeyFile # NOTE: Replace #Password1 with your admin password $Password = "#Password1" | ConvertTo-SecureString -AsPlainText -Force $Password | ConvertFrom-SecureString -key $Key | Out-File $PasswordFile
-
-
Now that those four files are created, we will create a script that will run the powershell script (
c:\scripts\create-file.ps1) against the two remote machines (c:\scripts\computers.txt). So create a new file calledc:\scripts\test-remote.ps1. At the beginning of the script we will place a code block that pulls in our admin credentials (fromaespassword.txtandaes.key) so we can run as a scheduled task if needed. Contents ofc:\scripts\test-remote.ps1:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
$User = "domain.com\administrator" $PasswordFile = "C:\Scripts\aespassword.txt" $KeyFile = "C:\Scripts\aes.key" $Key = Get-Content $KeyFile $Creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, (Get-Content $PasswordFile | ConvertTo-SecureString -Key $Key) $Computers = Get-Content C:\scripts\computers.txt ForEach ($Computer in $Computers) { Try { Invoke-Command -FilePath "c:\scripts\create-file.ps1" -ComputerName $Computer -Credential $Creds } Catch { Add-Content c:\scripts\Unavailable-Computers.txt $Computer } }
-
Now in Windows file explorer, click File => Open Windows Powershell => Open Windows Powershell as administrator. Now just type:
.\test.remote.ps1 -
The script has now been ran against our two remote computers. Go ahead and check them out! Open file explorer on the remote computers and see if
c:\scripts2exists along with itstest.txtfile. This is all it takes to run scripts against multiple machines. You could modify this and set it up on a remote share, but this is just the basics and works just fine. -
If you want to run Powershell interactively against multiple machines, it’s even easier:
1
Invoke-Command -ComputerName $Computers -Scriptblock { hostname }
- I usually do this if I just need to figure out one-off’s but I prefer to use scripts because each of my scripts log everything to a log file that I can then query to see the results. This works perfect for anything longer than a one liner and has the benefit that you can setup a scheduled task with a script and then run another script like this one to query the remote log file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
$User = "domain.com\administrator" $PasswordFile = "C:\Scripts\aespassword.txt" $KeyFile = "C:\Scripts\aes.key" $Key = Get-Content $KeyFile $Creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, (Get-Content $PasswordFile | ConvertTo-SecureString -Key $Key) $Computers = Get-Content C:\scripts\computers.txt # Create a file locally to collect the results for each of the remote computers. New-Item -Itemtype file -path c:\scripts\results.txt # Get the log that is output by each script and pump to results. ForEach ($Computer in $Computers) { $file = "\\$computer\c`$\scripts\create-file.log" $a = $computer + "`n" + "##############################"; Add-Content -Path C:\scripts\results.txt -Value $a Add-Content -Path C:\scripts\results.txt -Value (Get-Content $file) } #Show the results $Results = Get-Content C:\scripts\results.txt Write-Output $Results
-
My create-file in the example above doesn’t write to a log file, please see my Template script to see what I mean about logging.
-
What this command will generate is a file on your
C:\scriptsdirectory calledresults.txt. Inside of it, it will have the contents of a logfile for each computer that you ran a prior script against. It breaks up each computer into it’s own line so it’s clear to see what log belongs to what computer.
So that is what I use in my environment. Scripts on the internet that I have seen usually try to incorporate a complicated Process block or try to include Credentials and stuff in their parameters which seems to work fine as well. For example:
-
Look for built in cmdlets that include the parameter
-ComputerName(Get-Command -ParameterName ComputerName) and try to use those in your script. -
Or try and use it in your own functions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Param ( [Alias("Cn", "__Server", "Pscomputername")] [Parameter( Position = 0, Valuefrompipeline = $True, Valuefrompipelinebypropertyname = $True)] [String[]]$Computername = $Env:Computername, [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Creds = [System.Management.Automation.PSCredential]::Empty )
-
Then somewhere in your script you would try:
1 2 3 4 5 6 7 8 9 10 11 12
ForEach ($Computer in $ComputerName) { # Using Credentials If ($PSBoundParameters['Creds']) { Invoke-Command -ComputerName $Computer -Scriptblock { Get-Content C:\scripts\test.log } -Credential $Creds } Else { Invoke-Command -ComputerName $Computer -Scriptblock { Get-Content C:\scripts\test.log } } }
- or something like that, check out this blog post for more things to try.
- I used to try this when I first started scripting but I couldn’t ever get it to consistently work like my method above, by all means, feel free to go that route as well.
-
Here is one way to capture verbose in remotely running scripts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
$VerbosePreference = "continue" Start-Transcript -path c:\temp\transcript.txt Write-Verbose "here 1" $job = Invoke-Command $RemoteComputer -ScriptBlock { $ErrorActionPreference = "Stop"; $VerbosePreference = "continue"; Write-Verbose "there 1"; Write-Verbose "there 2"; Write-Verbose "there 3"; } -AsJob Wait-Job $job | Out-Null $VerboseMessages = $job.ChildJobs[0].verbose.readall() ForEach ($oneMessage In $VerboseMessages) {Write-Verbose $oneMessage} Write-Verbose "here 2" Stop-Transcript
Comments