Executing a .exe using powershell to multiple remote server - powershell

I am trying to upgrade all endpoint with latest version of SCCM agent using installing them from a server through powershell script. All the commands are running except Invoke-command.
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted -Force
$Comps = Get-Content C:\sccm_client_latest\SCCMAgentinstall_script\Computers.txt
Foreach ($comp in $Comps)
{
$b = Test-Connection $comp -Quiet -Count 2
if ($b -eq 'true')
{
Copy-Item C:\sccm_client_latest\SCCM_v2111_Client -Destination \\$comp\c$\temp -Recurse -force
timeout /t 120
New-PSSession -ComputerName $comp
invoke-command -ComputerName $comp -ScriptBlock {
& 'C:\Temp\SCCM_v2111_Client\ccmsetup.exe' /mp:server.example.com SMSSITECODE=P01 SMSMP=server.example.com DNSSUFFIX=example.com
}
Start-Sleep -Seconds 120
}
}

Related

Invoke-Command only running on the last node in an array

I have a total of 3 servers with 2 folders on each server that have an .exe I need to execute from their respective locations. Below is my current code. What is happening is the output is showing the code gets run but when I log into those servers, the .exe is not running. However, every time the last node works perfectly fine. I'm lost.
Here is my current code:
Foreach ($server in $NodeArray) {
# $NodeArray consists of server1, server2 and server3
$Session = New-PSSession -ComputerName $server -Authentication Negotiate -Credential $HPCSlaveCreds -ErrorAction Stop
$Scriptblock = {
param ($MasterNode, $ControlFile)
$FolderName = (Get-ChildItem 'C:\HPC' | select -last 1).name
$Path = (Get-ChildItem "C:\HPC\$FolderName" -Recurse -Filter "Agent*").Name
foreach ($agent in $Path) {
$PestArguments = "$ControlFile /H ${MasterNode}:4004"
Write-Host "Starting Beopest on $env:COMPUTERNAME - $PestArguments"
Start-Process -FilePath "pestpp-ies.exe" -WorkingDirectory "C:\HPC\$FolderName\$agent" -ArgumentList $PestArguments
}
}
Invoke-Command -Session $Session -ScriptBlock $ScriptBlock -ArgumentList $MasterNode, $ControlFile
}
Any help would be greatly appreciated!

Check if process is running on multiple remote computers and copy a file if process is NOT running, keep list of those which succeeded

I need to copy a file to multiple computers, but can only do so if a particular app (process) is not running.
I know I can use Invoke-Command to run a script (scriptblock) on a list of machines.
But how can I check if process is running on the machine and then only copy file if it is not running.
So that at the end of running against a load of computers I can easily see those which succeeded e.g. process was not running and file was copied
Thanks
UPDATE:
I am assuming something like this will do the first bits of what I am asking, but how to visually show or log success or failure so I know which computers have been done - doesn't need to be anything fancy, even if simply a variable that holds computername of those where process wasn't running and file was copied okay
Invoke-Command -ComputerName PC1, PC2, PC3 -ScriptBlock {
If ((Get-process -Name notepad -ea SilentlyContinue) -eq $Null){
Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "C:\test\file.txt" -Force
}
}
$Procs = invoke-command -ComputerName PC1 { get-process | Select Name }
If($Procs -notmatch "Notepad"){ Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$PC1\c$\test\" -Force}
edited:
$computers = #("PC1","PC2","PC3")
Foreach($computer in $computers){
$Procs = invoke-command -ComputerName $computer { Get-Process Notepad -ErrorAction SilentlyContinue}
If(!$Procs){"$Computer - not running Notepad"; Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$computer\c$\test\" -Force}
elseif($Procs){"$Computer - is running Notepad"}
}
Edit2(for clean output):
$computers = #("PC1","PC2","PC3")
$RNote = #()
$NNote = #()
$off = #()
Foreach($computer in $computers){
$TestC = Test-Connection -ComputerName $computer -Count 1
If(!($TestC)){$off += $computer} Else{
$Procs = invoke-command -ComputerName $computer { Get-Process Notepad -ErrorAction SilentlyContinue}
If(!$Procs){$NNote +=$computer; Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$computer\c$\test\" -Force}
elseif($Procs){$RNote +=$computer}
}
}
$leng =[array]$RNote.count,$NNote.Count,$off.count
[int]$max = ($leng | measure -Maximum).Maximum
for($i=0; $i -lt $max;$i++){
[pscustomobject]#{
"Notepad On" = $(if ($RNote[$i]){$RNote[$i]})
"Notepad Off" = $(if ($NNote[$i]){$NNote[$i]})
"Offline " = $(if ($off[$i]){$off[$i]})
}
}
I think this is what you're looking for or at least close:
$Results = #()
$Results +=
Invoke-Command -ComputerName DellXPS137000, DellXPS8920 -ScriptBlock {
$GPArgs = #{Name = "Notepad++"
ErrorAction = "SilentlyContinue"}
If ( $Null -ne (get-process #GPArgs )) {
#Process your copy here
$Status = "Success"
}
Else {$Status = "Failed"}
$Machine =
(Get-CimInstance -ClassName 'Win32_OperatingSystem').CSName
Return ,"$Machine : $Status"
}
Value of $Results:
PS> $results
DELLXPS137000 : Success
DELLXPS8920 : Failed
HTH

Set-Content using variables

I'm trying to generate PowerShell scripts using a PowerShell script. How do I get the variables' values inside the content of the newly generated script?
foreach ($server in $testServers) {
New-Item -ItemType File -Name "$($server)_wu.ps1" -Path "D:\tools\windows updates\uscripts"
Set-Content -Path "D:\tools\windows updates\uscripts\$($server)_wu.ps1" -Value {
$computer = $server
Invoke-Command -ComputerName $computer -ScriptBlock {
Set-ExecutionPolicy Bypass
Find-Module PSWindowsUpdate | Install-Module -Force | Import-Module -Force
}
$updatesCount = (Get-WindowsUpdate -ComputerName $computer).Count
while ($updatesCount -gt "0") {
psexec \\$computer -s "powershell.exe "Install-WindowsUpdate -Confirm:`$false -IgnoreReboot""
Restart-Computer -ComputerName $computer -Force -Wait
$updatesCount = (Get-WindowsUpdate -ComputerName $computer).Count
}
}
}
The result here is the following:
$computer = $server
Invoke-Command -ComputerName $computer -ScriptBlock {
Set-ExecutionPolicy Bypass
Find-Module PSWindowsUpdate | Install-Module -Force | Import-Module -Force
}
$updatesCount = (Get-WindowsUpdate -ComputerName $computer).Count
while ($updatesCount -gt "0") {
psexec \\$computer -s "powershell.exe "Install-WindowsUpdate -Confirm:`$false -IgnoreReboot""
Restart-Computer -ComputerName $computer -Force -Wait
$updatesCount = (Get-WindowsUpdate -ComputerName $computer).Count
}
But, in the new script, I'd like to have $server replaced by the name of the server for which the script is generated.
Alright - so I couldn't figure out how to manipulate my string and ended up solving it like this... Any shorter, simpler answer is absolutely welcome (note that the rest of the script changed a bit as well)
foreach ($server in $testServers) {
New-Item -ItemType File -Name "$($server)_wu.ps1" -Path "\\VILV12ICTSCRIPT\d$\windowsupdates\uscripts"
$value = '
$computer = $PSCommandPath
$computer = $computer.Split("`\")[3]
$computer = $computer.Split("_")[0]
Invoke-Command -ComputerName $computer -ScriptBlock {
Set-ExecutionPolicy Bypass
Find-Module PSWindowsUpdate | Install-Module -Force | Import-Module -Force
}
$updatesCount = (PsExec.exe \\$computer -s "powershell.exe "Get-WindowsUpdate"").Count
while ($updatesCount -gt "0") {
psexec \\$computer -s "powershell.exe "Install-WindowsUpdate -Confirm:`$false -IgnoreReboot""
Restart-Computer -ComputerName $computer -Force -Wait
$updatesCount = (PsExec.exe \\$computer -s "powershell.exe "Get-WindowsUpdate"").Count
}
Write-Host "No more updates available for $($computer)" -BackgroundColor DarkGreen
'
Set-Content -Path "\\VILV12ICTSCRIPT\d$\windowsupdates\uscripts\$($server)_wu.ps1" -Value $value
}
And both the script to generate and the generated scripts work like charms (^^,)
Cheers
The canonical way to do what you're asking is probably to use a here-string and insert particular values via the format operator (-f):
foreach ($server in $testServers) {
#'
$computer = '{0}'
Invoke-Command -ComputerName $computer -ScriptBlock {
Set-ExecutionPolicy Bypass
Find-Module PSWindowsUpdate | Install-Module -Force | Import-Module -Force
}
$updatesCount = (Get-WindowsUpdate -ComputerName $computer).Count
while ($updatesCount -gt "0") {
psexec \\$computer -s 'powershell.exe "Install-WindowsUpdate -Confirm:$false -IgnoreReboot"'
Restart-Computer -ComputerName $computer -Force -Wait
$updatesCount = (Get-WindowsUpdate -ComputerName $computer).Count
}
'# -f $server | Set-Content -Path "D:\tools\windows updates\uscripts\${server}_wu.ps1"
}
However, I'd argue that writing one parameterized script and invoking that with the respective computer name might be a more appropriate solution than creating one script per computer:
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]$Computer
)
Invoke-Command -ComputerName $Computer -ScriptBlock {
Set-ExecutionPolicy Bypass
Find-Module PSWindowsUpdate | Install-Module -Force | Import-Module -Force
}
$updatesCount = (Get-WindowsUpdate -ComputerName $Computer).Count
while ($updatesCount -gt "0") {
psexec \\$Computer -s 'powershell.exe "Install-WindowsUpdate -Confirm:$false -IgnoreReboot"'
Restart-Computer -ComputerName $Computer -Force -Wait
$updatesCount = (Get-WindowsUpdate -ComputerName $Computer).Count
}
Invocation:
PS> script.ps1 -Computer FOO

Background jobs stops after a few minutes

See below a simplified version of my PowerShell code.
$cred = Import-Clixml d:\cred.xml
Invoke-Command -ComputerName $computer -Authentication Credssp -Credential $cred -InDisconnectedSession -SessionOption #{OutputBufferingMode="Drop";IdleTimeout=2147483647} -Scriptblock {
$start_job = Start-Job -Scriptblock {
foreach ($user in $list) {
$a = Get-ChildItem -LiteralPath $user -Recurse -Force
Write-Output "$a.Count" | Out-File c:\test.log -Append
} -ArgumentList $list
}
I would send a list of maybe 30 names but after a few minutes of scanning the operations would stop without any error reported, it seems as if the Start-Job is blocked or suspended as the Invoke-Command is still running.
any idea on how to check or bypass this limitation?

Check the AppPool status through power-shell and start that on remote machine

The requirement is to extract the server name one by one and check the AppPool status, if that is found to be stopped, make it running. below code is not helping out.
$ErrorActionPreference = "Continue"
$status = gc -path "D:\Servers\server.txt"|ForEach-Object (invoke-command -ComputerName $_ -ScriptBlock {Import-Module Webadministration Get-WebAppPoolState -name (gc "D:\AppPool.txt")})
if ($status.value -eq "Started")
{
Write-Host ("ApppPool already running")
}
else
{
Invoke-Command -ScriptBlock {Start-WebAppPool}
Write-host ("AppPool has started successfully")
}
There were multiple problems with your code, I've gone through them individually so you can see what was stopping it from working correctly.
The syntax for foreach was wrong, you needed to use {} not () in this case. Normal brackets are only used like this ForEach ($number in $numArray ) {CODE} which you aren't.
You were checking $status outside the foreach loop - so it so was evaluating $status only once (with the final computers AppPool status) rather than for each computer.
Your second Invoke-Command didn't have a ComputerName parameter specified so was only running the command locally not against the remote computer, meaning the AppPool would never be started.
As you were specifying the AppPool name using gc "D:\AppPool.txt" this file would have to be present on every remote computer for it to work. I've changed this to be passed into the command as an argument so the file only needs to be on the computer running the script.
$Credentials = Get-Credential
$AppPools = Get-Content "D:\AppPool.txt"
$Servers = Get-Content -Path "D:\Servers\server.txt"
ForEach ($Server in $Servers) {
ForEach ($AppPool in $AppPools) {
$AppPoolState = Invoke-Command -ComputerName $Server -ScriptBlock {Import-Module WebAdministration; Get-WebAppPoolState -Name $args[0] } -ArgumentList $AppPool -Credential $Credentials
if ($AppPoolState.Value -eq "Started")
{
Write-Host "$AppPool AppPool already running on $Server"
}
else
{
Invoke-Command -ComputerName $Server -ScriptBlock {Start-WebAppPool -Name $args[0] } -ArgumentList $AppPool -Credential $Credentials
Write-Host "$AppPool AppPool started on $Server"
}
}
}
Note: I run a non-privileged account so have to supply Credentials. If the account you're running the script as has appropriate permissions to all the remote computers you can remove the three Credentials references.