Run powershell commands in parallel - powershell

How can I run a PowerShell script in parallel on multiple computers?
$TempFolder = "C:\DOWNLOADED_APPLICATIONS"
if(!(test-path $TempFolder))
{New-Item -path $TempFolder -type directory}
Write-Host ""
Write-Host "Downloading .NET framework v4.0 installation package" -ForegroundColor Yellow;
$src = "\\onesoul\tools\DOTNET45\dotNetFx45_Full_x86_x64.exe"
$dest = "$TempFolder" + "\" + "dotNetFx45_Full_x86_x64.exe"
$wc = New-Object System.Net.WebClient
$wc.DownloadFile($src, $dest)
$args = " /q /norestart"
Write-Host "Installing .NET framework v4.0" -ForegroundColor Yellow;
Start-Process -FilePath $dest -ArgumentList $args -Wait
Write-Output "Dot net 4.5 installed"
This script works fine if I run it remotely on one computer at a time. How do I make it run in parallel?

To run scripts on multiple remote computers you use PowerShell remoting. This requires that every computer you connect to has to have PowerShell remoting enabled which you do with the command:
Enable-PSRemoting -Force
Then from your PC, running an elevated console, use the Invoke-Command to run a script against multiple computers e.g.:
Invoke-Command -Computer server1,server2,server3 -Auth CredSSP `
-FilePath c:\script.ps1 -ArgumentList scriptParameter1, scriptParameter2
I suggest you use the -Auth CredSSP to avoid a second hop issue with your credentials since you're accessing a network share from the remote computer. Also the FilePath parameter will take the path to a local script and copy its contents across the wire to each remote computer. That script should not rely on other scripts unless they exist on all the remote computers. Finally, if you use your credentials, you should admin privs on all the remote computers. If you don't, use the -Credential parameter to provides credentials for an account that does.

Related

MSI installation command works locally, but not over PowerShell Remoting

I am trying to use Invoke-Command to install an MSI on a remote computer. I am creating the session using credentials for a domain admin account, and have verified it connects correctly.
# DEBUG: Test that the connection is running as admin.
$IsAdmin = Invoke-Command -Session $Session -ScriptBlock {[bool] (net session 2>$null)}
Write-Verbose "Is running as admin: $IsAdmin"
# Now we need to run the MSI install file.
$TempInstallFile = Join-Path -Path $TempDir -ChildPath $InstallFile
$Script = "Start-Process $TempInstallFile -Wait -ArgumentList '/quiet COLLECTORAGENTLIST=`"$CollectorIP`:$CollectorPort`" PORTPOOLRANGES=`"$Ports`" MAXPORTRANGESPERSESSION=""2"" PORTNUMPERALLOC=`"200`" LOGLEVEL=`"3`" SSLMODE=`"2`"'"
Write-Verbose "Installation command: $Script"
Invoke-Command -Session $Session -ScriptBlock {$Script}
The verbose output:
VERBOSE: PSSession to RDPServer created
VERBOSE: Is running as admin: True
VERBOSE: Installation command: Start-Process C:\temp\TSAgent_Setup_5.0.0301.msi -Wait -ArgumentList '/quiet COLLECTORAGENTLIST="REMOVED:8002" PORTPOOLRANGES="1024-49151" MAXPORTRANGESPERSESSION="2" PORTNUMPERALLOC="200" LOGLEVEL="3" SSLMODE="2"'
VERBOSE: PSSession to RDPServer closed
Nothing happens though. The program is not installed on the remote computer and there are no errors. When I copy the "Installation Command" directly from the output, however, and run it in an admin PowerShell on the remote machine, it installs the program correctly. So there must be some issue with the PowerShell remote session, no? I am fairly new to this, what am I missing here?

New-PSDrive doesn't work when script is run from network share, but does work when run on the local machine

Context: IT staff may or may not log in with a local administrator account and run the following script. Due to the way Windows credential caching works on a local admin account, a simple copy and paste of the files without hardcoded credentials from a UNC path may not work occasionally.
My workaround for this issue was to just create a PSDrive for the session using a set of credentials that are reserved exclusively for scripting and domain joins.
IT staff access this script from the same server (fileserver05). The script attempts to make a temporary map of fileserver05. Is it just looping and confusing itself, or what?
Function Install {
Start-Process net -wait -ArgumentList "use X /delete" -ErrorAction SilentlyContinue
Remove-PSDrive X -Force -ErrorAction SilentlyContinue
Try {
$agentPass = Read-Host -AsSecureString -Prompt "Deployment operations supervisor password"
$agentUser = 'domain.com\agent'
$agentCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $agentUser,$agentPass
New-PSDrive -Name "X" -PSProvider "FileSystem" -Root "\\fileserver05\Fileserver05\IT\Programs\Windows\Yardi CHECKscan Client" -Credential $agentCred -ErrorAction Stop
Write-Host 'Deploying CHECKscan client to local machine...'
Copy-Item -Path "X:\install.msi" -Destination "${env:TEMP}" -Force
Start-Process msiexec -Wait -ArgumentList "/i `"${env:TEMP}\install.msi`" /qn /norestart"
Write-Host 'Deploying connection settings to current and default user profile...'
Copy-Item -Path "X:\yCheckSettings.xml" -Destination "${env:APPDATA}" -Force
Copy-Item -Path "X:\yCheckSettings.xml" -Destination "C:\Users\Default\AppData\Roaming" -Force
}
Catch {
Write-Host ""
Write-Host "An error occured during installation" -BackgroundColor Red -ForegroundColor White
Write-Host "Ensure you have the correct credentials and a connection to the corporate network"
Write-Host "Ensure that paths to the installation files have not changed on the fileserver"
Write-Host ""
Install
}
}
Install
Please also note that ExecutionPolicy is set to Unrestricted.
If a staff member runs the script from the server, it fails and is unable to map the drive, therefore making the rest of the script not work. If a staff member takes the script and drags it to the local desktop, then runs it, it succeeds.
How can I make it so it can be ran from the server itself?

Powershell/batch uninstall script works locally but not when using invoke-command

I have a script that installs an .exe with some arguments remotely to a list of servers that works fine. When I try to do almost the exact same thing but run the uninstall.exe that gets installed to C:\Program Files (x86)\ it won't work.
When I run the scripts on the server locally, it kicks off the uninstall. When I try to run the exact same script or command using the powershell invoke-command it won't work.
$serverlist = Get-Content -Path C:\NagiosInstall\test.txt
ForEach ($server in $serverlist) {
New-Item -Path "\\$server\C$\" -Name "NagiosInstall" -Force -ItemType "directory"
Copy C:\NagiosInstall\ncpa-2.1.6.exe \\$server\C$\NagiosInstall\ncpa-2.1.6.exe
Copy C:\NagiosInstall\install.bat \\$server\C$\NagiosInstall\install.bat
invoke-command -ComputerName $server -ScriptBlock {C:\NagiosInstall\install.bat}
Start-Sleep -s 15
invoke-Command -ComputerName $server -ScriptBlock {Remove-Item -LiteralPath "C:\NagiosInstall" -Force -Recurse}
}
The install .bat is just a simple command to silently install that ncpa-2.1.6.exe.
Above is my install script, that part all works fine.
invoke-command -ComputerName $server -ScriptBlock {Start-Process -FilePath "C:\Program Files (x86)\Nagios\NCPA\uninstall.exe" -ArgumentList "/S"}
Running the above command, nothing happens. No errors, nothing.
& "C:\Program Files (x86)\Nagios\NCPA\uninstall.exe" -ArgumentList "/S"
But running the above command in powershell that's running as admin locally on the server and it works just fine.
I've also tried the same approach to create and copy and run a batch file, very similar to the above "install" code. Same thing... nothing happens but if you run the batch locally on the server, it works just fine. I can post this code if anyone is interested.
I'm guessing it has to do with the invoke-command or the fact that it's in C:\Program Files (x86) which might make the syntax different, but I've tried many things and I'm out of ideas besides making an account and posting here.
The issue is that Invoke-Command runs non-interactively, and therefore cannot run as Administrator and respond to a UAC prompt.
The only workaround is to connect to the computer via a PSSession with credentials, and execute it that way:
$Cred = Get-Credential
$Session = New-PSSession -ComputerName $server -Credential $Cred
Invoke-Command -Session $Session -ScriptBlock {Start-Process -FilePath "C:\Program Files (x86)\Nagios\NCPA\uninstall.exe" -ArgumentList "/S"}
$Session | Exit-PSSession
Edit:
The reason that the installer works is that the UAC prompt for Windows Installs is different than anything else in Windows see: How to Silence the UAC Prompt for Per-Machine MSI Packages for Non-Admins or Using Windows Installer with UAC.
Essentially, Windows Installer (already running as admin and UAC approved), is what runs the install on your behalf, and it is Windows Installer and installer settings that determines if you need to see a UAC prompt or not. Hence, this is why the install works. Windows Installer determined that you did not need to see the UAC prompt, and the install proceeds.
Uninstalling is different. Since you are running uninstall.exe, the executable needs admin access and Windows will do UAC before the uninstall.exe even runs.

Unable to install program in self extracting cabinet using Invoke-Command

I'm writing a script to set up a test SharePoint server for trusted (AD FS) authentication on a stamped test environment that consists of a SharePoint server (server 2016) and a domain controller (server 2008R2). I'm writing the script to run on the SharePoint server and use a remote session to configure the DC because the DC only has PowerShell 2.0 which is missing some convenient functionality.
I have a specific segment of the script that runs a script block on the DC which downloads the AD FS 2.0 installer, a self extracting cabinet, and tries to install it. Every line of the block executes except for the actual installation. If I log onto the machine and run those same lines it works perfectly.
Invoke-Command -Session $domainControllerSession -ScriptBlock {
$installerUrl = "https://download.microsoft.com/download/F/3/D/F3D66A7E-C974-4A60-B7A5-382A61EB7BC6/RTW/W2K8R2/amd64/AdfsSetup.exe"
$filename = "$($PWD.Path)\AdfsSetup.exe"
$wc = New-Object System.Net.WebClient
$wc.DownloadFile($installerUrl, $filename)
Start-Process -FilePath $filename -ArgumentList "/quiet" -Wait
}
I tried manually extracting the contents (using /x:) and then executing the setup file but there was no change in result (Note: The files are extracted but the extractor process never exits, this doesn't seem pertinent to the problem). I also moved to the DC and created a session to localhost and got the same exact behavior.
PS C:\Users\Administrator> $session = New-PSSession -ComputerName Localhost
PS C:\Users\Administrator> Invoke-Command -Session $session -ScriptBlock {
>> $filename = "$($PWD.Path)\AdfsSetup.exe"
>> write-host $filename
>> Test-Path -Path $filename
>> Start-Process -FilePath $filename -ArgumentList "/quiet" -Wait
>> Test-Path -Path 'C:\Program Files\Active Directory Federation Services 2.0'
>> }
>>
C:\Users\Administrator\Documents\AdfsSetup.exe
True
False
PS C:\Users\Administrator>
Update 1
I ran the process with the /Logfile parameter and confirmed that the installation is failing due to an access denied error. I've also confirmed that, as expected, the remote session is running under the same administrator account that I am using to initiate the session. I am assuming that the missing ingredient here is that the remote session is not running in an elevated shell. However, I can't seem to get that working either.
Invoke-Command -Session $session -ScriptBlock {
Start-Process PowerShell -Verb RunAs -ArgumentList "& C:\Users\Administrator\Documents\AdfsSetup.exe /quiet /Logfile C:\Users\Administrator\Documents\AdfsSetup.log" -Wait -PassThru
}
The error is the same.

Relaunching PowerShell script as admin user

I have a quite a few computer systems which we need to deploy software. I've been using a simple method for detecting if a user is a local admin, then detecting if they have admin rights. If needed, the script relaunches with elevated privileges. If the user is not a local admin, the script relaunches using a different credentials (local admin). The script works great on systems which have a later version of PowerShell such as Windows 8 and Windows 10.
The problem is when the user is not an admin and the script is running on Windows 7. The script uses $PSScriptPath to relaunch the script. I don't think this works in earlier versions of PowerShell. So I tried setting $PSScriptRoot myself if the Major PowerShell version is < 3. The problem is then the script gets stuck in a loop of some sort where it just constantly opens and closes windows and then I have to kill it... If I don't define $PSScriptRoot I get the error
Cannot bind argument to parameter 'Path' because it is null
I assume this is because $PSScriptRoot isn't defined in PowerShell 2.0.
Here's an example of what I'm trying to do:
#Check if PowerShell version is greater than 2. If not, set $PSSriptRoot.
if ($PSVersionTable.PSVersion.Major -lt 3) {
$PSScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Definition
}
#Check if the user is a local admin. If they are, set $LocalAdmin to $True.
$LocalAdmin = $false
if ((net localgroup administrators) -match ([System.Environment]::UserDomainName + "\\" + [System.Environment]::Username)) {
$LocalAdmin = $true
}
if ($LocalAdmin) {
#Check if the local admin needs to run the script as administrator
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
$arguments = "& '" + $MyInvocation.MyCommand.Definition + "'"
Start-Process powershell -Verb runas -ArgumentList $arguments
break
}
} else {
#Not a local admin. Relaunch script as admin user.
Start-Process -Credential $credential (Join-Path $PSHome powershell.exe) -ArgumentList (#("-File",
(Join-Path $PSScriptRoot $MyInvocation.MyCommand)) + $args)
exit
}
Don't re-define automatic variables. Nothing good will come of it.
Besides, why do you want to anyway? The only thing you use $PSScriptRoot for is to reconstruct the script path you already have. Just assign that path to a variable and use that in your script.
$script = $MyInvocation.MyCommand.Definition
$ps = Join-Path $PSHome 'powershell.exe'
$isLocalAdmin = [bool]((net localgroup administrators) -match "$env:USERDOMAIN\\$env:USERNAME")
if ($isLocalAdmin) {
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]'Administrator')) {
Start-Process $ps -Verb runas -ArgumentList "& '$script'"
exit
}
} else {
Start-Process $ps -ArgumentList (#('-File', $script) + $args) -Credential $credential
exit
}