How do I add multi-threading? - powershell

Is there a way of getting the below to run in parallel (multi-threading)? I have about 200 servers that need to run and was wondering if there is a way of checking say 10 servers at once rather then one at a time...WMI is very slow in checking this one at a time.
clear
Write-Host "Script to Check if Server is Alive and Simple WMI Check"
$servers = Get-Content -Path c:\Temp\Servers.txt
foreach($server in $servers)
{
if (Test-Connection -ComputerName $server -Quiet)
{
$wmi = (Get-WmiObject -Class Win32_ComputerSystem -ComputerName $server).Name
Write-Host "$server responds: WMI reports the name is: $wmi"
}
else
{
Write-Host "***$server ERROR - Not responding***"
}
}

Use powershell jobs:
$scriptblock = {
Param($server)
IF (Test-Connection $server -Quiet){
$wmi = (gwmi win32_computersystem -ComputerName $server).Name
Write-Host "***$server responds: WMI reports the name is: $wmi"
} ELSE { Write-Host "***$server ERROR -Not responding***" }
}
$servers | % {Start-Job -Scriptblock $scriptblock -ArgumentList $_ | Out-Null}
Get-Job | Wait-Job | Receive-Job

Related

Updating windows server remotely via Powershell

Hello I have been trying to make this script work for some time now, but I keep running into problems. The script is written with multiple servers in mind, although I am just testing it only on one test system for now.
Following I will post the script.
#List of Servers
$SQLServer = #('testserver.net')
$ApplicationServer = #('testserver.net')
$Servers = #($SQLServer, $ApplicationServer)
$ServiceName = #('tomcat9', 'MSSQLSERVER')
#Information Message
Write-Host "Starting update process for the following Servers:" $Servers "and Services:" $ServiceName
#Gets status of Servers
ForEach ($Index in $Servers)
{
Invoke-command {Get-Service tomcat9} -comp $Servers[$Index]
Invoke-command {Get-Service -Name 'MSSQLSERVER'} -comp $Servers[$Index]
}
#Information Message
Write-Host "Stopping the specified Services:" $ServiceName
#Stops services
#Sequentially stops TomCat and then DB Applications
ForEach ($Index in $Servers)
{
$ArrayService = Invoke-Command {Get-Service tomcat9} -comp $Servers[$Index]
if ($ArrayService.Status -ne "Stopped")
{
Invoke-Command {Stop-Service tomcat9} -comp $Servers[$Index]
}
$ArrayService = Invoke-Command {Get-Service -Name 'MSSQLSERVER'} -comp $Servers[$Index]
if ($ArrayService.Status -ne "Stopped")
{
Invoke-Command {Stop-Service -Name 'MSSQLSERVER'} -comp $Servers[$Index]
}
}
#Information Message
Write-Host "Will now start Windows Updates on servers and reboot"
#Will Update Windows, automatically reboot and out the log in the specified directory. This will be done to all listed Servers
ForEach ($Index in $Servers)
{
$Scriptblock = {
Invoke-WUJob -Comp $Servers[$Index] -RunNow -Confirm:$false -Verbose -Script {
Install-WindowsUpdate -MicrosoftUpdate -NotCategory "SilverLight" -NotTitle "Preview" -AcceptAll -AutoReboot | Out-File C:\PSWindowsUpdate.log
}
}
Invoke-Command -Comp $Servers[$Index] -ScriptBlock $Scriptblock
}
#Information Message
Write-Host "Starting up Services again"
#Starting SQL Server and checking if its running, if not running it will give more time before the Script continues
ForEach ($Index in $SQLServer)
{
$ArrayService = Invoke-Command {Get-Service -Name $ServiceName} -comp $SQLServer[$Index]
$FilteredService = $ArrayService | where {$_ -like 'MSQL'}
while ($FilteredService.Status -ne 'Running')
{
Invoke-Command {Start-Service $FilteredService} -comp $SQLServer[$Index]
Write-Host $FilteredService.Status
Write-Host "SQL Service starting on " $SQLServer[$Index]
function Check-Status
{
Start-Sleep -seconds 30
$FilteredService.Refresh()
if ($FilteredService.Status -eq 'Running')
{
Write-Host "SQL Service running"
#Invoke-Command {Start-Service $ArrayService | where {$_ -like 'tomcat9'}} -comp $ApplicationServer
}
else
{
Check-Status
}
}
}
}
#Starting TomCat Service again
ForEach ($Index in $ApplicationServer)
{
Write-Host "MUMService starting on " $ApplicationServer[$Index]
#Invoke-Command {Start-Service $ArrayService | where {$_ -like 'tomcat9'}} -comp $ApplicationServer[$Index]
$ServiceStatus = Invoke-Command {Get-Service tomcat9} -comp $ApplicationServer[$Index]
if ($ServiceStatus.Status -eq 'Running')
{
Invoke-Command {Start-Service $ArrayService | where {$_ -like 'tomcat9'}} -comp $ApplicationServer[$Index]
Write-Host "MUM Service running"
}
else
{
Invoke-Command {Start-Service $ArrayService | where {$_ -like 'tomcat9'}} -comp $ApplicationServer[$Index]
}
}
ForEach ($Index in $Servers)
{
invoke-command {Get-Service mum_software_tomcat9} -comp $Servers[$Index]
invoke-command {Get-Service -Name 'MSSQLSERVER'} -comp $Servers[$Index]
}
Either my logic is really wrong, or this doesnt work this way in powershell, but when I try to execute the script as is, there is an error for the parameter 'ComputerName'. It says that the argument is null or empty. Provide an argument that is not null or empty and then try the command again. Apparently it does not like $SQLServer[$Index].
The update functionality itself seems to work by itself though.
What is wrong with this?

Powershell, I do input a list gather data and output that whole list into one CSV

I am creating a script that reads a list of computer names and collects data from security event logs about who is on the computer, how long they have been on for, and how long it has been since the computer has restarted. I have it working except that it does not output all the data into one CSV. I just receive one CSV file with one computer name.
function Get-KioskInfo {
param (
[parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True,Position=0)]
[Alias('PSComputerName','DNSHostName','CN','Hostname')]
[string]
$ComputerName = $env:COMPUTERNAME
)
#PARAM
$User = try {(Get-WmiObject -ComputerName $ComputerName Win32_ComputerSystem | Select-Object -ExpandProperty username).trimstart("NG\")} catch {Write-Output "User not detected";break}
$BootStart = ((get-date) - (Get-CimInstance win32_operatingsystem -ComputerName $ComputerName).LastBootUpTime).Days
#These variables are for the DATE & Time calculation
If ($user -NE $null)
{ Write-Verbose 1
# Do something
$Date1 = Get-date
Write-Verbose 2
$SP = Get-WinEvent -ComputerName $ComputerName -FilterHashTable #{LogName = "Security";ID="5379";Data=$User; StartTime=((Get-Date).AddDays(-1))}
Write-Verbose 3
$Date2 =($SP | select -first 1).timecreated
Write-Verbose 4
$USERLOGTIME = ($Date1-$Date2).hours.tostring("N2")
Write-Verbose 5
}
else{Write-Output "No user";break}
Write-Verbose 6
#Rename-Computer -ComputerName "Srv01" -NewName "Server001" -DomainCredential Domain01\Admin01 -Force ------ Rename script for computers if it is needed.
#$computers = Get-Content C:\Users\jaycbee\Desktop\kiosknames.txt ------ To load kiosk list
#foreach ($c in $computers) {start-job -Name $c -ScriptBlock ${Function:get-kioskinfo} -ArgumentList $c} for learning how to do a foreach script
Write "Computer Name: $Computername"
Write "---USER---"
Write "Name: $User"
Write "Log in Time $USERLOGTIME"
Write "Boot start $BootStart days ago"
$ComputerName | ForEach-Object {
if (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet)
{
Invoke-Command -ComputerName $ComputerName {
}
} # Offline Check
else
{
Write-Host "Computer is Unreachable or Offline" -ForegroundColor Gray
}
} # Foreach
$Continue = Read-Host "WARNING! This will READ LIST of computers in \\ou\ouor-groups\Desktop_Support\SD\Kiosks\kiosknames.txt Type CONTINUE to proceed."
if ($Continue -eq "CONTINUE")
{
$Computers = Get-Content '\\ou\ouor-groups\Desktop Support\SD\Kiosks\kiosknames.txt'
foreach ($C in $Computers) {start-job -Name $c -ScriptBlock ${Function:get-kioskinfo} -ArgumentList $c
}
}
[pscustomobject]#{ Name = $ComputerName ; User = $User ; "User Log in time in hours" = $USERLOGTIME;"BootStart days ago" = $BootStart} | export-csv -path "\\ou\ouor-groups\Desktop Support\SD\Kiosks\test45$ComputerName.csv" -Append
} #Function
#For each-computer | do this at this location,
Continuing from my comment. I too wonder why the use of jobs for this use case. Unless you are doing this on hundreds of computers, thus needing parallel processing.
This refactor/formatting is just my way of making sense of what you posted. I'm old, and crowded code just really hurts my eyes. ;-} Yet, code the way you like of course. ;-}
I do not have an environment to test this, but give it a shot.
function Get-KioskInfo
{
param
(
[parameter(ValueFromPipeline = $True,ValueFromPipelineByPropertyName = $True,Position = 0)]
[Alias(
'PSComputerName',
'DNSHostName',
'CN',
'Hostname'
)]
[string]
$ComputerName = $env:COMPUTERNAME
)
($User = try
{
(Get-WmiObject -ComputerName $ComputerName Win32_ComputerSystem |
Select-Object -ExpandProperty username).trimstart("NG\")
}
catch
{
'User not detected'
break
}
)
($BootStart = ((get-date) - (Get-CimInstance win32_operatingsystem -ComputerName $ComputerName).LastBootUpTime).Days)
If ($user -NE $null)
{
($Date1 = Get-date)
($SP = Get-WinEvent -ComputerName $ComputerName -FilterHashTable #{
LogName = 'Security'
ID = '5379'
Data = $User
StartTime = ((Get-Date).AddDays(-1))
})
($Date2 = (
$SP |
select -first 1
).timecreated)
($USERLOGTIME = ($Date1-$Date2).hours.tostring('N2'))
}
else
{
'No user'
break
}
"Computer Name: $Computername
---USER---
Name: $User
Log in Time $USERLOGTIME
Boot start $BootStart days ago"
$ComputerName |
ForEach-Object {
if (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet)
{Invoke-Command -ComputerName $ComputerName}
else
{Write-Warning -Message 'Computer is Unreachable or Offline'}
}
$UserMessage = '
WARNING!
This will READ LIST of computers in:
\\ou\ouor-groups\Desktop_Support\SD\Kiosks\kiosknames.txt
Type CONTINUE to proceed'
$Continue = Read-Host $UserMessage
if ($Continue -eq 'CONTINUE')
{
Get-Content '\\ou\ouor-groups\Desktop Support\SD\Kiosks\kiosknames.txt' |
foreach {
{start-job -Name $PSItem -ScriptBlock ${Function:get-kioskinfo} -ArgumentList $PSItem}
[pscustomobject]#{
Name = $ComputerName
User = $User
'User Log in time in hours' = $USERLOGTIME
'BootStart days ago' = $BootStart
}
} |
Export-Csv -path "$PWD\$ComputerName.csv" -Append
}
}
These didn't help me with my solution, but you were right about the start-jobs. I have to rework the entire script in order to get the correct info.

Can't assign value to a variable inside of Invoke-Command

It seems to be strange but I can't assign a value to variable inside of Invoke-Command. Here is the code below but when print out $targetComputerPath it's simply empty. What's wrong?
foreach ($item in $computersPath){
$computername = $item.Name
$username = $item.UserID
Write-Host computer $computername and user $username
if (Test-Connection -ComputerName $computername -Count 1 -ErrorAction SilentlyContinue)
{
if ($((Get-Service WinRM -ComputerName $computername).Status) -eq "stopped")
{
(Get-Service WinRM -ComputerName $computername).Start()
}
Invoke-Command -ComputerName $computername -ScriptBlock {
if ($((Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").ReleaseId) -eq "1903" )
{
$targetComputerPath = "\\"+$computername+"\c$\Users\"+$username+"\Desktop\"
write-host "1903"
}
else
{
$targetComputerPath = "\\"+$computername+"\c$\Users\"+$username+"\Desktop\"
write-host "something else"
}
}
}
write-host $targetComputerPath
}
The point of WinRM is that you take a script block, and execute it on a different machine.
None of the variables you define in the host script will be available on the remote machine.
This becomes more apparent when you separate the "task", a.k.a the script block, from the Invoke-Command, like this:
$task = {
$version = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
if ($version.ReleaseId -eq "1903") {
# note that `$username` cannot be available here, it's never been defined!
return "\\$env:COMPUTERNAME\c$\Users\$username\Desktop"
} else {
return "\\$env:COMPUTERNAME\c$\Users\$username\Desktop"
}
}
foreach ($item in $computersPath) {
$computername = $item.Name
$username = $item.UserID
Write-Host computer $computername and user $username
if (Test-Connection -ComputerName $computername -Count 1 -ErrorAction SilentlyContinue) {
$winrm = Get-Service WinRM -ComputerName $computername
if ($winrm.Status -eq "stopped") { $winrm.Start() }
$targetComputerPath = Invoke-Command -ComputerName $computername -ScriptBlock $task
Write-Host "The machine returned: $targetComputerPath"
}
}
As you can see, you can return values from the script block and they will be available as the return value of Invoke-Command.
If you want to pass arguments to your script block, this thread talks about that: How do I pass named parameters with Invoke-Command?

powershell -parallel not working

I am writing a PowerShell script to display the status of free space and CPU, below is snippet:
function Get_FreeSpace ($authType, $comp) {
$freespace = Invoke-Command -ScriptBlock {
Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='E:'" |
Select-Object Size,Freespace
} -ComputerName $comp -Credential $authType
$freesize = [String]::Format("{0:P2}" -f ($freespace.Freespace / $freespace.Size))
return $freesize
}
function Get_CPUAverage ($authType, $comp) {
$Cpu_Average = Invoke-Command -ScriptBlock {
Get-WmiObject Win32_Processor |
Measure-Object -Property LoadPercentage -Average |
Select-Object Average
} -ComputerName $comp -Credential $authType
return $Cpu_Average.Average
}
workflow Test-Workflow {
foreach -parallel ($serv1 in $Servers) {
$Size = Get_FreeSpace -authType $cred -comp $serv1
$Cpu_Avg = Get_CPUAverage -authType $cred -comp $serv1
Write-Host $Size
}
}
Test-Workflow
Write-Host $Size
$Size is not returning anything if I use -parallel loop.
should pass $Servers as param as shown below:
workflow Test-Workflow {
param($Servers)
foreach -parallel ($serv1 in $Servers) {
$Size = Get_FreeSpace -authType $cred -comp $serv1
$Cpu_Avg = Get_CPUAverage -authType $cred -comp $serv1
Write-Host $Size
}
}
Test-Workflow $Servers
Alternatively, invoke-command runs in parallel with multiple computers:
invoke-command comp1,comp2,comp3 { sleep 5; "done $env:computername" }

powershell loop to continous check if server is up

I want to run a script to check if 5 servers are up and running based on a specific service is running.If that service is running then we know that server is up and accesible. If it does not reply with a response back then I want it to continously check for it. Heres what I got so far:
Get-Service LANMANSERVER -ComputerName JOHNJ1
Get-Service LANMANSERVER -ComputerName JOHNJ2
Get-Service LANMANSERVER -ComputerName JOHND1
Get-Service LANMANSERVER -ComputerName JOHNM
Get-Service LANMANSERVER -ComputerName JOHNI
start-sleep 90
Yep you just need to check the Status property on the returned object like this:
$servers = "JOHNJ1", "JOHNJ2"
foreach ($server in $servers) {
$status = (get-service -Name lanmanserver -ComputerName $server).Status
if ($status -eq "Running") {
"Its Up!"
} else {
"Its Down!"
}
}
Update Here is an example of how to wait for a server to become online:
$servers = "JOHNJ1", "JOHNJ2"
foreach ($server in $servers) {
while ( (get-service -Name lanmanserver -ComputerName $server).Status -ne "Running" ) {
"Waiting for $server ..."
Start-Sleep -Seconds 10
}
"$server is Up!"
}