Thread does not create multiple VMs (session issue) - multithreading

Data is taken from *.CSV file. Script should create in my test case 2 VM on the same vcenter but in different thread. Need guidance how to solve this.
cls
Add-PSSnapin VMware.VimAutomation.Core | Out-Null
#import VM settings
$VMs = Import-CSV '...\Data.csv' -UseCulture
#array for connections
$server = #()
#threads
$Throttle = 2
# Create session state
$sessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
# Create runspace pool consisting of runspaces
$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, $Throttle, $sessionState, $Host)
$RunspacePool.Open()
#array for jobs
$Jobs = #()
$global:DefaultVIServer
$ScriptBlocky = {
Param ($VM, $ses)
Add-PSSnapin VMware.VimAutomation.Core | Out-Null
Write-Host $VM.vm_name "Father session: " $ses.SessionSecret
$sessionf = Connect-VIServer $VM.vs_xstream__vc_host -Session $ses.SessionSecret -wa 0
Write-Host $VM.vm_name "Child session: " $sessionf.SessionId
$domain_name = $VM.FQDN.split('.')
Write-Host $domain_name
#Random for testing purposes
$OSspec = ("BasicLinuxSpec_rand{0}" -f (Get-Random))
# Create new OS Customization Specification template for further usage
New-OSCustomizationSpec –Name $OSspec –Domain ("{0}.{1}" -f $domain_name[1],$domain_name[2]) –DnsServer $VM.DNS,$VM.DNS2 –NamingScheme VM –OSType Linux
Get-OSCustomizationSpec $OSspec -Server $VM.vs_xstream__vc_host | Get-OSCustomizationNicMapping | Set-OSCustomizationNicMapping -IpMode UseStaticIp -IpAddress $VM.Ipaddress -SubnetMask $VM.NetMask -DefaultGateway $VM.Gateway
Write-Host $VM.vm_name "OSspec: "$OSspec
Write-Host $VM.vm_name "Cluster: "$VM.Cluster
Write-Host $VM.vm_name "Host: "$VM.vs_xstream__vc_host
# Selecting random Cluster for VMs deployment
$ClusterHost = Get-Cluster $VM.Cluster -server $VM.vs_xstream__vc_host | Get-VMHost | Get-Random
Write-Host "================="
Write-Host $VM.vm_name "ClusterHost " $ClusterHost
Write-Host "================="
New-Vm -VMhost $ClusterHost -Name $VM.vm_name -Server $VM.vs_xstream__vc_host -Datastore $VM.Datastore -Template $VM.Template -Description $VM.Description -DiskStorageFormat "Thin" -OScustomizationSpec $OSspec
Remove-OSCustomizationSpec $OSspec -Server $VM.vs_xstream__vc_host -confirm:$false
}
Write-Host ("Starting script: {0}" -f (Get-Date))
$startTime = Get-Date
$count = 0
ForEach($VM in $VMs)
{
$count = $count + 1
Write-Host $VM.vs_xstream__vc_host
Write-Host "Current connections:"
$Global:DefaultVIServers.count
$ses = Connect-VIServer $VM.vs_xstream__vc_host -User $VM.vs_xstream__vc_admin -Password $VM.vs_xstream__vc_password -wa 0
$Job = [powershell]::create()
$Job.RunspacePool = $RunspacePool
$Job.AddScript($ScriptBlocky).AddParameter("VM", $VM).AddParameter("ses", $ses)
Write-Host "Adding job to jobs list"
$Jobs += New-Object PSObject -Property #{
RunNum = $count
Job = $Job
Result = $Job.BeginInvoke()
}
#Disconnect-VIServer $VM.vs_xstream__vc_host -Confirm:$false -Force
}
Write-Host "Waiting.." -NoNewline
Do {
Write-Host "." -NoNewline
Start-Sleep -Seconds 1
} While ( $Jobs.Result.IsCompleted -contains $false)
$endTime = Get-Date
$totalSeconds = "{0:N4}" -f ($endTime-$startTime).TotalSeconds
Write-Host "All jobs finished in $totalSeconds seconds"
Results:
Name Port User
---- ---- ----
10.xxx.xx.10 443 VSPHERE.LOCAL\do1xstream
Starting script: 2/29/2016 2:48:20 PM
10.xxx.xx.10
Current connections:
1
Adding job to jobs list
10.xxx.xx.10
Current connections:
1
The specified mount name 'vmstores' is already in use.
The specified mount name 'vis' is already in use.
virtual38 Father session: f8bafd42e6b7c85aa313e2896eba11c0f9f876cf
Adding job to jobs list
virtual38 Child session: f8bafd42e6b7c85aa313e2896eba11c0f9f876cf
virtual38 domain com
Waiting...virtual39 Father session: f8bafd42e6b7c85aa313e2896eba11c0f9f876cf
virtual39 Child session:
virtual39 domain com
virtual39 OSspec: BasicLinuxSpec_rand618798101
virtual39 Cluster: do1mgmt
virtual39 Host: 10.xxx.xx.10
=================
virtual39 ClusterHost
=================
virtual38 OSspec: BasicLinuxSpec_rand581276505
virtual38 Cluster: do1mgmt
virtual38 Host: 10.xxx.xx.10
.=================
virtual38 ClusterHost 10.xxx.xx.2
=================
..............
As result shows because virtual38 was first in line it has session information and can perform actions.
maybe i am wrong about this I think session isssue is preventing to create second VM (virtual39 )
Update/notice: I realized that when threads $Throttle = 1 then both VM is created, when I set $Throttle = 2 or more then only one of them (first one) is created.
Any suggestions?

Believe me or not but I found what was wrong. Script is working, but when connecting to new session or existing one (or even different vCenter, when is need to create VM in more then one vCenter environment) DefaultVIServers variable is modified and session drops.
Solution:
* Create all connection to vCenter's before all other actions so connection would be stable. for example:
# Iterate and connect to all vCenters so connections to them will be stable and read only
ForEach($VM in $VMs)
{
If ($VM.Use -eq 1)
{
If($server.Contains($VM.vs_xstream__vc_host) -eq $FALSE)
{
$server += $VM.vs_xstream__vc_host
$AllSessions += Connect-VIServer $VM.vs_xstream__vc_host -User $VM.vs_xstream__vc_admin -Password $VM.vs_xstream__vc_password -NotDefault
Write-Host "Connected to: " $VM.vs_xstream__vc_host
}
}
}
After all work is done i simply disconnect from all sessions
# Disconnect all created sessions
ForEach($item in $server)
{
Write-Host ("Disconnecting from {0}...." -f ($item))
Disconnect-VIServer $item -Confirm:$false
}
Now script works perfectly and creates VM in different or the same vCenter. if anyone will experience any problems with it let me know.

Related

Get running VMs on Azure

I have the below code to check daily if all the VMs that are meant to auto-start have started successfully.
$Date = (Get-Date -Format "dd-MM-yyyy HH-mm").toString()
$Results = "C:\temp\DailyChecks_$($Date).txt"
$Cred = get-credential -UserName 'AZ-User'
Connect-AzAccount -Tenant 'TenantID' -credential $Cred | out-null
$fCount = (Get-AzVM -Status | Where-Object { $_.tags['Managed By'] -like 'Manager' }).count
do {
$rCount = (Get-AzVM -Status | Where-Object { $_.tags['Managed By'] -like 'Manager' -and $_.PowerState -eq 'VM Running' }).count
if($rCount -lt $fCount)
{
write-host "There are $rCount VMs running, checking again"
}
elseif($rCount -eq $fCount){write-host "There are $rCount VMs running, exiting loop"}
}until($fCount -eq $rCount)
Get-AzVM -Status | Where-Object { $_.tags['Managed By'] -like 'Manager' } |
Select-Object Name, PowerState | Format-Table | out-file $Results
I have a couple of questions:
Is there a better way to write this code?
How can I check if the code has been running for longer than 30 minutes and yet not all VMs are running?
You can determine for how much the time the script is running by using a StopWatch object.
$sw = [System.Diagnostics.Stopwatch]::new()
$sw.Start()
Whenever you want, just check the elapsed time by checking the elapsed property
$sw.Elapsed
$sw.Elapsed.TotalSeconds
Write-Host "Time elapsed: $([Math]::Round($sw.Elapsed.TotalMinutes,0)) min $($sw.Elapsed.Seconds) seconds"

Issues having a retry count work in powershell

I have a Powershell script that checks on a set of VM status before starting them. If the VM'S are in deallocating mode there should be a sleep and retry on 30 seconds. The code does not do a a retry. The code does a vm start in batches on 2 for vm's with wilcards as mentioned below in an order.
Need help if possible
$ResName= "resvmtest"
$action="start"
if($action -eq "start"){
$vnames=#('*dom*','*DBs*','*')
foreach($vname in $vnames) {
Write-Host "Starting VM with "$vname
$vmList = Get-AzVM -ResourceGroupName $ResName -Name $vname -Status | Select-Object Name, PowerState, ResourceGroupName
do{
$batch = #{
Skip = 0
First = 2
}
do{
foreach($vm in ($vmList | Select-Object #batch)){
$Stoploop = $false
[int]$Retrycount = "0"
do {
try {
if($vm.PowerState -eq "VM Deallocated"){
Write-Host "Job completed"
$Stoploop = $true
}
}
catch {
if ($vm.PowerState -eq "VM Deallocatting") {
Write-Host "VM Still not Deallocated"
Start-Sleep -Seconds 10
$Retrycount = $Retrycount + 1
}
}
}
While ($Stoploop -eq $false)
$params = #($vm.Name, $vm.ResourceGroupName,$vm.PowerState)
$job = Start-Job -ScriptBlock {
param($ComputerName,$serviceName,$statuses)
Start-AzVM -Name $ComputerName -ResourceGroupName $serviceName
} -ArgumentList $params
}
Wait-Job -Job $job
Get-Job | Receive-Job
$batch.Skip += 2
}
until($batch.skip -ge $vmList.count)
}
while($job.state -ne "Completed")
}
}
If you just want to wait for a VM's PowerState turns to VM Deallocated from VM Deallocatting, try the code below:
$vms = Get-AzVm -Status| Select-Object Name, PowerState,ResourceGroupName
#pick certain vm for demo here.
$vm = $vms[1]
$retryCount = 0
while($vm.PowerState -eq 'VM deallocating'){
Write-Host "waiting for VM deallocated..."
Start-Sleep -Seconds 5
$retryCount +=1
Write-Host "check count:$retryCount"
#get latest vm status
$vm.PowerState = (Get-AzVM -Name $vm.Name -ResourceGroupName $vm.ResourceGroupName -Status).Statuses[1].DisplayStatus
Write-Host "vm current state:"$vm.PowerState
}
Write-Host "vm new state:" + $vm.PowerState
Result:

Struggling to export PowerShell job information to excel

I have a PowerShell script that is running a web request as part of a job. With the script block that is run by the job I am logging the Endpoint Uri and the response time. and then after all the jobs are finished but before I remove the jobs I am trying to export the result into excel.
PS Verion:
Major Minor Build Revision
----- ----- ----- --------
5 1 17763 503
Code:
$Code = {
Param(
[array]$Domain,
[array]$Services,
[array]$TestData,
[string]$Path
)
$Log = #()
## Get Random School ##
$Random = Get-Random -InputObject $TestData -Count 1
## Get Random Service ##
$RandService = Get-Random -InputObject $Services
## Get Random Endpoint ##
$ServiceEndpoints = Get-Content "$Path\Service.Endpoints.json" | Out-String | ConvertFrom-Json
$GatewayEndpoints = $ServiceEndpoints.Services.$RandService.Gateway
$RandomEndpoint = Get-Random -InputObject $GatewayEndpoints
$Headers = #{
"Authorization" = "Bearer" + ' ' + $Random.Token
}
$Uri = 'https://' + $Domain + $RandomEndpoint
Try {
$TimeTaken = Measure-Command -Expression {
$JsonResponse = Invoke-WebRequest -Uri $Uri -Headers $Headers -ContentType 'application/json' -Method Get -UseBasicParsing
}
}
Catch {
}
$ResponseTime = [Math]::Round($TimeTaken.TotalMilliseconds, 1)
$LogItem = New-Object PSObject
$LogItem | Add-Member -type NoteProperty -Name 'Endpoint' -Value $Uri
$LogItem | Add-Member -type NoteProperty -Name 'Time' -Value $ResponseTime
$Log += $LogItem
Write-Host $Log
}
#Remove all jobs
Get-Job | Remove-Job
#Start the jobs. Max 4 jobs running simultaneously.
foreach($Row in $TestData){
While ($(Get-Job -state running).count -ge $MaxThreads){
Start-Sleep -Milliseconds 3
}
Start-Job -Scriptblock $Code -ArgumentList $Domain, $Services, $TestData, $Path
}
#Wait for all jobs to finish.
While ($(Get-Job -State Running).count -gt 0) {
start-sleep 1
}
$Log | Export-XLSX -Path .\Test.Results\Performance\Performance.Test.Log.xlsx -ClearSheet
#Get information from each job.
foreach($Job in Get-Job) {
$Info = Receive-Job -Id ($Job.Id)
}
#Remove all jobs created.
Get-Job | Remove-Job
I cannot seem to get the endpoint uri and the response time out of the script block. When I try to export the $Log, all that happens is it creates an empty excel file.
Write-Host $Log
#{Endpoint=https://domain/customer/v1/years/2019/marks; Time=1233.3}
#{Endpoint=https://domain/customer/v1/years/2019/marks; Time=2131.7}
You've to return $Log in your script block, since $Log lives in another scope. You can return $Log in your $Code script block, and finally, fetch it via Receive-Job.
Change your code to:
$Code = {
...
$Log += $LogItem
Write-Host $Log
$Log # return via pipeline to the caller
}
As Niraj Gajjar's answer suggests you can use Export-Csv cmdlet to create an Excel file:
#Get information from each job.
foreach($Job in Get-Job) {
Receive-Job -Id ($Job.Id) | Export-CSV -Path .\Test.Results\Performance\Performance.Test.Log.xlsx -Append -NoTypeInformation
}
Global variables inside of jobs aren't visible in the main script since job is running in a new session with its own global space.
You can use Export-Csv to open file in excel.
sample code with multiple jobs to CSV format :
$jobs = #() # INITILIZING ARRAY
$jobs += Start-Job { appwiz.cpl } # START JOB 1 AND ADDING TO ARRAY
$jobs += Start-Job { compmgmt.msc } # START JOB 2 AND ADDING TO ARRAY
$jobs += Start-Job { notepad.exe } # START JOB 3 AND ADDING TO ARRAY
foreach ( $job in $jobs) # INTERATION OF JOBS
{
Export-Csv -InputObject $job "C:\result.csv" -Append # SAVING TO FILE
}
Note : There are some internal properties of job which are not converted by CSV because depth is 1 but some basic properties are available.

How to use powershell to wait for an action to complete

I have a script to start VMs/Stop them at a specific time. The issue is, I want the script to only exit when all the VMs are started/running or when they are deallocated/stopped. My current problem is when a VM is starting, it waits until it has started before moving to the next one.
eg: vm1 is starting..
vm2 is starting..
vm3 is starting..
vm1 is now running..
vm2 is still starting
vm3 is still starting
vm1 is now running..
vm2 is now running..
vm3 is now running..
Then script exits.
Full script here
$ACTION="start"
Write-Output "Number of Virtual Machines: $($GetVMS.Name.Count)" `n
$GetVMS | Format-Table
$startstopvm = {
$ResourceGroupName = $args[0]
$Name = $args[1]
$ACTION = $args[2]
# Get VM status
try {
$VMs = Get-AzureRmVM -Name $Name -ResourceGroupName $ResourceGroupName -Status -WarningAction SilentlyContinue
} catch {
Write-Output ("Cloud not get vm $Name in $ResourceGroupName")
$Error[0]
Exit 1
}
if ($ACTION -eq "start")
{
foreach ($VM in $VMs)
{
if ($VM.Statuses[1].Code -eq "PowerState/running")
{
Write-Output ($VM.Name + " in " + $VM.ResourceGroupName + " is already running")
}
else
{
# The VM needs to be started
Write-Output ("Starting VM " + $VM.Name)
$startVM += Start-AzureRmVM -Name $VM.Name -ResourceGroupName $VM.ResourceGroupName -AsJob -ErrorAction Continue
$startTime = Get-Date
$timeElapsed = $((Get-Date) - $startTime).TotalMinutes
while ($timeElapsed -lt 2)
{
$startVM = Get-AzureRmVM -Name $VM.Name -ResourceGroupName $VM.ResourceGroupName -Status -WarningAction SilentlyContinue
if ($startVM.Statuses[1].Code -match "PowerState/(running|starting)")
{
# The VM started, so send notice
Write-Output ($VM.Name + " in " + $VM.ResourceGroupName + " has been started`n")
break
}
Start-Sleep -s 30
}
if ($getStat.Statuses[1].Code -ne "PowerState/(running|starting)")
{
# The VM failed to start, so send notice
Write-Output ($VM.Name + " failed to start`n")
}
}
}
}
NOTE: I am reading the VM name & RG from a file
try {
$VMList = Get-Content C:\Users\local\Desktop\VMs.csv | ConvertFrom-Csv
} catch {
Write-Output ("Cannot open file...")
exit -1
}
$Result = #()
foreach ($vm in $VMList) {
Invoke-Command -ArgumentList $vm.ResourceGroupName, $vm.Name, $ACTION -Verbose -ScriptBlock $startstopvm
}
The simplest way I have done this in the past is to use the -Wait parameter on a Start-Process cmdlet.
You should be able to implement this with small changes.
Saving your current code block into a seperate .ps1 i.e. startstopvm.ps1
You could then change the Invoke-Commmand line to read something like:
Invoke-Command -ArgumentList $vm.ResourceGroupName, $vm.Name, $ACTION -Verbose -ScriptBlock {Start-Process powershell.exe -Argument "C:\Scripts\Backup.ps1 TestBackup" -Wait}
Definitely some other ways to do it but an approach like this has always worked for me

Enabling Sharepoint Features With Powershell

I am new to PowerShell and have been asked to modify a script that is used to activate features on a site. The script has some latency issues between the two feature activations, so I decided to make a new function that will enable the feature and sleep or delay until the feature is finished enabling. Will this work? And if so, how can I tell that the feature is finished activating?
# This is a function that enable's a Sharepoint Feature and Sleeps Until Its Finished Enabling
function Feature-Enable
{param ([string]$ID, [string]$URL)
#Enable Feature
Enable-SPFeature -Identity $ID -url $URL -Confirm:$false
#Sleep Until Feature is Done Enabling
}
#Set parameters/variables for script
$serverName = "someServerName"
$rootwebUrl = "someRootUrl"
$currentpath=Split-Path -Path $MyInvocation.MyCommand.Path -Parent
$siteURL = "http://" + $servername + $rootwebURL
$start = Get-Date -DisplayHint Time
# check to ensure Microsoft.SharePoint.PowerShell is loaded
$snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'}
if ($snapin -eq $null) {
Write-Host "Loading SharePoint Powershell Snapin"
Add-PSSnapin "Microsoft.SharePoint.Powershell"
}
# Active Site Collection Features (in order)
write-host ""
write-host "----------------------------------------"
write-host "Begin activation of site collection features"
write-host "----------------------------------------"
write-host ""
Feature-Enable –identity "3cbf5d1e-ff2e-48d2-82a4-99b060381268" -URL $siteUrl
#NOTE: POTENTIAL LATENCY ISSUES. MAY NEED TO INSERT DELAY TIMER!
Feature-Enable –identity "bbde524e-9293-468e-84f7-fdb763ffa309" -URL $siteUrl
write-host ""
write-host "----------------------------------------"
write-host "Activation of site collection features - DONE!"
write-host "----------------------------------------"
write-host ""
$end= Get-Date -DisplayHint Time
Write-Host "Started at: " $start " Ended at: " $end;
function WaitForJobToFinish([string]$SolutionFileName)
{
$JobName = "*solution-deployment*$SolutionFileName*"
$job = Get-SPTimerJob | ?{ $_.Name -like $JobName }
if ($job -eq $null)
{
Write-Host 'Timer job not found'
}
else
{
$JobFullName = $job.Name
Write-Host -NoNewLine "Waiting to finish job $JobFullName"
while ((Get-SPTimerJob $JobFullName) -ne $null)
{
Write-Host -NoNewLine .
Start-Sleep -Seconds 2
}
Write-Host "Finished waiting for job.."
}
}

Resources