Test battery status using PowerShell - powershell

I'm trying to use PowerShell to tell me when my computer is on battery or AC Power.
I want my script to send me a windows notification when my laptop's charger unplugs.
For the moment, I try to use a recursive fonction to test my battery status every 5 seconds but it doesn't work...
Please, be indulgent about my level, I didn't know anything about PowerShell 3 hours ago... And the last time I coded something was a long time ago !
Function Test-IsOnBattery
{
$battery = Get-WmiObject Win32_Battery
If ($battery.BatteryStatus -eq 2) {
Write-Host "PC sur secteur."
Start-Sleep -Seconds 5
return Test-IsOnBattery
}
Else {
Write-Host "PC sur batterie."
New-BurntToastNotification -Text "Battery Notification" , "Batterie plus sur secteur !"
}
}

Nathan,
Here's a script you can use that you can run from a Scheduled Task, rather than a loop, and have it start on boot up and repeat every so many minutes.
<#+---------------------------------------------------------------------------+
| PowerShell Pgm: BatteryStatus.ps1 |
| Programmed By : The Computer Mentor |
| aka : RetiredGeek # askWoody.com & StackOverFlow.com |
| Created : 06 Mar 2013 |
| Last Updated : 23 Jan 2023 | |
| Current Ver. : 6.0 |
+---------------------------------------------------------------------------+
#>
Clear-Host
Add-Type -AssemblyName "System.Windows.Forms"
$StatusMsg = {
[Windows.Forms.MessageBox]::Show($Message, $Title,
[Windows.Forms.MessageBoxButtons]::OK ,
[Windows.Forms.MessageBoxIcon]::Information)}
$Message = ""
$Title = "Battery Status:"
<#+-----------------------------------------------------+
| BatterStatus Values |
|Other (1) The battery is discharging. |
|Unknown (2) The system has access to AC so no |
| battery is being discharged. However, |
| the battery is not necessarily charging.|
|Fully Charged (3) |
|Low (4) |
|Critical (5) |
|Charging (6) |
|Charging and High (7) |
|Charging and Low (8) |
|Charging and Critical (9) |
|Undefined (10) |
|Partially Charged (11) |
+-----------------------------------------------------|#>
$GWArgs = #{ Class = "Win32_Battery"
ComputerName = "LocalHost"
}
$MyBattery = Get-CIMInstance #GWArgs
If ($Null -eq $MyBattery) {
$Message = "No Battery Present"
}
Else {
$BatteryRemaining = [Int]$MyBattery.EstimatedChargeRemaining
if(($BatteryRemaining -lt 30) -and
$($MyBattery.BatteryStatus) -eq 1) {
$Message = "Battery Low...Please Charge Me!"
}
Elseif(($BatteryRemaining -gt 90) -and
$($MyBattery.BatteryStatus) -ne 1) {
$Message =
"Battery CHARGED above 90%...Please Unplug Me!"
}
} #End Else
if($Message -ne "") {
$Null = & $StatusMsg
}
<#
+----------------------------------------------------------+
| Notes: |
| 1. To call as a scheduled task do the following in the |
| Action Pane |
| A. Action: Start a Program |
| B. Program/script: powershell.exe |
| D. Set the Trigger run At Logon and then repeat |
| every few minutes. |
+----------------------------------------------------------+
#>
If you want to use Toast msgs. just replace that logic where I have my $StatusMgs lines.

Related

Why isn't the conditional statement working right?

I've got some Reports that I'm trying to loop through, and fetch the connection ID's (User ID's) and list those reports along with the usernames.
A report can have the following scenarios:
No DataSources
1 DataSource (1 User ID)
More than 1 DataSources (Therefore more than 1 user id's)
The following script does the job, however, for some reason, reports with only 1 datasource seem to be getting executed in the else ... No Connection ID's found! statement. That shouldn't be the case considering there is AT LEAST 1 Datasource, so they should be going through this if ($DataSourceValue.DataModelDataSource.count -gt 0) statement instead!
Below is script accompanied by current output vs expected output:
$loopCount = 1
$PBI_Reports_Array = #()
$DataSourceQueue = New-Object System.Collections.Queue
try {
$PBI_Reports_Array = $(Invoke-RestMethod -UseDefaultCredentials -uri $($webPortalURL + "/api/v2.0/PowerBIReports"))
foreach ($reportPath in $PBI_Reports_Array.value.path) {
try {
foreach ($DataSource in $(Invoke-RestMethod -UseDefaultCredentials -uri $($webPortalURL + "/api/v2.0/PowerBIReports(path='" + $reportPath + "')/DataSources")))
{
$DataSourceQueue.Enqueue($DataSource)
}
while($DataSourceQueue.count)
{
$DataSourceValue = $($DataSourceQueue.Dequeue()).value
if ([string]::IsNullOrEmpty($($DataSourceValue))) {
write-host "$loopCount | $reportPath | No DataSource connection exists for this Report!";
}
else {
if ($DataSourceValue.DataModelDataSource.count -gt 0) #if there is at least 1+ DataSources and usernames...
{
#because there is more than 1 DataSources, that means there's also more than 1 connection ID and more than 1 connection ID's
#to loop through both of those variables, foreach loop would not be suitable for more than 1 variable,
#so we use good old for loop
0..($DataSourceValue.DataModelDataSource.length-1) | ForEach-Object {
write-host "$loopCount | $reportPath | $($DataSourceValue.DataModelDataSource[$_].Username) | Retrieved!"
}
}
else
{
write-host "$loopCount | $reportPath | $($DataSourceValue.DataModelDataSource.Username) | No Connection ID's found!"
}
}
}
#$loopCount++
}
catch {
write-host "$loopCount | ERROR! $($error[0])`r`n$($error[0].InvocationInfo.PositionMessage)`r`n$($error[0].ScriptStackTrace)"
}
$loopCount++
}
}
catch {
}
Current output:
1 | /Cash Flow/CFG | SI_123456_P | No Connection ID's found!
2 | /CPUR/DQ Dashboards | gp_powerbi_cpur | No Connection ID's found!
3 | /ABC/DQ Dashboards/PreCost ABC DQ Dashboard | gp_powerbi_cpur | Retrieved!
3 | /ABC/DQ Dashboards/PreCost ABC DQ Dashboard | SI_123456_P | Retrieved!
4 | /Prototypes/ARCHIVE/dummy data | | No Connection ID's found!
Expected Output:
1 | /Cash Flow/CFG | SI_123456_P | Retrieved!
2 | /CPUR/DQ Dashboards | gp_powerbi_cpur | Retrieved!
3 | /ABC/DQ Dashboards/PreCost ABC DQ Dashboard | gp_powerbi_cpur | Retrieved!
3 | /ABC/DQ Dashboards/PreCost ABC DQ Dashboard | SI_123456_P | Retrieved!
4 | /Prototypes/ARCHIVE/dummy data | | No Connection ID's found!
Note the 4th report has no connection ID's, therefore makes sense that the status is No Connection ID's found!. but reports 1 and 2 have 1 ID, so the status should be Retrieved! not No Connection ID's found! as it's currently displaying. For the 3rd report, it appears because it has more than 1 DataSource, the conditional statement is being applied correctly...
EDIT:
$DataSourceValue.DataModelDataSource.GetType()
yields:
for reports 1 & 2:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
for report 3:
True True Object[] System.Array
EDIT 2:
62 | /test/BMI vs Chocolate | | Retrieved!
Type : Import
Kind : File
AuthType : Windows
SupportedAuthTypes : {Windows}
Username :
Secret :
ModelConnectionName :
62 | /test/BMI vs Chocolate | | Retrieved!
Type : Import
Kind : File
AuthType : Windows
SupportedAuthTypes : {Windows}
Username :
Secret :
ModelConnectionName :
62 | /test/BMI vs Chocolate | | Retrieved!
Type : Import
Kind : File
AuthType : Windows
SupportedAuthTypes : {Windows}
Username :
Secret :
ModelConnectionName :
63 | /VP/Complexity_PROD | gp_powerbi_vp | Retrieved!
Id : 12345-cvgfgh7-87964-e76ufhf5
Name :
Description :
Path :
Type : DataSource
Hidden : False
Size : 0
ModifiedBy : user1
ModifiedDate : 2021-02-03T12:17:26.413-05:00
CreatedBy : SI_123456_P
CreatedDate : 2021-02-03T12:13:42.523-05:00
ParentFolderId :
IsFavorite : False
ContentType :
Content :
IsEnabled : True
ConnectionString : db=maxisdb;driver={DataDirect 7.1 Greenplum Wire Protocol};em=1;host=gp.companyxyz.com;port=5432;vsc=0
DataSourceType :
IsOriginalConnectionStringExpressionBased : False
IsConnectionStringOverridden : False
CredentialRetrieval : prompt
IsReference : False
DataSourceSubType : DataModel
Roles : {}
CredentialsByUser :
CredentialsInServer :
DataModelDataSource : #{Type=Import; Kind=Odbc; AuthType=UsernamePassword; SupportedAuthTypes=System.Object[]; Username=gp_powerbi_vp; Secret=; ModelConnectionName=}
Type : Import
Kind : Odbc
AuthType : UsernamePassword
SupportedAuthTypes : {UsernamePassword, Anonymous, Windows}
Username : gp_powerbi_vp
Secret :
ModelConnectionName :
I don't mean this as an answer but could you test your script by replacing this portion of code:
$DataSourceValue = $($DataSourceQueue.Dequeue()).value
if ([string]::IsNullOrEmpty($($DataSourceValue))) {
write-host "$loopCount | $reportPath | No DataSource connection exists for this Report!";
}
else
{
if ($DataSourceValue.DataModelDataSource.count -gt 0)
{
0..($DataSourceValue.DataModelDataSource.length-1) | ForEach-Object {
write-host "$loopCount | $reportPath | $($DataSourceValue.DataModelDataSource[$_].Username) | Retrieved!"
}
}
else
{
write-host "$loopCount | $reportPath | $($DataSourceValue.DataModelDataSource.Username) | No Connection ID's found!"
}
}
For this:
$DataSourceValue = $DataSourceQueue.Dequeue().Value
if (-not $DataSourceValue)
{
write-host "$loopCount | $reportPath | No DataSource connection exists for this Report!";
}
else
{
if ($DataSourceValue.DataModelDataSource.Username)
{
foreach($i in $DataSourceValue.DataModelDataSource.Username)
{
write-host "$loopCount | $reportPath | $i | Retrieved!"
}
}
else
{
write-host "$loopCount | $reportPath | $($DataSourceValue.DataModelDataSource.Username) | No Connection ID's found!"
}
}
Edit: Complete new script here, let's see if this works.
$ErrorActionPreference = 'Stop'
$loopCount = 1
$hash = #{
UseDefaultCredentials = $true
}
try
{
$hash.Uri = "$webPortalURL/api/v2.0/PowerBIReports"
$PBI_Reports_Array = (Invoke-RestMethod #hash).Value.Path
foreach ($reportPath in $PBI_Reports_Array)
{
$hash.Uri = "$webPortalURL/api/v2.0/PowerBIReports(path='$reportPath')/DataSources"
try
{
$DataSourceQueue = (Invoke-RestMethod #hash).Value
}
catch
{
Write-Warning ("$loopCount | ERROR! Can't connect to {0}" -f $hash.uri)
continue
}
foreach ($value in $DataSourceQueue)
{
if (-not $value.DataModelDataSource)
{
write-host "$loopCount | $reportPath | No DataModelDataSource"
continue
}
foreach($i in $value.DataModelDataSource)
{
if($i.Username)
{
$i.Username | foreach-object {
write-host "$loopCount | $reportPath | $_ | Retrieved!"
}
}
else
{
write-host "$loopCount | $reportPath | DataModelDataSource Found but No Usernames"
}
}
}
$loopCount++
}
}
catch
{
Write-Warning ("ERROR! Can't connect to {0}" -f $hash.uri)
}

Powershell foreach executing after pause [duplicate]

This question already has an answer here:
PowerShell output is crossing between functions
(1 answer)
Closed 2 years ago.
I created a script to read a list of names and translate them to IP by using "Resolve-DnsName".
I want users to just double click the script and read the output.
cls
Write-Host -ForegroundColor Green -BackgroundColor DarkRed "
______________________________
|X | Y | Z |
|-------+ ------+--------------|
|F2M1 | 49621 |esbsa9908ee43 |
|F2M2 | 47546 |esbsa009908jc1|
|F2M3 |xxxxxxx|7417191543366 |
|F2M4 | 47516 |esbsa9908ee18 |
|F2M5 | 47385 |7417191543116 |
|Capital| 86658 |7417191543242 |
|______________________________|"
Write-Host "`n`n"
$estacoes = #(
'esbsa9908ee43',
'esbsa009908jc1',
'7417191543366',
'esbsa9908ee18',
'7417191543116',
'7417191543242'
)
ForEach ($estacao in $estacoes){
Resolve-DnsName -ErrorAction Continue -Type A -QuickTimeout -Name $estacao | Select-Object Name,IpAddress
}
Pause
It happens that the "pause" command at the end is being executed before "foreach".
______________________________
|X | Y | Z |
|-------+ ------+--------------|
|F2M1 | 49621 |esbsa9908ee43 |
|F2M2 | 47546 |esbsa009908jc1|
|F2M3 |xxxxxxx|7417191543366 |
|F2M4 | 47516 |esbsa9908ee18 |
|F2M5 | 47385 |7417191543116 |
|Capital| 86658 |7417191543242 |
|______________________________|
Press Enter to continue...:
Name IPAddress
---- ---------
esbsa9908ee43 172.18.18.215
esbsa009908jc1 172.18.18.44
7417191543366 172.18.18.18
esbsa9908ee18 172.18.18.21
7417191543116 172.18.18.126
7417191543242 172.30.165.50
I can't seem to find a reason for that.
Can you guys help out?
It looks similar to the funny things that happen with pipe output and write-host output, where the write-host output will come out first even though it's run after the pipeline. Here's one way to change the order of the output:
$estacoes = echo microsoft.com yahoo.com
$result = ForEach ($estacao in $estacoes){
Resolve-DnsName -ErrorAction Continue -Type A -QuickTimeout -Name $estacao |
Select-Object Name,IpAddress
}
$result | format-table
pause
# didn't work
# 'Press Enter to continue...:'
# [void][console]::readkey()

Start-Job PowerShell two jobs getting created

I want to execute set of un-contained stored procedures, as jobs, to see if there are any issues in the code after making the database as contained database. After some time, I want to see whether the stored procedures are successful or not. But, I see that instead of one job for a stored procedure, two jobs are getting created. How to avoid the creation of two jobs?
$unContainedSProcs = Import-Csv -Path C:\DFLog\UnContained_Parameterless_SPs.csv
$batchSize = 50
$currentCompletedIdx = 0
$jobIds = #()
$scriptToExecute = {
Param($storedProcToExecute)
Invoke-Sqlcmd -Query "Exec $storedProcToExecute" -ServerInstance "<<ServerInstance>>" -Database "<<Database>>" -QueryTimeout 120
}
while ($currentCompletedIdx -le $unContainedSProcs.Length) {
for ($i= 0; $i -le $batchSize; $i++) {
$job = Start-Job -Name $unContainedSProcs[$i].UnContainedSProcName -ScriptBlock $scriptToExecute -ArgumentList $unContainedSProcs[$i].UnContainedSProcName
$jobIds += $job.Id
++$currentCompletedIdx
}
}
When I see the jobs list, using Get-Job, I see two jobs:
+----+--------------+------------------+-----------+---------------+--------------+------------------------------+
| Id | Name | PSJobTypeName | State | HasMoreData | Location | Command |
+----+--------------+------------------+-----------+---------------+--------------+------------------------------+
| 1 | dbo.SPName | BackgroundJob | Completed | True | localhost | param($storedProcToExe... |
| 41 | Job41 | BackgroundJob | Completed | True | localhost | param($storedProcToExe... |
+----+--------------+------------------+-----------+---------------+--------------+------------------------------+
I have figured it out. It is due to wrong definition of for loop condition. When the number of stored procedures is 1 and for loop is running twice: 0, 1. It is due to -le (less than or equal to) Length of $unContainedSProcs(1).
There is only one entry [0] in $unContainedSProcs. For the first run, it is fine. For second run, there is no entry[1] in $unContainedSProcs. So, empty job is getting created. After changing -le to -lt, there is only one job created.
From:
for ($i= 0; $i -le $batchSize; $i++) {
To:
for ($i= 0; $i -lt $batchSize; $i++) {

Partial/near match for name and/or username in Active Directory / Powershell

Our users sometimes gives us misspelled names/usernames and I would like to be able to search active directory for a near match, sorting by closest (any algorithm would be fine).
For example, if I try
Get-Aduser -Filter {GivenName -like "Jack"}
I can find the user Jack, but not if I use "Jacck" or "ack"
Is there a simple way to do this?
You can calculate the Levenshtein distance between the two strings and make sure it's under a certain threshold (probably 1 or 2). There is a powershell example here:
Levenshtein distance in powershell
Examples:
Jack and Jacck have an LD of 1.
Jack and ack have an LD of 1.
Palle and Havnefoged have an LD of 8.
Interesting question and answers. But a possible simpler solution is to search by more than one attribute as I would hope most people would spell one of their names properly :)
Get-ADUser -Filter {GivenName -like "FirstName" -or SurName -Like "SecondName"}
The Soundex algorithm is designed for just this situation. Here is some PowerShell code that might help:
Get-Soundex.ps1
OK, based on the great answers that I got (thanks #boxdog and #Palle Due) I am posting a more complete one.
Major source: https://github.com/gravejester/Communary.PASM - PowerShell Approximate String Matching. Great Module for this topic.
1) FuzzyMatchScore function
source: https://github.com/gravejester/Communary.PASM/tree/master/Functions
# download functions to the temp folder
$urls =
"https://raw.githubusercontent.com/gravejester/Communary.PASM/master/Functions/Get-CommonPrefix.ps1" ,
"https://raw.githubusercontent.com/gravejester/Communary.PASM/master/Functions/Get-LevenshteinDistance.ps1" ,
"https://raw.githubusercontent.com/gravejester/Communary.PASM/master/Functions/Get-LongestCommonSubstring.ps1" ,
"https://raw.githubusercontent.com/gravejester/Communary.PASM/master/Functions/Get-FuzzyMatchScore.ps1"
$paths = $urls | %{$_.split("\/")|select -last 1| %{"$env:TEMP\$_"}}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
for($i=0;$i -lt $urls.count;$i++){
Invoke-WebRequest -Uri $urls[$i] -OutFile $paths[$i]
}
# concatenating the functions so we don't have to deal with source permissions
foreach($path in $paths){
cat $path | Add-Content "$env:TEMP\Fuzzy_score_functions.ps1"
}
# to save for later, open the temp folder with: Invoke-Item $env:TEMP
# then copy "Fuzzy_score_functions.ps1" somewhere else
# source Fuzzy_score_functions.ps1
. "$env:TEMP\Fuzzy_score_functions.ps1"
Simple test:
Get-FuzzyMatchScore "a" "abc" # 98
Create a score function:
## start function
function get_score{
param($searchQuery,$searchData,$nlist,[switch]$levd)
if($nlist -eq $null){$nlist = 10}
$scores = foreach($string in $searchData){
Try{
if($levd){
$score = Get-LevenshteinDistance $searchQuery $string }
else{
$score = Get-FuzzyMatchScore -Search $searchQuery -String $string }
Write-Output (,([PSCustomObject][Ordered] #{
Score = $score
Result = $string
}))
$I = $searchData.indexof($string)/$searchData.count*100
$I = [math]::Round($I)
Write-Progress -Activity "Search in Progress" -Status "$I% Complete:" -PercentComplete $I
}Catch{Continue}
}
if($levd) { $scores | Sort-Object Score,Result |select -First $nlist }
else {$scores | Sort-Object Score,Result -Descending |select -First $nlist }
} ## end function
Examples
get_score "Karolin" #("Kathrin","Jane","John","Cameron")
# check the difference between Fuzzy and LevenshteinDistance mode
$names = "Ferris","Cameron","Sloane","Jeanie","Edward","Tom","Katie","Grace"
"Fuzzy"; get_score "Cam" $names
"Levenshtein"; get_score "Cam" $names -levd
Test the performance on a big dataset
## donload baby-names
$url = "https://github.com/hadley/data-baby-names/raw/master/baby-names.csv"
$output = "$env:TEMP\baby-names.csv"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri $url -OutFile $output
$babynames = import-csv "$env:TEMP\baby-names.csv"
$babynames.count # 258000 lines
$babynames[0..3] # year, name, percent, sex
$searchdata = $babynames.name[0..499]
$query = "Waren" # missing letter
"Fuzzy"; get_score $query $searchdata
"Levenshtein"; get_score $query $searchdata -levd
$query = "Jon" # missing letter
"Fuzzy"; get_score $query $searchdata
"Levenshtein"; get_score $query $searchdata -levd
$query = "Howie" # lookalike
"Fuzzy"; get_score $query $searchdata;
"Levenshtein"; get_score $query $searchdata -levd
Test
$query = "John"
$res = for($i=1;$i -le 10;$i++){
$searchdata = $babynames.name[0..($i*100-1)]
$meas = measure-command{$res = get_score $query $searchdata}
write-host $i
Write-Output (,([PSCustomObject][Ordered] #{
N = $i*100
MS = $meas.Milliseconds
MS_per_line = [math]::Round($meas.Milliseconds/$searchdata.Count,2)
}))
}
$res
+------+-----+-------------+
| N | MS | MS_per_line |
| - | -- | ----------- |
| 100 | 696 | 6.96 |
| 200 | 544 | 2.72 |
| 300 | 336 | 1.12 |
| 400 | 6 | 0.02 |
| 500 | 718 | 1.44 |
| 600 | 452 | 0.75 |
| 700 | 224 | 0.32 |
| 800 | 912 | 1.14 |
| 900 | 718 | 0.8 |
| 1000 | 417 | 0.42 |
+------+-----+-------------+
These times are quite crazy, if anyone understand why please comment on it.
2) Generate a table of Names from Active Directory
The best way to do this depends on the organization of the AD. Here we have many OUs, but common users will be in Users and DisabledUsers. Also Domain and DC will be different (I'm changing ours here to <domain> and <DC>).
# One way to get a List of OUs
Get-ADOrganizationalUnit -Filter * -Properties CanonicalName |
Select-Object -Property CanonicalName
then you can use Where-Object -FilterScript {} to filter per OU
# example, saving on the temp folder
Get-ADUser -f * |
Where-Object -FilterScript {
($_.DistinguishedName -match "CN=\w*,OU=DisabledUsers,DC=<domain>,DC=<DC>" -or
$_.DistinguishedName -match "CN=\w*,OU=Users,DC=<domain>,DC=<DC>") -and
$_.GivenName -ne $null #remove users without givenname, like test users
} |
select #{n="Fullname";e={$_.GivenName+" "+$_.Surname}},
GivenName,Surname,SamAccountName |
Export-CSV -Path "$env:TEMP\all_Users.csv" -NoTypeInformation
# you can open the file to inspect
Invoke-Item "$env:TEMP\all_Users.csv"
# import
$allusers = Import-Csv "$env:TEMP\all_Users.csv"
$allusers.Count # number of lines
Usage:
get_score "Jane Done" $allusers.fullname 15 # return the 15 first
get_score "jdoe" $allusers.samaccountname 15

Powershell Shutdown VM Script

I have a script that can shutdown vms based on names entered into an array.
That part works fine, but the next part after the shutdown initiates is supposed to wait for a period and notify how many vms it is waiting to shut down before it moves on to another phase of shutting down other vms.
I am not getting any vms counted out of the array. Here is the code for a particular phase:
$waittime = 5 #Seconds
#Create Phase 1 Array
[Array] $PHASE1 = "TestSvr2008"
# For each of the VMs on the Hyperv hosts that are powered on
Foreach ($VM in ($PHASE1 | %{ Get-VM | Where { $_.State -eq "Running" }})){
# Shutdown the guest cleanly
$VM | Stop-VM -Confirm:$false }
# Set the amount of time to wait before assuming the remaining powered on guests are stuck
$waittime = 120 #Seconds
$Time = (Get-Date).TimeofDay
do {
# Wait for the VMs to be Shutdown cleanly
sleep 1.0
$timeleft = $waittime - ($Newtime.seconds)
$numvms = ($PHASE1 | %{ Get-VM | Where { $_.$VM -eq $PHASE1 }}).Count
Write "Waiting for shutdown of $numvms VMs or until $timeleft seconds"
$Newtime = (Get-Date).TimeofDay - $Time
} until ((#($PHASE1 | %{ Get-VM | Where { $_.$VM -eq $PHASE1 }}).Count) -eq 0 -or ($Newtime).Seconds -ge $waittime)
Thanks
This line in do and until blocks has issues:
$numvms = ($PHASE1 | %{ Get-VM | Where { $_.$VM -eq $PHASE1 }}).Count
$_.$VM is typo, it should be $_.VM
$_.VM -eq $PHASE1 tries to check if VM name is equal to array. Comparisons do not work that way.
ForeEach-Object is unnecessary
Get-Vm accepts array of VM names by pipeline or directly as Name parameter. So you can do this:
$numvms = ($PHASE1 | Get-VM).Count
or
$numvms = (Get-VM -Name $PHASE1).Count