I'm new to powershell and while practicing the examples for loops, I came across a problem that I couldn't solve.
I want to call a notification about the status of a process running in the system through a loop
However, in the code, only the alarm of the process executed first among Taskmgr or Calculator is called by Wait-Process.
I mean, Wait-Process by Sequential Execution makes notification invocation by loop no longer possible
below is my code
do{
Start-Sleep -s 3
$Calculator = Get-Process -Name 'Calculator'
$Taskmgr = Get-Process -Name 'Taskmgr'
if ($Calculator)
{
Show-Notification -ToastTitle 'Calculator is open.'
Wait-Process -name 'Calculator'
Show-Notification -ToastTitle 'Calculator is closed.'
}
if ($Taskmgr)
{
Show-Notification -ToastTitle 'Taskmgr is open.'
Wait-Process -name 'Taskmgr'
Show-Notification -ToastTitle 'Taskmgr is closed.'
}
} while (1 -eq 1)
Since I am Japanese, I am not familiar with English, so I am using a translator. thank you.
I'm still not good enough skill to recreate the code. sorry
Wait-Process blocks the thread indefinitely without the -Timeout parameter but also, while using this parameter if it times out you would get an error and the cmdlet is not particularly useful for this use case.
You could achieve this by using only Get-Process with -ErrorAction SilentlyContinue to avoid errors when the processes are not running and adding else conditions:
while ($true) {
Start-Sleep -s 3
$calc = Get-Process -Name CalculatorApp -ErrorAction SilentlyContinue
$taskmgr = Get-Process -Name Taskmgr -ErrorAction SilentlyContinue
# if calc is running
if ($calc) {
# and there was no notification before
if (-not $calcOpen) {
# display notification
'Calculator is opened.'
# and set a reminder to not display more popups
$calcOpen = $true
}
}
# if calc was not running
else {
# and was running before
if ($calcOpen) {
# display notification
'Calculator is closed.'
# and set a reminder that calc has been closed
$calcOpen = $false
}
}
# same logic applies here
if ($taskmgr) {
if (-not $taskmgrOpen) {
'Taskmgr is opened.'
$taskmgrOpen = $true
}
}
else {
if ($taskmgrOpen) {
'Taskmgr is closed.'
$taskmgrOpen = $false
}
}
}
Related
I need to kill the process if start time is less than 2 hours.
I have written the below cmdlet to find out the starttime but how to find out if is it less than 2 hours:
get-process process1 | select starttime
There is also a possibility that on some hosts process1 is not running. so I need to check first if the process1 is running
You can use a loop of your choice, in this example ForEach-Object in addition to an if condition to check if the StartTime value is lower than 2 hours.
If you need to check first is the process is running then you would need to get all processes and filter by the process name you're looking for. Then check if the returned value from Where-Object is $null or not.
$procName = 'myprocess'
$process = Get-Process | Where-Object Name -EQ $procName
if(-not $process) {
Write-Warning "$procName not found!"
}
else {
$process | ForEach-Object {
if($_.StartTime -lt [datetime]::Now.AddHours(-2)) {
try {
'Attempting to stop {0}' -f $_.Name
Stop-Process $_ -Force
'{0} successfully stopped.' -f $_.Name
}
catch {
Write-Warning $_.Exception.Message
}
}
}
}
I have this script to check for file creation and give me a balloon notification.
How do I get this to run without having a powershell console/window open?
Ideally would like to be able to run this from task scheduler at log on, and run continuously - which is the other problem, it seems to stop running after a while and I'm not sure why.
Any solutions?
Note. I have strung this together from snippets copied off chats, I am not a programmer.
### FOLDER WATCHER
$filewatcher = New-Object System.IO.FileSystemWatcher
#SET FOLDER TO WATCH
$filewatcher.Path = "\\dc1\Harrows CNC_Full Access\"
$filewatcher.Filter = "*.pg_"
#include subdirectories
$filewatcher.IncludeSubdirectories = $true
$filewatcher.EnableRaisingEvents = $true
###Define Actions after event is raised
$writeaction = {
$path = $Event.SourceEventArgs.FullPath
$filename = [System.IO.Path]::GetFileNameWithoutExtension($path)
$ownerwithdomain = Get-Acl $path | Select-Object -ExpandProperty Owner
$domain,$owner = $ownerwithdomain.split('\')
Add-Type -AssemblyName System.Windows.Forms
$global:balloon = New-Object System.Windows.Forms.NotifyIcon
Get-Member -InputObject $Global:balloon
[void](Register-ObjectEvent -InputObject $balloon -EventName MouseDoubleClick -SourceIdentifier IconClicked -Action {
#Perform cleanup actions on balloon tip
$global:balloon.dispose()
Unregister-Event -SourceIdentifier IconClicked
Remove-Job -Name IconClicked
Remove-Variable -Name balloon -Scope Global
})
$path = (Get-Process -id $pid).Path
$balloon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path)
[System.Windows.Forms.ToolTipIcon] | Get-Member -Static -Type Property
$balloon.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Info
$balloon.BalloonTipText = $owner, ' just opened ', $filename
$balloon.BalloonTipTitle = "Attention:"
$balloon.Visible = $true
$balloon.ShowBalloonTip(1000)
}
###Decide which events should be watched
Register-ObjectEvent $filewatcher “Created” -Action $writeaction
while ($true) {sleep 5}
In the Task Scheduler you can start the script with with:
powershell.exe -windowstyle hidden -File "path to script"
There you can also make a trigger, that it should be executed at logon. In each Task there is an option, that the task ends after a specific amount of time. You can disable that.
Using the basic construct:
try
{
Do-Something
}
catch
{
Write-Output "Something threw an exception"
}
Is it possible to keep trying the Do-Something until it succeeds? Perhaps using a while loopp like this:
$Timeout = 60
$timer = [Diagnostics.Stopwatch]::StartNew()
$t = 0
while (($timer.Elapsed.TotalSeconds -lt $Timeout) -and ($t -ne 1))) {
Start-Sleep -Seconds 1
try
{
Do-Something
$t = 1
}
catch
{
Write-Output "Something threw an exception"
}
}
$timer.Stop()
Here I use a timer to make sure PowerShell doesn't run indefinitely.
It should keep trying until the try succeeds and $t = 1 is executed. However, it fails in about 2 seconds. Please help.
More specifically the Do-Something is:
(Get-Process -Name FineReader).MainWindowHandle
I'd like the code to keep trying until FineReader exists and it can get the MainWindowHandle.
You can use break keyword.
# Set the erroracton preference to stop when an error occurs,
$ErrorActionPreferenceBak = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
While($True){
try{
Do-Something
break
}
catch{
Write-Output "Something failed"
Start-Sleep -Seconds 1 # wait for a seconds before next attempt.
}
finally{
#Reset the erroracton preference
$ErrorActionPreference = $ErrorActionPreferenceBak
}
}
Your Do-Something should be called with the switch -ErrorAction Stop in order to issue a terminating exception that can be caught by try
For that, you also need to bind your function as a CmdLet. For example:
function DoSomething {
[CmdLetBinding()]
Param(
)
# Your code
}
And then call your function with the -ErrorAction Stop switch:
try {
Do-Something -ErrorAction Stop
}
If your DoSomething is not a function but rather an existing powershell CmdLet, then ... you guessed it, just call it with -ErrorAction Stop
You can learn more about the try/catch/finally in powershell here
Assume that you have a function failing in 9 out of 10 calls:
function Get-RandomFail{
$value = [Random]::new().Next(10);
if ($value -ne 5) { throw }
return $value
}
And you want to limit time window, you can use following:
function Try-Invoke{
[CmdletBinding()]
param(
$Action,
$MaxSeconds,
$Delay=1
)
$timeout = [timespan]::FromSeconds($MaxSeconds)
$start = [DateTime]::Now
do {
try {
return &$Action
} catch {
Write-Verbose "Error"
}
Start-Sleep -Seconds $Delay
} until (([DateTime]::Now - $start) -gt $timeout)
throw
}
$result = Try-Invoke {Get-RandomFail} -MaxSeconds 5 -Verbose
Get-RandomFail will be invoked until no error occurs or until time is over. You can also use Delay argument to modify sleep time after every unsuccesfull Get-RandomFail invocation.
I am building a deployment script to install software on a new device using a ppkg file.
The script looks at which drive is the USB drive and copies the software over to the local temp folder and runs them according to a set of variables as shown below.
What I am struggling to do is simplify the script so I am not repeating code 7 times down the page, I want to just run a loop 7 times to pull in the needed software. I tried an array but I think I am not quite understanding it completely.
This is my script so far with the repeating code:
#SOE application Variables
#applcation1 CM_client
$app1name = "Config Manager Client 1706"
$app1skip = "no"
$app1path = "$env:SystemDrive\temp\soe\application_installs\app1\CM_client_inst_1706\"
$app1runcommand = "clientx64.bat"
$app1arguments = ""
#applcation2
$app2name = "Office 2016 Pro Plus"
$app2skip = "no"
$app2path = "$env:SystemDrive\temp\soe\application_installs\app2\O2016\"
$app2runcommand = "setup.exe"
$app2arguments = "/configure configuration.xml"
#log Folder
$datetime = Get-Date -format "yyyy.MM.dd-HH.mm.ss"
$logpath = "$env:ALLUSERSPROFILE\SOEInst_ppkg\$datetime"
New-Item -Path $logpath -ItemType Directory -ErrorAction SilentlyContinue
#Transcript Start
Start-Transcript -Path $logpath\SOE-app-installer-ppkg-$datetime.log
#Timer Function
$pkgremovetime = Get-Date -format "HH:mm:ss"
write-host "Script Start Time - $pkgremovetime"
#Find USB Drive
Write-host Discovering USB Drive
$drives = (GET-WMIOBJECT –query “SELECT * from win32_logicaldisk").DeviceID
foreach ($drive in $drives) {
$usbdrive = (dir $drive USBIMG.FILE | Select-Object -Unique "USBIMG.FILE")
if ($usbdrive -match "USBIMG.FILE*") {
$datadrive = $drive
}
}
Write-host Found $datadrive is the USB drive
#Copy Applications to Local Drive
Write-Host Creating Installer Folder
New-Item -Path $env:SystemDrive\temp\SOE -ItemType Directory
Copy-Item $datadrive\application_installs $env:SystemDrive\temp\soe -Recurse -Verbose
#Install Applications
#Application 1
if ($app1skip -eq "no") {
if ($app1arguments) { #Arguments Variable Populated
Write-Host Installing Applcation 1 `($app1name`)
$app1 = Start-Process -Wait -FilePath $app1path$app1runcommand -ErrorAction Continue -ArgumentList $app1arguments -WindowStyle Normal
if ($app1.ExitCode -eq "0") {
Write-Host $app1name Installed ok
} Else {
Write-host $app1name install exited with code $app1.ExitCode
}
}
}Else { #Argurments Variable Empty
Write-Host Installing Applcation 1 `($app1name`)
$app1 = Start-Process -Wait -FilePath $app1path$app1runcommand -ErrorAction Continue -WindowStyle Normal
if ($app1.ExitCode -eq "0") {
Write-Host $app1name Installed ok
} Else {
Write-host $app1name install exited with code $app1.ExitCode
}
}
#Application 2
if ($app2skip -eq "no") {
if ($app2arguments) { #Arguments Variable Populated
Write-Host Installing Applcation 2 `($app2name`)
$app2 = Start-Process -Wait -FilePath $app2path$app2runcommand -ErrorAction Continue -ArgumentList $app2arguments -WindowStyle Normal
if ($app2.ExitCode -eq "0") {
Write-Host $app2name Installed ok
} Else {
Write-host $app2name install exited with code $app2.ExitCode
}
}
}Else { #Argurments Variable Empty
Write-Host Installing Applcation 2 `($app2name`)
$app2 = Start-Process -Wait -FilePath $app2path$app2runcommand -ErrorAction Continue -WindowStyle Normal
if ($app2.ExitCode -eq "0") {
Write-Host $app2name Installed ok
} Else {
Write-host $app2name install exited with code $app2.ExitCode
}
}
#cleanup
Remove-Item $env:SystemDrive\temp\soe -Recurse -Force -Verbose
#get end time
$pkgremovetime_end = Get-Date -format "HH:mm:ss"
#calculate time difference
$timetaken = New-TimeSpan $pkgremovetime $pkgremovetime_end
if ($timetaken.Seconds -lt 0) {
$Hrs = ($timetaken.Hours) + 23
$Mins = ($timetaken.Minutes) + 59
$Secs = ($timetaken.Seconds) + 59 }
else {
$Hrs = $timetaken.Hours
$Mins = $timetaken.Minutes
$Secs = $timetaken.Seconds }
$Difference = '{0:00}:{1:00}:{2:00}' -f $Hrs,$Mins,$Secs
#log time difference
write-host "Script End Time - $pkgremovetime_end"
Write-Host "Total time taken $difference"
#Transcript End
Stop-Transcript
I suggest you make a function which takes in the variables. I did a quick comparison of your installation codes and something like this should work
function installApplication{
Param($skip, $arguments, $name, $path, $runcommand)
if ($skip -eq "no"){
if ($arguments){
write-host "Installing Application $appname"
$app = Start-Process -Wait -FilePath $path$runcommand -ErrorAction....
if($app.ExitCode -eq "0"){
....
....
}
and so on, You can then call the function using
installApplication $app1skip $app1arguments $app1name $app1path $app1runcommand
installApplication $app2skip $app2arguments $app2name $app2path $app1runcommand
Your input arguments will replace the function parameters in the order you pass them in, or you can use -skip $app1skip to assign the parameters.
If your repeating the same code too many times, I suggest throwing it into something like diffchecker, put the code into a function and replace all the differences with variables.
You can see your code here https://www.diffchecker.com/FxAIdD6g (1 Day only)
I found this gui progress bar on a different site. The way that it explains, should solve my problem which when the job is running not let the gui freeze.
However, since I'm dealing with batch files (installing applications) I need to do a foreach app and install them one by one and not let the gui freeze.
Here is the link to the site link
EDIT: UPDATE the SCRIPT. so far this works but all batch files install at the same time which is causing them to fail. I have more applications but for testing I just added 3.
I am only assuming that the $job is not passing its status to "updatescript" and "completedscript"
$Appname = #("Adobe_FlashPlayer", "Acrobat_Reader, "Microsoft_RDP_8.1")
$formJobProgress_Load={
#TODO: Initialize Form Controls here
$timer1.Interval = 1000
$timer1.Tag = 0
$timer1.Start()
[System.Windows.Forms.Application]::DoEvents()
}
$formMain_FormClosed=[System.Windows.Forms.FormClosedEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.FormClosedEventArgs]
#Stop any pending jobs
Stop-JobTracker
}
$timerJobTracker_Tick={
Update-JobTracker
}
#region Job Tracker
$JobTrackerList = New-Object System.Collections.ArrayList
function Add-JobTracker
{
Param (
#[ValidateNotNull()]
#[Parameter(Mandatory = $true)]
[string]$Name,
#[ValidateNotNull()]
#[Parameter(Mandatory = $true)]
[ScriptBlock]$CompletedScript,
[ScriptBlock]$UpdateScript,
[ScriptBlock]$JobScript,
$ArgumentList = $null)
$job = Start-Job -ScriptBlock $JobScript -ArgumentList $ArgumentList
if($job -ne $null)
{
#Create a Custom Object to keep track of the Job & Script Blocks
$psObject = New-Object System.Management.Automation.PSObject
Add-Member -InputObject $psObject -MemberType 'NoteProperty' -Name Job -Value $job
Add-Member -InputObject $psObject -MemberType 'NoteProperty' -Name CompleteScript -Value $CompletedScript
Add-Member -InputObject $psObject -MemberType 'NoteProperty' -Name UpdateScript -Value $UpdateScript
[void]$JobTrackerList.Add($psObject)
#Start the Timer
if(-not $timerJobTracker.Enabled)
{
$timerJobTracker.Start()
}
}
elseif($CompletedScript -ne $null)
{
#Failed
Invoke-Command -ScriptBlock $CompletedScript -ArgumentList $null
}
}
function Update-JobTracker
{
<#
.SYNOPSIS
Checks the status of each job on the list.
#>
#Poll the jobs for status updates
$timerJobTracker.Stop() #Freeze the Timer
for($index =0; $index -lt $JobTrackerList.Count; $index++)
{
$psObject = $JobTrackerList[$index]
if($psObject -ne $null)
{
if($psObject.Job -ne $null)
{
if($psObject.Job.State -ne "Running")
{
#Call the Complete Script Block
if($psObject.CompleteScript -ne $null)
{
#$results = Receive-Job -Job $psObject.Job
Invoke-Command -ScriptBlock $psObject.CompleteScript - ArgumentList $psObject.Job
}
$JobTrackerList.RemoveAt($index)
Remove-Job -Job $psObject.Job
$index-- #Step back so we don't skip a job
}
elseif($psObject.UpdateScript -ne $null)
{
#Call the Update Script Block
Invoke-Command -ScriptBlock $psObject.UpdateScript -ArgumentList $psObject.Job
}
}
}
else
{
$JobTrackerList.RemoveAt($index)
$index-- #Step back so we don't skip a job
}
}
if($JobTrackerList.Count -gt 0)
{
$timerJobTracker.Start()#Resume the timer
}
}
function Stop-JobTracker
{
<#
.SYNOPSIS
Stops and removes all Jobs from the list.
#>
#Stop the timer
$timerJobTracker.Stop()
#Remove all the jobs
while($JobTrackerList.Count -gt 0)
{
$job = $JobTrackerList[0].Job
$JobTrackerList.RemoveAt(0)
Stop-Job $job
Remove-Job $job
}
}#endregion
$buttonStartJob_Click= {
$progressbaroverlay1.Value = 0
$progressbaroverlay1.Step = 1
$progressbaroverlay1.Maximum = $Appname.Count
$this.Enabled = $false
$buttonStartJob.Enabled = $false
#Create a New Job using the Job Tracker
foreach ($app in $Appname)
{
$install = "C:\pstools\Update\cmd\$app\install.cmd"
$run = "C:\Windows\System32\cmd.exe"
Add-JobTracker -Name "test"`
-JobScript {
param (
[string]$batchFilePath
)
Write-Verbose "Launching: [$batchFilePath]" -Verbose
Set-Location $env:windir
& ($run, $batchFilePath)
}` -ArgumentList $install -CompletedScript {
Param ($Job)
#$progressbar1.Value = 100
#Enable the Button
$ProgressBarOverlay1.PerformStep()
$buttonStartJob.ImageIndex = -1
$buttonStartJob.Enabled = $true
}`
-UpdateScript {
Param ($Job)
$results = Receive-Job -Job $Job | Select-Object -Last 1
if ($results -is [int])
{
$progressbaroverlay1.Value = $results
}
}
}
}
$timer1_Tick={
#TODO: Place custom script here
#if ([timespan]::FromSeconds($timerUpdate.Tag) -ge [timespan]::Fromminutes(1))
IF($progressbaroverlay1.Value -eq 100)
{
$timerUpdate.stop()
$formSampleTimer.Close()
}
else
{
[System.Windows.Forms.Application]::DoEvents()
$label1.Text = [timespan]::FromSeconds($timer1.Tag++)
}
}
I am assuming that you either copied this from somewhere and modified it to kind of work for you, or you used something like PowerGUI or Sapien to generate the script for you because this is seriously hard to read code in my opinion.
If I understand it right you create $JobTrackerList as an ArrayList object. Then for each application that you want to install you add a PSCustomObject to that ArrayList with 3 properties, one being a Job that runs in the background, one being a script to run when that is completed, and one being a script to update your progress bar. So each object has a background job that is running, and this is the root issue at this point. But wait, we're not done, you have a timer going on (not actually defined in the code you gave us) that runs, and every 1000 ticks it stops, checks each job, if it is completed it runs the "completed" scriptblock that you defined for that object, and then removes the object from the ArrayList. If it is still not completed it updates the progressbar with the "update" scriptblock. Then it starts the timer again.
Here's what I think needs to happen for this to work like you want... You need to go ahead and make your ArrayList, and add objects to it for each application that you want to run, but do not create jobs for them all! Once you create all the objects start a job for the first application (and add it as a member of the object like you did before). Then when your script checks for completed jobs, before it deletes the object it is looking at it should start the job for the next object.
So, that's going to require a bit of re-write of your code. I'm not sure where to really start with this, because the code that you have is so very different to how I would have written it, so I've given you my suggestions, and now I'll let you run with it from here. If you need help writing the code (at least try first) let me know and I'll see what I can do. Probably not today, I'm dead tired and not on my A game, but I'm sure I can come up with something tomorrow if you end up needing help.
Edit: Ok, I think I have code that should do what you want, and work with what you already have in place:
$formJobProgress_Load={
#TODO: Initialize Form Controls here
$timer1.Interval = 1000
$timer1.Tag = 0
$timer1.Start()
[System.Windows.Forms.Application]::DoEvents()
}
$timerJobTracker_Tick={
$timerJobTracker.Stop() #Freeze the Timer
for($index =0; $index -lt $JobTrackerList.Count; $index++){
$psObject = $JobTrackerList[$index]
if($psObject -ne $null -AND $psObject.Job -ne $null -AND $psObject.Job.State -ne "Running"){
#Perform old 'Complete' scriptblock to update progressbar
$ProgressBarOverlay1.PerformStep()
#Check if this is the only job, and if not start the next one
If($JobTrackerList.count -gt 1){
$NextJob = $JobTrackerList[($index+1)]
$NextJob.Job = Start-Job -ScriptBlock $NextJob.JobScript
}
$JobTrackerList.RemoveAt($index)
Remove-Job -Job $psObject.Job
$index-- #Step back so we don't skip a job
}Else{
$results = Receive-Job -Job $Job | Select-Object -Last 1
if ($results -is [int]){
$progressbaroverlay1.Value = $results
}
}
}
if($JobTrackerList.Count -gt 0){
$timerJobTracker.Start()#Resume the timer
}
}
$formMain_FormClosed=[System.Windows.Forms.FormClosedEventHandler]{
#Stop the timer
$timerJobTracker.Stop()
#Remove all the jobs
while($JobTrackerList.Count -gt 0){
$job = $JobTrackerList[0].Job
$JobTrackerList.RemoveAt(0)
Stop-Job $job
Remove-Job $job
}
}
$Appname = #("Adobe_FlashPlayer", "Acrobat_Reader", "Microsoft_RDP_8.1")
$JobTrackerList = New-Object System.Collections.ArrayList
$buttonStartJob_Click= {
$progressbaroverlay1.Value = 0
$progressbaroverlay1.Step = 1
$progressbaroverlay1.Maximum = $Appname.Count
$this.Enabled = $false
$buttonStartJob.Enabled = $false
ForEach($App in $Appname){
$install = "C:\pstools\Update\cmd\$app\install.cmd"
$run = "C:\Windows\System32\cmd.exe"
$JobScript = {
Write-Verbose "Launching: [$install]" -Verbose
Set-Location $env:windir
& $install
}
[void]$JobTrackerList.Add((New-Object PSObject -Property #{
'Application' = $Name
'JobScript' = $JobScript
'Job' = $null
}))
}
$JobTrackerList[0].Job = Start-Job -ScriptBlock $JobTrackerList[0].JobScript
}
$timer1_Tick={
#TODO: Place custom script here
#if ([timespan]::FromSeconds($timerUpdate.Tag) -ge [timespan]::Fromminutes(1))
IF($progressbaroverlay1.Value -eq 100)
{
$timerUpdate.stop()
$formSampleTimer.Close()
}
else
{
[System.Windows.Forms.Application]::DoEvents()
$label1.Text = [timespan]::FromSeconds($timer1.Tag++)
}
}
I hope that works for you.