I have a powershell script that stops/starts VM in parallel at specific time using Jenkins. This script uses the -AsJob powershell cmdlet, this way the VMs show they are stopped in Jenkins output but really they are in process of deallocating in the Azure portal.
I also have a sleep timer for 5 minutes to get the Get-AzureRmVM -Status command.
Question:- Is there a way to loop it where I can check for the status of the VMs and if the VMs are NOT in Deallocated or Running state, the Script checks back in another minute or so. Once the VMs are finally in deallocated or running state, the script/job exits with success.
Code snippet
$JobList = #()
foreach ($VM in $vms)
{
if ($env:OPTION -eq "start")
{
Write-Output "Starting :- $($VM.Name) in $($VM.ResourceGroupName)"
$JobList += Start-AzureRmVM -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name -AsJob -Verbose
Write-Output "$($VM.Name) has started successfully `n"
Write-Output "--------------------------------------------------"
}
elseif ($env:OPTION -eq "stop")
{
Write-Output "Deallocating :- $($VM.Name) in $($VM.ResourceGroupName)"
$JobList += Stop-AzureRmVM -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name -Force -AsJob -Verbose
Write-Output "$($VM.Name) has been deallocated successfully `n"
Write-Output "--------------------------------------------------"
}
else
{
Write-Output "ERROR!!! No option selected, select an option"
}
}
sleep 300
Write-Output "`n##############################################"
Write-Output "### Writing Status of VMs to Workspace ###"
Write-Output "##############################################"
Get-AzureRmVM -Status | Where-Object {($_.tags.ManagedBy -eq "blaah") -And ($_.tags.Environment -eq "stage")}
EDIT - This code checks the status of the VM to see if it is deallocated. I cannot get it to run the loop over again because the VMs are in Running state.
$vms = (Get-AzureRmResource | Where-Object {($_.tags.ManagedBy -eq "blaah") -And ($_.tags.Environment -eq "Stage")}
foreach ($VM in $VMs) {
$vmDetail = Get-AzureRmVM -Name $VM.Name -ResourceGroupName $VM.ResourceGroupName -Status
foreach ($vmDetail in $vmDetail.Statuses[1]) {
$VMStatusDetail = $vmDetail.DisplayStatus
if ($VMStatusDetail -ne "Stopped") {
Write-Output "Waiting for $($VM.Name) to deallocate"
Write-Output "State:- $($VM.Name) is $VMStatusDetail"
start-sleep -s 5
}
else {
Write-Output "State:- $($VM.Name) is $VMStatusDetail"
}
}
}
If you throw the Stop-AzureRMVM as Jobs and keep the output in array of Jobs called $JobList, once all jobs have started, at the end and outside the loop you can add the following command to wait the jobs finish.
Write-Host $JobList
$JobList| Get-Job | Wait-Job | Receive-Job | Format-Table -AutoSize
Related
I wrote a script to tell me if the SEP Master Service is running for all computer listed in a file, start the service if its stopped, and let me know if it doesn't exist. It's working, but when the script hits a computer that is not online, it slows down until an error is returned and then finally goes to the next computer in the list. Is there a way to only scan for the service on the deivces in the list that are online and can be pinged on the network?
$computers = Get-Content -Path "C:\temp2\ComputerList.txt"
foreach ($computer in $computers) {
$service = Get-Service -name SepMasterService -computername $computer
$ServiceStatus = $service.Status
$ServiceDisplayName = $service.DisplayName
if ($ServiceStatus -eq 'Running') {
Write-Output "Service OK - Status of $ServiceDisplayName is $ServiceStatus on $computer"
}
elseif ($ServiceStatus -eq 'stopped') {
Start-Service -Name SepMasterService -PassThru
}
else {
Write-Output "Symantec Endpoint Protection doesn't exist on $computer"
}
}
Add a check for online machines using the test-connection cmdlet and go to the next computer.
$computers = Get-Content -Path "C:\temp2\ComputerList.txt"
foreach ($computer in $computers) {
if(!(Test-Connection -ComputerName $computer -Count 1 -Quiet))
{
Write-Output "$computer is offline"
continue
}
$service = Get-Service -name SepMasterService -computername $computer
$ServiceStatus = $service.Status
$ServiceDisplayName = $service.DisplayName
if ($ServiceStatus -eq 'Running') {
Write-Output "Service OK - Status of $ServiceDisplayName is $ServiceStatus on $computer"
}
elseif ($ServiceStatus -eq 'stopped') {
Start-Service -Name SepMasterService -PassThru
}
else {
Write-Output "Symantec Endpoint Protection doesn't exist on $computer"
}
}
The -Count parameter specifies how many time to ping the computer. The default is 4 but this will speed up your process. The continue statement will stop executing any of the code in the current iteration of the foreach loop and go to the next computer.
Hope this helps.
In Powershell ISE, the first condition is met. When running this same exact script in CLI, it skips the if statement despite it's condition being the same. The CLI simply outputs the ending 'else' statement. It doesn't even seem to evaluate the first 2 statements. Any ideas?
foreach ($vm in (Get-VM -Name $vm)) {
if($vm.ExtensionData.Runtime.PowerState -eq "poweredOn") {
Shutdown-VMGuest -VM $vm -Confirm:$false
while ($vm.ExtensionData.Runtime.PowerState -eq "poweredOn" -and ($x++ -lt 60))
{
Start-Sleep -Seconds 1
$vm.ExtensionData.UpdateViewData("Runtime.PowerState")
Write-Output "Waiting for $vm to shutdown gracefully. Took $x`s."
if ($x -gt 5) {
Write-Output "Forcefully powering off VM $vm"
Stop-VM $vm -Confirm:$false
}
}
}
elseif($vm.ExtensionData.Runtime.PowerState -eq "poweredOff") {
$DateTime = "$date"+"_autosnap"
New-Snapshot -VM $vm -Name ("$date"+"_autosnap")
Start-VM -VM $vm
}
else {
Write-Host "Snapshot failed. Machine is not shutdown."
}
Write-Host "Cleaning up previous snapshots for $vm"
Get-Snapshot -VM $vm | Where-Object {$_.Name -match "$retention"+"_autosnap"} | Remove-Snapshot
}
I would take a look at changing your $vm variable usage, this could be confusing for the terminal and ISE.
Perhaps changing the variable used in the Name parameter to be $vmname instead. Have it look something like the following:
foreach ($vm in (Get-VM -Name $vmname)) {
can somebody help me to create a powershell script to check a particular service is running in all the machines in active directory.
for example process like ccsvchst is available in all systems. I got the code for how to check in a single machine. but need to get code for all machines in AD.
$ProcessName = "ccsvchst"
if((get-process $ProcessName -ErrorAction SilentlyContinue) -eq $Null)
{ echo "Process is not running" }else{ echo "Process is running" }
$ProcessName = "ccsvchst"
Get-ADComputer -Filter * | ForEach-Object {
if((get-process $ProcessName -ComputerName $_.CN -ErrorAction SilentlyContinue) -eq $Null)
{ echo "Process is not running on $($_.CN)" }else{ echo "Process is running on $($_.CN)" }
}
More of a theory question...
I have a powershell script that exists on three servers. In this example the three servers are:
server1
server2
server3
I am using another machine, server4, to call script C:\ExampleScript.ps1 remotely using Invoke-Command while specifying the remote machine via the ComputerName parameter. The ultimate goal of the script is to detect whether powershell is running, if it is not, then the computer is "not busy" and can open up the script being called remotely. If the computer is "busy", move onto the next server and continue on through the three machines until all the parameter values have been exhausted. If all machines are busy, it would be ideal if there was a way to periodically check the processes and see if they are still open. In this way, execution of the script can be balanced across the various machines, in an albeit primitive fashion.
Consider the following code:
$servers = "server1","server2","server3"
$data = "param1", "param2", "param3", "param4", "param5", "param6"
#somehow loop through the different servers/data using the above arrays
$job = Invoke-Command $servers[0] {
$ProcessActive = Get-Process powershell -ErrorAction SilentlyContinue
if($ProcessActive -eq $null)
{
"Running"
Invoke-Command -ComputerName $env:computername -FilePath C:\ExampleScript.ps1 -ArgumentList $data[0]
}
else
{
"Busy go to next machine"
}
} -AsJob
Wait-Job $job
$r = Receive-Job $job
$r
The expected result trying to be achieved is attempting to load balance the script across the machines based on whether there is an active powershell process, if not move onto the next machine and perform the same test and subsequent possible execution. The script should go through all the values as specified in the $data array (or whatever).
I found this question interesting, so I wanted to give it a try.
$servers = "server1","server2","server3"
$data = New-Object System.Collections.ArrayList
$data.AddRange(#("param1", "param2", "param3", "param4", "param5", "param6"))
$jobs = New-Object System.Collections.ArrayList
do
{
Write-Host "Checking job states." -ForegroundColor Yellow
$toremove = #()
foreach ($job in $jobs)
{
if ($job.State -ne "Running")
{
$result = Receive-Job $job
if ($result -ne "ScriptRan")
{
Write-Host " Adding data back to que >> $($job.InData)" -ForegroundColor Green
$data.Add($job.InData) | Out-Null
}
$toremove += $job
}
}
Write-Host "Removing completed/failed jobs" -ForegroundColor Yellow
foreach ($job in $toremove)
{
Write-Host " Removing job >> $($job.Location)" -ForegroundColor Green
$jobs.Remove($job) | Out-Null
}
# Check if there is room to start another job
if ($jobs.Count -lt $servers.Count -and $data.Count -gt 0)
{
Write-Host "Checking servers if they can start a new job." -ForegroundColor Yellow
foreach ($server in $servers)
{
$job = $jobs | ? Location -eq $server
if ($job -eq $null)
{
Write-Host " Adding job for $server >> $($data[0])" -ForegroundColor Green
# No active job was found for the server, so add new job
$job = Invoke-Command $server -ScriptBlock {
param($data, $hostname)
$ProcessActive = Get-Process powershell -ErrorAction SilentlyContinue
if($ProcessActive -eq $null)
{
# This will block the thread on the server, so the JobState will not change till it's done or fails.
Invoke-Command -ComputerName $hostname -FilePath C:\ExampleScript.ps1 -ArgumentList $data
Write-Output "ScriptRan"
}
} -ArgumentList $data[0], $env:computername -AsJob
$job | Add-Member -MemberType NoteProperty -Name InData -Value $data[0]
$jobs.Add($job) | Out-Null
$data.Remove($data[0])
}
}
}
# Just a manual check of $jobs
Write-Output $jobs
# Wait a bit before checking again
Start-Sleep -Seconds 10
} while ($data.Count -gt 0)
Basically I create an array, and keep it constantly populated with one job for each server.
Data is removed from the list when a new job starts, and is added back if a job fails. This is to avoid servers running the script with the same data/params.
I lack a proper environment to test this properly at the moment, but will give it a whirl at work tomorrow and update my answer with any changes if needed.
I have done lots of reading about multi-threading in PowwerShell with Get-Job and Wait-Job but still cant seem to work it out.
Eventually, I will have this as a GUI based script to run and don't want my GUI to freeze up while its doing its task.
The script is looking for Event Logs of my Domain Controllers and then getting the details I want, then outputting them, it works like I need it to.
I can start a job using Invoke-Command {#script goes here} -ComputerName ($_) -AsJob -JobName $_ and the jobs run.
Script below:
Clear-Host
Get-Job | Remove-Job
(Get-ADDomainController -Filter *).Name | ForEach-Object {
Invoke-Command -ScriptBlock {
$StartTime = (Get-Date).AddDays(-4)
Try{
Get-WinEvent -FilterHashtable #{logname='Security'; id=4740;StartTime=$StartTime} -ErrorAction Stop `
| Select-Object * | ForEach-Object {
$Username = $_.Properties[0].Value
$lockedFrom = $_.Properties[1].Value
$DC = $_.Properties[4].Value
$Time = $_.TimeCreated
Write-Host "---------------------------------------------"
Write-Host $Username
Write-Host $lockedFrom
Write-Host $DC
Write-Host $Time
Write-Host "---------------------------------------------"
}#ForEach-Object
}catch [Exception] {
If ($_.Exception -match "No events were found that match the specified selection criteria") {
Write-Host "No events for locked out accounts." -BackgroundColor Red
}#If
}#Try Catch
} -ComputerName ($_) -AsJob -JobName $_ | Out-Null # Invoke-Command
}#ForEach-Object
Currently I have a While loop to tell me its waiting then to show me the result:
(Get-ADDomainController -Filter *).Name | ForEach-Object {
Write-Host "Waiting for: $_."
While ($(Get-Job -Name $_).State -ne 'Completed') {
#no doing anything here
}#While
Receive-Job -Name $_ -Keep
}#ForEach-Object
#clean up the jobs
Get-Job | Remove-Job
Thinking of my GUI (to be created), I will have a column for each Domain Controller and showing results under each heading, how do make it not freeze my GUI and show the results when they arrive?
I know its been asked a few times, but the examples I cant work out.
I would avoid Start-Job for threading - for efficiency try a runspace factory.
This is a basic setup which could be useful (I also have PS 4.0), and open to suggestions/improvements.
$MaxThreads = 2
$ScriptBlock = {
Param ($ComputerName)
Write-Output $ComputerName
#your processing here...
}
$runspacePool = [RunspaceFactory]::CreateRunspacePool(1, $MaxThreads)
$runspacePool.Open()
$jobs = #()
#queue up jobs:
$computers = (Get-ADDomainController -Filter *).Name
$computers | % {
$job = [Powershell]::Create().AddScript($ScriptBlock).AddParameter("ComputerName",$_)
$job.RunspacePool = $runspacePool
$jobs += New-Object PSObject -Property #{
Computer = $_
Pipe = $job
Result = $job.BeginInvoke()
}
}
# wait for jobs to finish:
While ((Get-Job -State Running).Count -gt 0) {
Get-Job | Wait-Job -Any | Out-Null
}
# get output of jobs
$jobs | % {
$_.Pipe.EndInvoke($_.Result)
}