I want to write a simple If Statement which checks if an Process Exists.
If it Exists, something should start.
Like this, but working.. ;)
If ((Get-Process -Name Tvnserver.exe ) -eq $True)
{
Stop-Process tnvserver
Stop-Service tvnserver
Uninstall...
Install another Piece of Software
}
Else
{
do nothing
}
Thanks
Get-Process doesn't return a boolean value and the process name is listed without extension, that's why your code doesn't work. Drop the extension and either check if the result is $null as Musaab Al-Okaidi suggested, or cast the result to a boolean value:
if ( [bool](Get-Process Tvnserver -EA SilentlyContinue) ) {
# do some
} else {
# do other
}
If you don't want the script to do anything in case the process isn't running: just omit the else branch.
This will evaluate to true if the process doesn't exist:
(Get-Process -name Tvnserver.exe -ErrorAction SilentlyContinue) -eq $null
or if you want to change it you can negate the statement as follows:
-not ( $(Get-Process -name Tvnserver.exe -ErrorAction SilentlyContinue) -eq $null )
It's important to have have -ErrorAction SilentlyContinue to avoid any errors been thrown if a process doesn't exist.
Related
I'm trying to test a powershell script that finds a specific event viewer task that excludes a certain case. For some reason, it's printing the event created time as empty. I think this is why it's falling into a wrong case. Why is this created time empty? This is an example for this website, so Init variable name doesn't quite make sense below, with chromoting.
#Look for crash within 150 hours of boot, and with Init within 7 minutes before that
$today=[system.datetime](Get-Date)
$startTime=$today.AddHours(-150)
$events = Get-WinEvent -FilterHashtable #{LogName='Application';ProviderName='SlotBroker';StartTime=$($startTime);EndTime=$($today);} -ErrorAction SilentlyContinue
if($events -ne $null)
{
foreach ($event in $events)
{
$crashOccurredTime=$event.TimeCreated
$lookForInitStart = $event.TimeCreated.AddMinutes(-7)
$eventInits = {Get-WinEvent -FilterHashtable #{LogName='Application';ProviderName='chromoting';StartTime=$lookForInitStart;EndTime=$crashOccurredTime;} -ErrorAction SilentlyContinue
}
if($eventInits -ne $null)
{
foreach ($eventInit in $eventInits)
{
#check that didn't have Terminate in that timeframe because we don't want this case
#look for exclude case of Terminate between Init and crash
$initTime = $eventInit.TimeCreated #chromoting
Write-Host "initTime $($initTime)" ##this is blank time
$eventInitTerminate = {Get-WinEvent -FilterHashtable #{LogName='Application';ProviderName='AppMgr';StartTime=$initTime;EndTime=$crashOccurredTime;} -ErrorAction SilentlyContinue | Where-Object {(-PipelineVariable Message -Match 'Terminate function called') -or (-PipelineVariable Message -Match 'Terminate function returned')}
}
if($eventInitTerminate -ne $null)
{ #it always falls in here no matter if it should or not.
Write-Host "Found application.exe after Init with Terminate TimeCreatedCrash $($event.TimeCreated) ProviderName $($event.ProviderName) Message $($event.Message) TerminateTime $($eventInitTerminate.TimeCreated)"
}
else #this will print
{
Write-Host "Found application.exe after Init without Terminate TimeCreated $($event.TimeCreated) ProviderName $($event.ProviderName) Message $($event.Message) InitTime $($eventInit.TimeCreated)"
}
} #foreach
}
}
Looking at the event log, I see this:
Error 8/11/2022 9:43 SlotBroker
Information 8/11/2022 9:37 chromoting
Information 8/11/2022 936 AlarmSoundHelper
This is a test case and should be falling into #this will print, but it prints the above case. I think it's because of the time printing blank, so it finds the Terminate elsewhere in the event log. Why is that time not printing out right? I need the time to see if I need to notify me of the event log or not.
The purpose of this script is to avoid events with Terminate between SlotBroker and chromoting. As you can see, it's not in this case, but falls into that if statement like it found events. We don't have PowerShellISE on this computer with the eventLog, so I can't step through.
When I was working on the script in PowerShellISE on my laptop, it seemed like $eventInit might not know what TimeCreated is, but it's not causing an error. I'm not sure how to get that TimeCreated.
Update:
I added this below the $lookForInitStart and it prints ok
Write-Host "lookForInitStart $($lookForInitStart)"
prints
lookForInitStart 8/11/2022 09:36
I'm unsure why the initTime is printing blank.
I got the $initTime to not be empty with this line and used that for all TimeCreated to make sure they were correct.
$initTime = $eventInit | Select-Object -Expand TimeCreated #chromoting
But it's still falling into the $eventInitTerminate block, even though it doesn't have the Terminate events in there.
That would be helpful if someone else knows why it's falling into the "Found application.exe after Init with Terminate..." printout, and I would accept that answer.
I got the $initTime to not be empty with this line and used that for all TimeCreated to make sure they were correct.
$initTime = $eventInit | Select-Object -Expand TimeCreated #chromoting
For the Terminate block, I changed this to fix it (simplified the match for terminate):
$today=[system.datetime](Get-Date)
$startTime=$today.AddHours(-135)
write-host "startTime $($startTime)"
$events = Get-WinEvent -FilterHashtable #{LogName='Application';ProviderName='SlotBroker';StartTime=$($startTime);EndTime=$($today);} -ErrorAction SilentlyContinue
if($events -ne $null)
{
foreach ($event in $events)
{
$crashOccurredTime=$event | Select-Object -Expand TimeCreated
write-host "crashOccurredTime $($crashOccurredTime)"
$lookForInitStart = $event.TimeCreated.AddMinutes(-7)
Write-Host "lookForInitStart $($lookForInitStart)"
$eventInits = Get-WinEvent -FilterHashtable #{LogName='Application';ProviderName='chromoting';StartTime=$lookForInitStart;EndTime=$crashOccurredTime;} -ErrorAction SilentlyContinue
if($eventInits -ne $null)
{
foreach ($eventInit in $eventInits)
{
#check that didn't have Terminate in that timeframe because we don't want this case
#look for exclude case of Terminate between Init and crash
$initTime = $eventInit | Select-Object -Expand TimeCreated #chromoting
Write-Host "initTime $($initTime)"
$eventInitTerminate = Get-WinEvent -FilterHashtable #{LogName='Application';ProviderName='AppMgr';StartTime=$initTime;EndTime=$crashOccurredTime;} -ErrorAction SilentlyContinue | Where-Object -PipelineVariable Message -Match 'Terminate'
if($eventInitTerminate -ne $null)
{
Write-Host "*****Found application.exe after Init with Terminate TimeCreatedCrash $($event.TimeCreated) ProviderName $($event.ProviderName) Message $($event.Message) TerminateTime $($eventInitTerminate.TimeCreated)*****"
}
else #this will print
{
Write-Host "***Found application.exe after Init without Terminate TimeCreated $($event.TimeCreated) ProviderName $($event.ProviderName) Message $($event.Message) InitTime $($eventInit.TimeCreated)***"
}
} #foreach
}
else
{
Write-Host "No application Crash found after Init in 4 hours preceding shell command"
}
}
}#if
else
{
Write-Host "no events found that meet criteria of crash after init"
}
I am trying to modify a variable within Invoke-Command in order to get out of a loop, however I'm having trouble doing that.
In the sample script below, I'm connecting to a host, grabbing information from NICs that are Up and saving the output to a file (Baseline). Then on my next iteration I will keep grabbing the same info and then compare Test file to Baseline file.
From a different shell, I've connected to the same server and disabled one of the NICs to force Compare-Object to find a difference.
Once a difference is found, I need to get out of the loop, however I cannot find a way to update the local variable $test_condition. I've tried multiple things, from Break, Return, $variable:global, $variable:script, but nothing worked so far.
$hostname = "server1"
$test_condition = $false
do {
Invoke-Command -ComputerName $hostname -Credential $credential -ScriptBlock{
$path = Test-Path -LiteralPath C:\Temp\"network_list_$using:hostname-Baseline.txt"
if ($path -eq $false) {
Get-NetAdapter | Where-Object Status -EQ "Up" | Out-File -FilePath (New-Item C:\Temp\"network_list_$using:hostname-Baseline.txt" -Force)
} else {
Get-NetAdapter | Where-Object Status -EQ "Up" | Out-File -FilePath C:\Temp\"network_list_$using:hostname-Test.txt"
$objects = #{
ReferenceObject = (Get-Content C:\Temp\"network_list_$using:hostname-Baseline.txt")
DifferenceObject = (Get-Content C:\Temp\"network_list_$using:hostname-Test.txt")
}
$test_condition = (Compare-Object #objects).SideIndicator -ccontains "<="
$test_condition #this is returning True <-----
}
}
} until ($test_condition -eq $true)
Any tips? What am I doing wrong?
TIA,
ftex
You can pass variables into a remote script block with the $Using:VarName scope modifier, but you can't use typical $Global: or $Script to modify anything in the calling scope. In this scenario the calling scope isn't the parent scope. The code is technically running in a new session on the remote system and $Global: would refer to that session's global scope.
For example:
$var = "something"
Invoke-Command -ComputerName MyComuter -ScriptBlock { $Global:var = "else"; $var}
The remote session will output "else". However, after return in the calling session $var will output "something" remaining unchanged despite the assignment in the remote session.
Based on #SantiagoSquarzon's comment place the assignment inside the Do loop with a few other modifications:
$hostname = "server1"
do {
$test_condition = $false
$test_condition =
Invoke-Command -ComputerName $hostname -Credential $credential -ScriptBlock{
$path = Test-Path -LiteralPath C:\Temp\"network_list_$using:hostname-Baseline.txt"
if ($path -eq $false) {
Get-NetAdapter | Where-Object Status -eq "Up" | Out-File -FilePath (New-Item C:\Temp\"network_list_$using:hostname-Baseline.txt" -Force)
} else {
Get-NetAdapter | Where-Object Status -eq "Up" | Out-File -FilePath C:\Temp\"network_list_$using:hostname-Test.txt"
$objects = #{
ReferenceObject = (Get-Content C:\Temp\"network_list_$using:hostname-Baseline.txt")
DifferenceObject = (Get-Content C:\Temp\"network_list_$using:hostname-Test.txt")
}
(Compare-Object #objects).SideIndicator -contains "<=" # this is returning True <-----
}
}
} until ($test_condition -eq $true)
I don't know why you were using -ccontains considering "<=" has no casing implications. Also it's very unusual to capitalize operators.
Notice there's no explicit return or assignment. PowerShell will emit the Boolean result of the comparison and that will be returned from the remote session and end up assigned to the $test_condition variable.
An aside:
I'm not sure why we want to use -contains at all. Admittedly it'll work fine in this case, however, it may lead you astray elsewhere. -contains is a collection containment operator and not really meant for testing the presence of one string within another. The literal meaning of "contains" makes for an implicitly attractive hazard, as demonstrated in this recent question.
In short it's easy to confuse the meaning, purpose and behavior on -contains.
This "<=" -contains "<=" will return "true" as expected, however "<==" -contains "<=" will return "false" even though the left string literally does contain the right string.
The answer, to the aforementioned question says much the same. My addendum answer offers a some additional insight for the particular problem and how different operators can be circumstantially applied.
So, as a matter of practice for this case wrap the Compare-Object command in the array sub-expression operator like:
#( (Compare-Object #objects).SideIndicator ) -contains "<="
Given the particulars, this strikes me as the least intrusive way to implement such a loosely stated best practice.
I am relatively new to PowerShell and cannot understand why my original attempts failed. I am attempting to validate the bit version of MS Office and perform actions off that. For whatever reason the strings were not comparing properly until I found a solution in the actual question here. Help understanding the difference between the two examples below would be much appreciated.
First attempt:
$getMSBitVersion= Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" | Select-Object -Property Platform
if( $getMSBitVersion -eq "x64" ){
Write-Host "true"
} else {
Write-Host "false"
}
Working solution:
$getMSBitVersion= (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" -Name Platform).Platform
if( $getMSBitVersion -eq "x64" ){
Write-Host "true"
} else {
Write-Host "false"
}
My assumption is the first is outputting an object instead of string and thus the comparison cannot be done. If this is the case, is the working solution the only way/best way to do this?
Thank you Mathias and Abraham.
From what I gather, the following are confirmed methods on how to make the desired comparison.
1: This will scope into the object property of "Platform" by using dot notation and return the string value instead of the whole object.
$getMSBitVersion= (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" -Name Platform).Platform
if( $getMSBitVersion -eq "x64" ){
Write-Host "true"
} else {
Write-Host "false"
}
2: This will take all the properties of the Path and pass through to Select-Object. Select-Object will take and expand the "Property" object and return it as a string.
$getMSBitVersion= Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" | Select-Object -ExpandProperty Platform
if( $getMSBitVersion -eq "x64" ){
Write-Host "true"
} else {
Write-Host "false"
}
I was unable to get this solution to function correctly, but should, in theory, work.
3: This, in theory, should work, but the two objects are recognized differently and do not compare as intended.
$getMSBitVersion= Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" | Select-Object -Property Platform
$test= #{Platform="x64"}
if( Compare-Object $getMSBitVersion $test ){
Write-Host "true"
} else {
Write-Host "false"
}
I've found myself having to write wrappers around powershell's Remove-VMSnapshot and Checkpoint-VM. The docs make no mention of it, but based on the Write-Host in both of the code snippets below executing, checkpoints are not fully deleted/created after the MS provided cmdlets. I hit this when trying to restore to a checkpoint by name immediately after creating it yielded an error.
Has anyone else encountered this? Thoughts on better ways to handle it? Tricks to prevent any of my code from calling the MS cmdlets directly?
function Remove-VMSnapshots-Sync
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)][object]$VM,
[Parameter(Mandatory=$True)][string]$CheckpointName,
)
$matchingSnapshots = #(Get-VMSnapshot $VM | Where-Object {$_.Name -eq $CheckpointName})
$matchingSnapshots | Remove-VMSnapshot
do
{
$matchingSnapshots = #(Get-VMSnapshot $VM | Where-Object {$_.Name -eq $CheckpointName})
$stillThere = $matchingSnapshots.length -gt 0
if ($stillThere)
{
Write-Host 'sleeping to try to let snapshot disappear'
start-sleep -s 1
}
} while ($stillThere)
}
function Checkpoint-VM-Sync
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)][object]$VM,
[Parameter(Mandatory=$True)][string]$CheckpointName
)
$checkpoint = Checkpoint-VM -VM $VM -SnapshotName $CheckpointName -PassThru
$checkpoint | Write-Host
while (-not (#(Get-VMSnapshot $VM | Select -ExpandProperty Id)).Contains($checkpoint.Id))
{
Write-Host 'waiting for checkpoint to be in list'
Get-VMSnapshot $VM | Write-Host
start-sleep -s 1
}
}
Had a similar issue, see the answer in Can I override a Powershell native cmdlet ... it shows you how easily it is to override commands.
You need to add it into your profile (only for you), or add it to the script (for every one that runs the script), it depends on your situation.
I've googled and not found anything useful to me.
I have 4 msi files I want to install but would like to check if some of it is installed on the computer.
Example:
check if program 1 is installed, if not install it and go to and install program 2.
However if it's not installed, install it and go to program 2 and do the same test there.
Execute-MSI -Action Install -Path "$dirFiles\Program1"
Execute-MSI -Action Install -Path "$dirFiles\Program2"
Execute-MSI -Action Install -Path "$dirFiles\Program3"
Execute-MSI -Action Install -Path "$dirFiles\Program4"
If you know the GUID, you could test path the uninstall key. Also don't forget that if your OS is 64 bit, there will be the same key in WOW6432Node for 32 bit apps.
$uninstallkey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\"
$uninstall32key = "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\"
#Example 64-bit app
$app1guid = "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"
if (!(Test-Path "$uninstallkey\$app1guid)) {Execute-MSI -Action Install -Path "$dirFiles\Program1"}
#Example 32-bit app
$app2guid = "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"
if (!(Test-Path "$uninstall32key\$app2guid)) {Execute-MSI -Action Install -Path "$dirFiles\Program1"}
Without knowing anything about your applications, there are two easy options I can think of.
1) Read a log file, looking for a pattern / string - check 6 times (configure as needed) and fail if it doesn't complete. If it does, move on to the next install and do the same check
$LOGFILE = 'C:\Somewhere.log'
$Complete = 'no'
$Counter = 1
$max = 6
Start-Sleep 10
DO {
$Check = SELECT-STRING -pattern 'status: 0.' -path $logfile -quiet
write-host $Check
If (($Check) -eq $True) {
Set-Variable -name Complete -Value "yes"
}
Else {Set-Variable -name Complete -Value 'no'}
Write-host $Counter
Start-Sleep 20
$Counter++
}
while ($Complete -eq 'no')
If (($Counter) -eq $max) {
Throw 'Installation failed, check the error log'
}
Option 2)
If you know what directories it creates or even a file count, can do something like the above with count instead of a file read
$PRDIR = "D:\Folder"
If (($PRDIR.Count) -gt 2)
{
Do something
}
Else
{
Do something else
{
Hope that helps!
R
You can use WMI to check if your MSIs are installed or not. Example:
$products = Get-WmiObject -Class win32_product | Where-Object { $_.Name -like "*someName*" } | Select-Object *
You might extend the Where-Object clause with additional patterns to query for (e.g. via $_.Name -like "*Sw1*" -or $_.Name -like "*Sw2*" ). $products should be an array including the findings, which you can use to check if the requested SW is installed or not. Example:
PS> $found = $results.Where({ $_.Name -like "*sw1*"})
PS> if ($found) { Write-Host "Found" }
Hope that helps