Powershell foreach behavior changes in ISE vs. CLI - powershell

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)) {

Related

PowerShell: Invoke-Command with Try-Catch

I'm using the following code to give me output on the status of a batch of computers:
$Win2k8r2Computers = "Server1", "Server2", "Server3", "Server4"
$results = Invoke-Command -ComputerName $Win2k8r2Computers { #}
$props = #{}
Try {
<#If ($PSVersionTable.PSVersion.Major -eq "2") {
$props.Add('Message',"Server (Win2008r2) is currently running an incompatible version of PowerShell (v2.1)")
}#>
If (Get-Service | Where-Object { $_.Name -eq "W3SVC" } -ErrorAction Stop) {
$props.Add('Message', "IIS is installed (Win2008r2)")
}
Else {
$props.Add('Message', "IIS is NOT installed (Win2008r2)")
}
}
catch {
$props.Add('Message', 'Error: {0}' -f $_)
}
New-Object -Type PSObject -Prop $Props
}
It's working as expected other than the catch not appearing to actually catch and return errors to the $results variable. What am I missing?
In your current code, you are passing parameter -ErrorAction only to Where-Object. So you would only catch errors of the Where-Object cmdlet. Get-Service woud still run with the default ErrorAction value of Continue.
To turn errors of both Get-Service and Where-Object into terminating errors that can be catched, either pass -ErrorAction 'Stop' to both of them...
If (Get-Service -ErrorAction Stop | Where-Object { $_.Name -eq "W3SVC" } -ErrorAction Stop)
... or (more efficiently) set the $ErrorActionPreference variable at the beginning of the script and remove the -ErrorAction parameter:
$Win2k8r2Computers = "Server1", "Server2", "Server3", "Server4"
$results = Invoke-Command -ComputerName $Win2k8r2Computers { #}
$props = #{}
Try {
$ErrorActionPreference = 'Stop'
<#If ($PSVersionTable.PSVersion.Major -eq "2") {
$props.Add('Message',"Server (Win2008r2) is currently running an incompatible version of PowerShell (v2.1)")
}#>
If (Get-Service | Where-Object { $_.Name -eq "W3SVC" }) {
$props.Add('Message', "IIS is installed (Win2008r2)")
}
Else {
$props.Add('Message', "IIS is NOT installed (Win2008r2)")
}
}
catch {
$props.Add('Message', 'Error: {0}' -f $_)
}
New-Object -Type PSObject -Prop $Props
}
Caveat:
$ErrorActionPreference is ignored by cmdlets in PowerShell script modules, unless they take special care of handling it.
See this answer for some insight.
In this case it works though, because both cmdlets are implemented in C# modules.

Make Azure powershell wait for task to complete

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

While loop to check registry for value and then run an exe file

I'm trying to write a while-loop that will check the registry for a value and continue checking until found. Once found I wan't it to run an executable located on a network share then stop the loop. So far I can get it to look for the value in the registry and then run the executable, but it keeps running the executable over and over and I'm not sure how to get it to break after running the executable.
Here's my code so far:
$regkey = 'HKLM:\Software\WOW6432Node\FolderPath'
$name = 'Test'
$exists = Get-ItemProperty -Path $regkey -Name $name -ErrorAction SilentlyContinue
While ($true) {
if (($exists -ne $null) -and ($exists.Lenght -ne 0)) {
& \\NETWORKSHARE\FolderName\Executable.exe -parameter -parameter "C:\Program Files (x86)\FolderName\JavaScriptObject.json" -parameter
} else {
Start-Sleep -Seconds 15
}
}
Thanks in advance!
Two things, first it would be better to make it a DO {} WHILE {} loop so it is forced to the the test and continue testing until true, and second the loop continues indefinitely without doing anything in your example because it is only continually testing whether or not $exists was true when it entered the while loop. To re-test the true/false status of $exists you have to reassign it at the beginning of your loop each time you run the loop.
$regkey = 'HKLM:\Software\WOW6432Node\FolderPath'
$name = 'Test'
$Notdone = $true
DO {
$exists = Get-ItemProperty -Path $regkey -Name $name -ErrorAction SilentlyContinue
if (($exists -ne $null) -and ($exists.Lenght -ne 0)) {
& \\NETWORKSHARE\FolderName\Executable.exe -parameter -parameter "C:\Program Files (x86)\FolderName\JavaScriptObject.json" -parameter
$Notdone = $false
}
else {
Start-Sleep -Seconds 15
}
} While ($Notdone)
Once the condition you're waiting on is met, you want to stop looping. Use the break statement to immediately jump to the next statement outside the loop:
$exists = Get-ItemProperty -Path $regkey -Name $name -ErrorAction SilentlyContinue
While ($true) {
if (($exists -ne $null) -and ($exists.Length -ne 0)) {
& \\NETWORKSHARE\FolderName\Executable.exe -parameter -parameter "C:\Program Files (x86)\FolderName\JavaScriptObject.json" -parameter
break
} else {
Start-Sleep -Seconds 15
}
}
By the way, the loop above will only work if the registry value exists before the loop is entered; otherwise it will get stuck in an infinite loop because the value of $exists never changes inside the loop. Instead, you need to reevaluate the condition on each iteration:
While ($true) {
$exists = Get-ItemProperty -Path $regkey -Name $name -ErrorAction SilentlyContinue
if (($exists -ne $null) -and ($exists.Length -ne 0)) {
& \\NETWORKSHARE\FolderName\Executable.exe -parameter -parameter "C:\Program Files (x86)\FolderName\JavaScriptObject.json" -parameter
break
} else {
Start-Sleep -Seconds 15
}
}
You can simplify the code slightly by starting the executable outside of the loop and omitting the else branch:
While ($true) {
$exists = Get-ItemProperty -Path $regkey -Name $name -ErrorAction SilentlyContinue
if (($exists -ne $null) -and ($exists.Length -ne 0)) {
break
}
Start-Sleep -Seconds 15
}
& \\NETWORKSHARE\FolderName\Executable.exe -parameter -parameter "C:\Program Files (x86)\FolderName\JavaScriptObject.json" -parameter
An even more concise but slightly less readable version would be this:
While (($exists = Get-ItemProperty -Path $regkey -Name $name -ErrorAction SilentlyContinue) -eq $null -or $exists.Length -eq 0) {
Start-Sleep -Seconds 15
}
& \\NETWORKSHARE\FolderName\Executable.exe -parameter -parameter "C:\Program Files (x86)\FolderName\JavaScriptObject.json" -parameter

Function within a Function - Powershell

OK I am going to try to explain this as best as I can. What started out as a simple script has turned into a huge mess and now I cannot figure out how to get it working. I have been coming here for answers for some time so maybe you guys can help.
What I am trying to do is a import a list of systems and check to see if they are online. If they are online they go in one list and if not they go in another.
foreach ($server in $servers) {
if (Test-Connection $server -Count 1 -ea 0 -Quiet) {
Write-Host "$server Is Up" -ForegroundColor Green
$server | out-file -Append $liveSystems -ErrorAction SilentlyContinue
} else {
Write-Host "$server Is Down" -ForegroundColor Red
$server | out-file -Append $inactive -ErrorAction SilentlyContinue
}
}
From there I check to see if the application I need installed is on the systems. That is where things start to go off-track. When I run the function to process the $liveSystems file all I get is the last line of the file (or the same system over and over) and not each system as it should be.
function Is-Installed( $program ) {
$x86 = ((Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall") |
Where-Object { $_.GetValue( "DisplayName" ) -like "*$program*" } ).Length -gt 0;
$x64 = ((Get-ChildItem "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall") |
Where-Object { $_.GetValue( "DisplayName" ) -like "*$program*" } ).Length -gt 0;
}
$program
function process-file1 {
param($filename)
Get-Content $filename -PipelineVariable line | ForEach-Object {
Is-Installed -program "My_Service"
if (Is-Installed -eq "True") {
Write-Host "$server has agent installed" -ForegroundColor Green
$server | Out-File $installed -ErrorAction SilentlyContinue
}
else
{
Write-Host "$server does not have agent installed" -ForegroundColor Red
$server | Out-File -Append $notInstalled -ErrorAction SilentlyContinue
}
}
}
process-file1 -filename $liveSystems
Once I can get the systems to process through the list of installed and not installed I am trying to take the list of installed systems and check which ones have the service running and which ones do not.
$array = #()
foreach($i in (gc $installed)) {
$svc = Get-Service my_service -ComputerName $i -ea "0"
$obj = New-Object psobject -Property #{
Name = $svc.name
Status = $svc.status
Computer = $i
}
$array += $obj
}
$array | Select Computer,Name,Status | Export-Csv -Path $resultsFile -
NoTypeInformation
Last but not least I run through that list of running and not running and attempt to start the service on systems that are not running.
function process-CSVfile2 {
param($filename)
Import-Csv $filename |
ForEach-Object -PipelineVariable object {
if($_.Status -eq "Running") {
Write-Host "Your Service is currently Running on" $_.Computer
}
if($_.Status -eq "Stopped") {
$serviceName = 'my_service'
$service = Get-CimInstance Win32_Service -ComputerName $_.Computer -Filter "Name=$serviceName"
$service.Start()
$service.WaitForStatus("Started",'00:00:30')
Start-Sleep 10
}
}
}
Several of these blocks run separately but when put together they will not run. I can't seem to get past the second block where it just looks at the same line over and over.
In addition there is a piece I have been trying to get working that would install the application on systems that do not have the service installed but that is not working either but I will save that for a different time.
If anyone can help me with this I would really appreciate it. After 3 days of trying to get it running I am at my wits end.
I'd create objects and properties instead of files with computers online etc...
Something like:
$Computers=New-Object -TypeName System.Collections.ArrayList
$Servers = #(Get-Content -path c:\servers.txt)
$Servers = $Servers | ? {$_} | select-object -uniqe |ForEach-Object {$_.TrimEnd()}
$Servers|ForEach-Object {
$tempobj=New-Object -TypeName PSObject
$tempobj | Add-Member -type NoteProperty -name Name -value $_
$tempobj | Add-Member -type NoteProperty -name isOnline -value $FALSE
$tempobj | Add-Member -type NoteProperty -name Installed -value $FALSE
$tempobj | Add-Member -type NoteProperty -name serviceRunning -value $FALSE
[void]$Computers.Add($tempobj)
then You could work on array (no need for additional files)
$Computers|Where-Object {$_.isOnline -eq $TRUE}
etc

How to Disable all BizTalk Hostinstances with PowerShell script

I'm trying to make a PowerShell script to stop and disable from starting all BizTalk host instances.
Stopping is no problem with this code:
$hosts = Get-WmiObject MSBTS_HostInstance -Namespace 'root/MicrosoftBizTalkServer'
foreach($hostinst in $hosts) {
if ( ($hostinst.ServiceState -ne 1) -and ($hostinst.ServiceState -ne 8) ) {
Write-Host "Stop Hostinstance" $hostinst.HostName
$hostinst.Stop()
Write-Host "Hostinstance" $hostinst.HostName "stopped"
}
}
But now I'm trying to Disable all stopped Host Instances from starting up.
My first try gives no error but doesn't do anything.
All host instance are mentioned in the output but they are not disabled.
$hosts = Get-WmiObject MSBTS_HostInstance -Namespace 'root/MicrosoftBizTalkServer'
foreach($hostinst in $hosts) {
if ( ($hostinst.ServiceState -eq 1) -or ($hostinst.ServiceState -eq 8) ) {
Write-Host "disable Hostinstance" $hostinst.HostName
$hostinst.IsDisabled = $true
Write-Host "Hostinstance" $hostinst.HostName "is disabled"
}
}
My second try gives an error because of the -path parameter.
$hosts = Get-WmiObject MSBTS_HostInstance -namespace 'root/MicrosoftBizTalkServer'
foreach($hostinst in $hosts) {
if ( ($hostinst.ServiceState -eq 1) -or ($hostinst.ServiceState -eq 8) ) {
Write-Host "disable Hostinstance" $hostinst.HostName
Set-ItemProperty -Path $hostinst__PATH -Name IsDisabled -Value $True
# $hostinst.IsDisabled = $true
Write-Host "Hostinstance" $hostinst.HostName "is disabled"
}
}
What is the correct method to set the property IsDisabled to $true or to $false?
BizTalk host instance is a windows service. So you can use powershell's Get-Service cmdlet to stop and disable it.
Suppose your host name is BizTalkServerApplication
Then following script will do the job:
Get-Service -Name BTSSvc`$BizTalkServerApplication | Stop-Service -PassThru | Set-Service -StartupType disabled
Also note the "$" is escaped as "`$"
#Zee is close, but still needs to loop through.. Here's one way to do that - concat the host's name with BTSSvc
$hosts = Get-WmiObject MSBTS_HostInstance -Namespace 'root/MicrosoftBizTalkServer'
foreach($hostinst in $hosts) {
if ( ($hostinst.ServiceState -ne 8) ) { # ignore isolated hosts
$svcName = ('BTSSvc${0}' -f $hostinst.HostName) # get something Get-Service can work with
Get-Service -Name $svcName | Stop-Service -PassThru | Set-Service -StartupType disabled
Write-Host "Hostinstance" $hostinst.HostName "stopped and disabled"
}
}
And if you need to set them back to Automatic/started:
$hosts = Get-WmiObject MSBTS_HostInstance -Namespace 'root/MicrosoftBizTalkServer'
foreach($hostinst in $hosts) {
if ( ($hostinst.ServiceState -ne 8) ) { # ignore isolated hosts
$svcName = ('BTSSvc${0}' -f $hostinst.HostName) # get something Get-Service can work with
Get-Service -Name $svcName | Set-Service -StartupType Automatic -PassThru | Start-Service
Write-Host "Hostinstance" $hostinst.HostName "stopped and disabled"
}
}
And here's a way that might be even a little clearer:
Get-Service | Where-Object { $_.Name.StartsWith("BTSSvc") } | Stop-Service -PassThru | Set-Service -StartupType Disabled
Thanks for your answers.
However there are 2 reasons why this doesn't work completely fine in a BizTalk environment with multiple servers.
First in your solution you have to manually configure the list of servers for each environment in OTAP. With the option Get-WmiObject MSBTS_HostInstance -Namespace 'root/MicrosoftBizTalkServer' BizTalk knows which servers belong the BizTalkGroup.
Second (and more important) the present state is "Automatic(Delayed start)".
Setting the state back to Automatic(Delayed start) with Powershell script is not posible according to several websites.
I understand now why $hostinst.IsDisabled = $true didn't work.
Because the change must be saved first.
That's why I have added a command to save the changes.
$script:btscatalog.SaveChanges() | Out-Null
Unfortunately still nothing seems to be changed.
Do not use default windows service to disable the BizTalk service
Use WMI to get list of BizTalk host instances
$inProcessHosts = "HostType=1"
$nsBTS = "root/MicrosoftBizTalkServer"
$filter = "($inProcessHosts)"
$hostInstances = Get-WmiObject MSBTS_HostInstance -Namespace $nsBTS -Filter $filter
Disable items in a for each loop (redirect output from put)
foreach ($hostInstance in $hostinstances)
{
$hostInstance.IsDisabled = $true
$hostInstance.Put() > $null
}
EDIT:
There is a way to set automatic (delayed start) if needed
#=== Change the startup type for BizTalk services to Automatic (Delayed Start) ===#
get-service BTSSvc* | foreach-object -process { SC.EXE config $_.Name start= delayed-auto}
From Technet WIKI:
https://social.technet.microsoft.com/wiki/contents/articles/19701.biztalk-server-best-practices-create-and-configure-biztalk-server-host-and-host-instances.aspx