I put together a powershell function that schedules server reboots.
I'm stuck on adding a for loop to run the function for multiple servers using ParameterSetName.
Before adding the ParamSets, I just setup a function and called the function in the same script within a for loop which worked great.
Function ScheduleReboot{
[CmdletBinding(SupportsShouldProcess=$true,DefaultParameterSetName="ViewOnly")]
Param(
[Parameter(Mandatory=$false,ParameterSetName="KillSwitch")]
[Switch]$NukeJobs,
[Parameter(Mandatory=$true,
HelpMessage="ServerName goes here",ParameterSetName="FullRun")]
[string]$server,
[Parameter(Mandatory=$true,
HelpMessage="Enter a Date/Time 07-28-15 16:00 For July 28th, 2015 at 4:00 PM",
ParameterSetName="FullRun"
)]
[ValidatePattern('(?#Enter MM-dd-YY HH:MM 24hr clock)\d{2}-\d{2}-\d{2}\s\d{2}[:]\d{2}')]
$date,
[Parameter(Mandatory=$false,ParameterSetName="ViewOnly")]
[switch]$ViewOnly
) #"$server will reboot on $date"
Switch ($PSCmdlet.ParameterSetName){
"KillSwitch"{
if($env:PROCESSOR_ARCHITECTURE -eq "x86")
{
set-alias ps64 "$env:windir\sysnative\WindowsPowerShell\v1.0\powershell.exe"
$script2 = [ScriptBlock]::Create("IF(Test-Path -Path C:\Windows\System32\Tasks\Microsoft\Windows\PowerShell\ScheduledJobs\$server)
{Remove-Item -Force -Path C:\Windows\System32\Tasks\Microsoft\Windows\PowerShell\ScheduledJobs\$server}")
"Removing previous scheduled server reboot data"
ps64 -command $script2
} else {
Get-ScheduledJob | Unregister-ScheduledJob
}
}#End killswitch block.
"FullRun" {
[array]$servers = gc D:\Scripts\servers.txt
[array]$dates = gc D:\Scripts\dates.txt
if($dates.Length -eq $servers.Length)
{
"Input count matches"
for($i=0;$i -lt $servers.Length;$i++)
{
$day = $dates[$i]
$comp = $servers[$i]
$user = Get-Credential -UserName $env:USERNAME -Message "UserName/password for scheduled Reboot"
$trigger = New-JobTrigger -once -at $day
$script = [ScriptBlock]::Create("D:\Scripts\Scheduled-Reboot-Single.ps1 -server $server | Out-File -Force \\SysLogSvr\d$\scripts\$server-Reboot.log")
Register-ScheduledJob -Name $comp -Credential $user -Trigger $trigger -ScriptBlock $script
}#end for loop
} else {
$warn = "Server list contains " + $servers.Count + " items and Date list contains " + $dates.Count + " items, Please re-check!!!"
Write-Warning $warn
}
}#end fullrun block.
"ViewOnly" {
Get-ScheduledJob | Get-JobTrigger
Get-ScheduledJob | Select Name,Command | FT -AutoSize
}#end viewonly block.
}#End param switch
}#end function.
Make that 3 (or more) different functions. The parameter sets the way you are using it is over-complicating it and making it hard for you to debug it.
I would have made a Get-ScheduledReboot, Clear-ScheduledReboot, Set-ScheduledReboot collection of functions.
There are a few bugs in this script as posted that are causing you issues. The first is the nuke parameter set does not contain the server parameter. The other bug is the one that is throwing you off. You define a $server parameter above but in the function, you change gears and load from a text file. You are mixing $server, $servers and $comp variables around causing it to fail.
You should change your [string]$server to [string[]]$server or better yet, [string[]$ComputerName and foreach($node in $ComputerName){...} on the collection. Keep the text file loading outside that function.
Related
I need to run parallel Search-Mailbox cmdlets against 100's mailboxes to delete the content but they need to fit certain parameters first like certain CAS protocols enabled and a forwarding address present. I've also parameterised it so I can pass a $maxJobCount int to it so the runner can specify a maximum number of concurrently running jobs to allow so as to account for resources on their machine.
Got the thing working then got to the start-job component which is a pretty simple function.
function _StartJob {
param (
$mailAddress
)
Start-Job -Name $mailAddress -Scriptblock {
Get-EXOMailbox $mailAddress -PropertySets Delivery
}
}
That's returning an error saying I need to run Connect-ExchangeOnline before using the cmdlets which is where I learned script blocks in Start-Job are actually new PowerShell.exe processes so don't inherit modules and session options.
Does anyone know an easier way around this? In an MFA environment, it either means sitting there and pasting the password in a few hundred times or convincing the Change board and Secops dept to let me setup a graph application with delete rights... both painful
Thanks for any advice
You just have to pass in the creds into the block however you want.
$kvCertName = 'Cert'
#I am using azure automation here to get the cert its different for keyvault
$kvCertPFX = Get-AutomationCertificate -Name $kvCertName
$tenantid = 'yourcompany.onmicrosoft.com'
$appid = '00000000-46da-6666-5555-33333cfe77ec'
$startDate = ([datetime]::Today).AddDays(-7)
#Build the script block
$block = {
Param(
$kvCert,
$appID,
$tenantID,
$n,
$startdate
)
$newCertPFX = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($kvCert)
Connect-ExchangeOnline -Certificate ([System.Security.Cryptography.X509Certificates.X509Certificate2]$newCertPFX) -AppID $appID -Organization $tenantID -ErrorAction Stop
Search-AdminAuditLog -StartDate $startDate.adddays($n) -EndDate $($startDate.AddDays($n) | get-date -Hour 23 -Minute 59 -Second 59) -ExternalAccess:$false -ResultSize 250000
Disconnect-ExchangeOnline -confirm:$false
}
#Remove all jobs created
Get-Job | Remove-Job
#Run All the Parrallel Jobs
$num = 0..6
$kvCert = $kvCertPFX.Export(3)
foreach($n in $num){Start-Job -Scriptblock $Block -ArgumentList #($kvCert,$appID,$tenantid,$n,$startdate)}
#Wait for all jobs to finish.
do {start-sleep 1}
until ($(Get-Job -State Running).count -eq 0)
#Get information from each job.
$adminPowerShellAuditLog = $null
foreach($job in Get-Job){$adminPowerShellAuditLog+= Receive-Job -Id ($job.Id)}
Write-Output $adminPowerShellAuditLog
I have a function that is used to purge a message queue on a machine but I'm looking to adapt it and make it a little more robust. I'd like to be able to fire the command to a machine even if the current machine doesn't have MSMQ installed.
The local command works without issue but when the invoke-command is called, the check to see if the queue exists returns false (even though the queue does exist). Has anyone run into anything like this before? Any suggestions?
This is my function:
Function Purge-MessageQueue
{
<#
.Synopsis
Use hostname to purge message queue
.DESCRIPTION
Checks if MSMQ is locally installed otherwise fire the purge
command to the machine you are purging
.EXAMPLE
Purge-MessageQueue -targetMachine $env:computername -queueName 'test'
#>
Param
(
[Parameter(Mandatory=$true)]
[String]$targetMachine,
[Parameter(Mandatory=$true)]
[string]$queueName
)
Write-Verbose "Purging $queueName queue on: $targetMachine"
$queueName = "$targetMachine\$queueName"
$error.clear()
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
try {[void][System.Messaging.MessageQueue]::Exists($queueName)}
catch
{
if ($_.exception.ToString() -like '*Message Queuing has not been installed on this computer.*')
{
#push command to machine
$RemoteSuccess = Invoke-Command -ComputerName $targetMachine -ScriptBlock { Param($queueName)
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
If([System.Messaging.MessageQueue]::Exists($queueName))
{
$queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
Try{$queue.Purge()}
Catch{$error}
}
} -ArgumentList $queueName
}
}
If(!$error)
{
If([System.Messaging.MessageQueue]::Exists($queueName))
{
$queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
$queue.Purge()
}
}
If(!$Error -and !$RemoteSuccess)
{
Write-Host "$queueName queue on $targetMachine cleared"
}
Else
{
Write-Warning "Failed locating queue $queueName on $targetMachine"
}
}
NOTES:
In order to identify what exactly is going on, I used write-host on the exists statement and it returns false. The queue is not being found when I pass the scriptblock. It is executing on the other machine (tested writing a file which succeeded). When I run:
Write-Host "$([System.Messaging.MessageQueue]::Exists($queueName))`n$queueName"
$objqueue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
I get the false, the correct queue name, and the following error:
Exception calling "Purge" with "0" argument(s): "The queue does not
exist or you do not have sufficient permissions to perform the
operation."
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : MessageQueueException
+ PSComputerName : XXXXXX
Running the same command directly on the machine works without issue.
I also found someone else trying to do something similar on serverfault:
https://serverfault.com/questions/399178/how-to-retrieve-names-of-all-private-msmq-queues-efficiently
And when I try this:
Invoke-Command -ComputerName $targetMachine -ScriptBlock { Get-MsmqQueue }
I get the following result:
Cannot find specified machine.
+ CategoryInfo : ObjectNotFound: (:) [Get-MsmqQueue], MessageQueueException
+ FullyQualifiedErrorId : MachineNotFound,Microsoft.Msmq.PowerShell.Commands.GetMSMQQueueCommand
This following command does return the data, but it doesn't allow me to send a purge command:
Invoke-Command -ComputerName $targetMachine -ScriptBlock {Get-WmiObject -class Win32_PerfRawData_MSMQ_MSMQQueue}
I also tried to write the content to a script file and then call the file, which when run on the machine, works without issue but not when called via invoke-command:
$filewriter = #"
[Reflection.Assembly]::LoadWithPartialName("System.Messaging")
If([System.Messaging.MessageQueue]::Exists('$queueName'))
{
`$objqueue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
Try{`$objqueue.Purge()}
Catch{`$error}
}
"#
$session = New-PSSession -ComputerName $targetMachine
Invoke-Command -Session $session -ScriptBlock {Param($FileWriter)
$FileWriter | Out-File "C:\Temp\PurgeQueue.ps1"
} -ArgumentList $filewriter
$test = Invoke-Command -Session $session -scriptblock {Pushd "C:\Temp\"
.\PurgeQueue.ps1}
I have not found the cause for this, but I will summarize what I have found and my workaround
Summary:
When invoking msmq commands via invoke-command, only private queues appear and can be manipulated.
Workaround:
I've build a function to deal with purging and adding message to queues by creating scheduled tasks on the remote machine to call the script created by the command.
Function Push-MSMQRemoteCommand
{
Param(
[Parameter(Mandatory=$true)]
$targetMachine,
[Parameter(Mandatory=$true)]
$password,
[Parameter(Mandatory=$true)]
$queueName,
[Switch]$purge,
$user,
$message)
Begin
{
If(!$user){$user = "$env:USERDOMAIN\$env:USERNAME"}
If($purge -and $message.Length -ne 0){Write-Error "Choose to purge or add... not both" -ErrorAction Stop}
$queuepath = "$targetMachine\$queueName"
#build commands to push
If($purge)
{
$scriptblock = #"
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
If ([System.Messaging.MessageQueue]::Exists('$queuePath')) {
`$queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queuePath
`$queue.Purge()
}
"#
}
ElseIf($message)
{
If($message.Length -eq 0){Write-Error "No message provided to add message" -ErrorAction Stop}
$scriptblock = #"
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
`$queue = new-object System.Messaging.MessageQueue "$queuepath"
`$utf8 = new-object System.Text.UTF8Encoding
`$msgBytes = `$utf8.GetBytes('$message')
`$msgStream = new-object System.IO.MemoryStream
`$msgStream.Write(`$msgBytes, 0, `$msgBytes.Length)
`$msg = new-object System.Messaging.Message
`$msg.BodyStream = `$msgStream
`$msg.Label = "RemoteQueueManagerPowershell"
`$queue.Send(`$msg)
"#
}
#Push Commands
Invoke-Command -ComputerName $targetMachine -ScriptBlock {
Param($user,$password,$scriptblock)
$scriptblock | Out-file -FilePath "C:\temp\ManageQueue.ps1" -Force
$action = New-ScheduledTaskAction -execute 'powershell.exe' -Argument '-File "C:\temp\ManageQueue.ps1"'
#scheudling action to start 2 seconds from now
$trigger = New-ScheduledTaskTrigger -Once -At ((Get-Date)+(New-TimeSpan -Seconds 2))
Register-ScheduledTask -TaskName RemoteQueueManager `
-Action $action `
-Trigger $trigger `
-User "$user"`
-Password $password
#Start-Sleep -Seconds 10
Unregister-ScheduledTask -TaskName RemoteQueueManager -Confirm:$false
Remove-Item -Path "C:\temp\ManageQueue.ps1" -Force
} -ArgumentList $user,$password,$scriptblock
}
}
From your analysis I have feeling that it is issue of rights.
Did you check the rights for your user?
If you are a normal user you have to do the following (not an Administrator) on the
destination computer/server/VM:
1) first create a group and add there users
net localgroup "Remote PowerShell Session Users" /add
net localgroup "Remote PowerShell Session Users" the-user /add
2) Invoke GUI
Set-PSSessionConfiguration microsoft.powershell -ShowSecurityDescriptorUI
3) Add Remote PowerShell Session Users group and grant it execute (invoke) rights
4) Restart the service:
Set-PSSessionConfiguration microsoft.powershell -ShowSecurityDescriptorUI
5) the user now should be able to run remote session
The original source is here.
This lambda function executes as expected:
$WriteServerName = {
param($server)
Write-Host $server
}
$server = "servername"
$WriteServerName.invoke($server)
servername
However, using the same syntax, the following script prompts for credentials and then exits to the command line (running like this: .\ScriptName.ps1 -ConfigFile Chef.config), implying that the lambda functions aren't executing properly (for testing, each should just output the server name).
Why does the former lambda function return the server name, but the ones in the script don't?
Param(
$ConfigFile
)
Function Main {
#Pre-reqs: get credential, load config from file, and define lambda functions.
$jobs = #()
$Credential = Get-Credential
$Username = $Credential.username
$ConvertedPassword = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credential.password)
$Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($ConvertedPassword)
$Config = Get-Content $ConfigFile -Raw | Out-String | Invoke-Expression
#Define lambda functions
$BootStrap = {
param($Server)
write-host $server
}
$RunChefClient = {
param($Server)
write-host $server
}
$SetEnvironment = {
param($Server)
write-host $server
}
#Create bootstrap job for each server and pass lambda functions to Scriptblock for execution.
if(($Username -ne $null) -and ($Password -ne $null))
{
ForEach($HashTable in $Config)
{
$Server = $HashTable.Server
$Roles = $HashTable.Roles
$Tags = $HashTable.Tags
$Environment = $HashTable.Environment
$ScriptBlock = {
param ($Server,$BootStrap,$RunChefClient,$SetEnvironment)
$BootStrap.invoke($Server)
$RunChefClient.invoke($Server)
$SetEnvironment.invoke($Server)
}
$Jobs += Start-Job -ScriptBlock $ScriptBlock -ArgumentList #($Server,$BootStrap,$RunChefClient,$SetEnvironment)
}
}
else {Write-Host "Username or password is missing, exiting..." -ForegroundColor Red; exit}
}
Main
Without testing, I am going to go ahead and say it's because you are putting your scriptblock executions in PowerShell Jobs and then not doing anything with them. When you start a job, it starts a new PowerShell instance and executes the code you give it along with the parameters you give it. Once it completes, the completed PSRemotingJob object sits there and does nothing until you actually do something with it.
In your code, all the jobs you start are assigned to the $Jobs variable. You can also get all your running jobs with Get-Job:
Get-Job -State Running
If you want to get any of the data returned from your jobs, you'll have to use Receive-Job
# Either
$Jobs | Receive-Job
# Or
Get-Job -State Running | Receive-Job
I run a script which performs many WMI-querys - but the cmdlet hangs if the server doesn't answer..
Is there any way I can make this (or any other cmndlet for that matter) timeout and exit if X seconds has passed?
Edit
Thanks to a tip from mjolinor the solution is to run this as -asjob and set a timeout in a while loop. But this is run from within a job already (started with Start-Job). So how do I know I am controlling the correct job?
This is my code from inside my already started job:
Get-WmiObject Win32_Service -ComputerName $server -AsJob
$Complete = Get-date
While (Get-Job -State Running){
If ($(New-TimeSpan $Complete $(Get-Date)).totalseconds -ge 5) {
echo "five seconds has passed, removing"
Get-Job | Remove-Job -Force
}
echo "still running"
Start-Sleep -Seconds 3
}
PS: My jobs started with Start-Jobs are already taken care of..
You could try the get-wmiCustom function, posted here. Wouldn't it be nice if get-wmiObject had a timeout parameter? Let's upvote this thing.
I've modified Daniel Muscetta's Get-WmiCustom to also support passing credentials.
I know this post is a little old, hopefully this helps someone else.
# Define modified custom get-wmiobject for timeout with credential from http://blogs.msdn.com/b/dmuscett/archive/2009/05/27/get_2d00_wmicustom.aspx
Function Get-WmiCustom([string]$Class,[string]$ComputerName,[string]$Namespace = "root\cimv2",[int]$Timeout=15, [pscredential]$Credential)
{
$ConnectionOptions = new-object System.Management.ConnectionOptions
$EnumerationOptions = new-object System.Management.EnumerationOptions
if($Credential){
$ConnectionOptions.Username = $Credential.UserName;
$ConnectionOptions.SecurePassword = $Credential.Password;
}
$timeoutseconds = new-timespan -seconds $timeout
$EnumerationOptions.set_timeout($timeoutseconds)
$assembledpath = "\\$Computername\$Namespace"
#write-host $assembledpath -foregroundcolor yellow
$Scope = new-object System.Management.ManagementScope $assembledpath, $ConnectionOptions
$Scope.Connect()
$querystring = "SELECT * FROM " + $class
#write-host $querystring
$query = new-object System.Management.ObjectQuery $querystring
$searcher = new-object System.Management.ManagementObjectSearcher
$searcher.set_options($EnumerationOptions)
$searcher.Query = $querystring
$searcher.Scope = $Scope
trap { $_ } $result = $searcher.get()
return $result
}
Glad my Get-WmiCustom function here http://blogs.msdn.com/b/dmuscett/archive/2009/05/27/get_2d00_wmicustom.aspx is useful.
when creating the job using get-wmiobject assign that job to a variable, then that variable can be piped into get-job for status or receive-job for results
$ThisJob = start-job -scriptblock {param ($Target) Get-WmiObject -Class Win32_Service -ComputerName $Target -AsJob} -ArgumentList $server
$Timer = [System.Diagnostics.Stopwatch]::StartNew()
While ($ThisJob | Get-Job | where {$_.State -imatch "Running"}){
If ($Timer.Elapsed.Seconds -ge 5) {
echo "five seconds has passed, removing"
$ThisJob | Get-Job | Remove-Job -Force
} # end if
echo "still running"
Start-Sleep -Seconds 3
} # end while
$Results = $ThisJob | where {$_.State -inotmatch "failed"} | receive-job
$Timer.Stop | out-null
The only two solutions I've seen for this problem are:
Run the queries as background jobs and put a timer on them, then stop/remove the jobs that run too long.
Fix your servers.
In addition to what has been said, not a bullet proof solution but consider pinging your servers first (Test-Connection), it can speed up execution time in case you have no responding machines.
I want to set a time limit on a PowerShell (v2) script so it forcibly exits after that time limit has expired.
I see in PHP they have commands like set_time_limit and max_execution_time where you can limit how long the script and even a function can execute for.
With my script, a do/while loop that is looking at the time isn't appropriate as I am calling an external code library that can just hang for a long time.
I want to limit a block of code and only allow it to run for x seconds, after which I will terminate that code block and return a response to the user that the script timed out.
I have looked at background jobs but they operate in a different thread so won't have kill rights over the parent thread.
Has anyone dealt with this or have a solution?
Thanks!
Something like this should work too...
$job = Start-Job -Name "Job1" -ScriptBlock {Do {"Something"} Until ($False)}
Start-Sleep -s 10
Stop-Job $job
Here's my solution, inspired by this blog post. It will finish running when all has been executed, or time runs out (whichever happens first).
I place the stuff I want to execute during a limited time in a function:
function WhatIWannaDo($param1, $param2)
{
# Do something... that maybe takes some time?
Write-Output "Look at my nice params : $param1, $param2"
}
I have another funtion that will keep tabs on a timer and if everything has finished executing:
function Limit-JobWithTime($Job, $TimeInSeconds, $RetryInterval=5)
{
try
{
$timer = [Diagnostics.Stopwatch]::StartNew()
while (($timer.Elapsed.TotalSeconds -lt $TimeInSeconds) -and ('Running' -eq $job.JobStateInfo.State)) {
$totalSecs = [math]::Round($timer.Elapsed.TotalSeconds,0)
$tsString = $("{0:hh}:{0:mm}:{0:ss}" -f [timespan]::fromseconds($totalSecs))
Write-Progress "Still waiting for action $($Job.Name) to complete after [$tsString] ..."
Start-Sleep -Seconds ([math]::Min($RetryInterval, [System.Int32]($TimeInSeconds-$totalSecs)))
}
$timer.Stop()
$totalSecs = [math]::Round($timer.Elapsed.TotalSeconds,0)
$tsString = $("{0:hh}:{0:mm}:{0:ss}" -f [timespan]::fromseconds($totalSecs))
if ($timer.Elapsed.TotalSeconds -gt $TimeInSeconds -and ('Running' -eq $job.JobStateInfo.State)) {
Stop-Job $job
Write-Verbose "Action $($Job.Name) did not complete before timeout period of $tsString."
} else {
if('Failed' -eq $job.JobStateInfo.State){
$err = $job.ChildJobs[0].Error
$reason = $job.ChildJobs[0].JobStateInfo.Reason.Message
Write-Error "Job $($Job.Name) failed after with the following Error and Reason: $err, $reason"
}
else{
Write-Verbose "Action $($Job.Name) completed before timeout period. job ran: $tsString."
}
}
}
catch
{
Write-Error $_.Exception.Message
}
}
... and then finally I start my function WhatIWannaDo as a background job and pass it on to the Limit-JobWithTime (including example of how to get output from the Job):
#... maybe some stuff before?
$job = Start-Job -Name PrettyName -Scriptblock ${function:WhatIWannaDo} -argumentlist #("1st param", "2nd param")
Limit-JobWithTime $job -TimeInSeconds 60
Write-Verbose "Output from $($Job.Name): "
$output = (Receive-Job -Keep -Job $job)
$output | %{Write-Verbose "> $_"}
#... maybe some stuff after?
I know this is an old post, but I have used this in my scripts.
I am not sure if its the correct use of it, but the System.Timers.Timer that George put up gave me an idea and it seems to be working for me.
I use it for servers that sometimes hang on a WMI query, the timeout stops it getting stuck.
Instead of write-host I then output the message to a log file so I can see which servers are broken and fix them if needed.
I also don't use a guid I use the servers hostname.
I hope this makes sense and helps you.
$MyScript = {
Get-WmiObject -ComputerName MyComputer -Class win32_operatingsystem
}
$JobGUID = [system.Guid]::NewGuid()
$elapsedEventHandler = {
param ([System.Object]$sender, [System.Timers.ElapsedEventArgs]$e)
($sender -as [System.Timers.Timer]).Stop()
Unregister-Event -SourceIdentifier $JobGUID
Write-Host "Job $JobGUID removed by force as it exceeded timeout!"
Get-Job -Name $JobGUID | Remove-Job -Force
}
$timer = New-Object System.Timers.Timer -ArgumentList 3000 #just change the timeout here
Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action $elapsedEventHandler -SourceIdentifier $JobGUID
$timer.Start()
Start-Job -ScriptBlock $MyScript -Name $JobGUID
Here is an example of using a Timer. I haven't tried it personally, but I think it should work:
function Main
{
# do main logic here
}
function Stop-Script
{
Write-Host "Called Stop-Script."
[System.Management.Automation.Runspaces.Runspace]::DefaultRunspace.CloseAsync()
}
$elapsedEventHandler = {
param ([System.Object]$sender, [System.Timers.ElapsedEventArgs]$e)
Write-Host "Event handler invoked."
($sender -as [System.Timers.Timer]).Stop()
Unregister-Event -SourceIdentifier Timer.Elapsed
Stop-Script
}
$timer = New-Object System.Timers.Timer -ArgumentList 2000 # setup the timer to fire the elapsed event after 2 seconds
Register-ObjectEvent -InputObject $timer -EventName Elapsed -SourceIdentifier Timer.Elapsed -Action $elapsedEventHandler
$timer.Start()
Main
How about something like this:
## SET YOUR TIME LIMIT
## IN THIS EXAMPLE 1 MINUTE, BUT YOU CAN ALSO USE HOURS/DAYS
# $TimeSpan = New-TimeSpan -Days 1 -Hours 2 -Minutes 30
$TimeSpan = New-TimeSpan -Minutes 1
$EndTime = (Get-Date).AddMinutes($TimeSpan.TotalMinutes).ToString("HH:mm")
## START TIMED LOOP
cls
do
{
## START YOUR SCRIPT
Write-Warning "Test-Job 1...2...3..."
Start-Sleep 3
Write-Warning "End Time = $EndTime`n"
}
until ($EndTime -eq (Get-Date -Format HH:mm))
## TIME REACHED AND END SCRIPT
Write-Host "End Time reached!" -ForegroundColor Green
When using hours or days as a timer, make sure you adjust the $TimeSpan.TotalMinutes
and the HH:mm format, since this does not facilitate the use of days in the example.
I came up with this script.
Start-Transcript to log all actions and save them to a file.
Store the current process ID value in the variable $p then write it to screen.
Assign the current date to the $startTime variable.
Afterwards I assign it again and add the extra time to the current date to the var $expiration.
The updateTime function return what time there is left before the application closes. And writes it to console.
Start looping and kill process if the timer exceeds the expiration time.
That's it.
Code:
Start-Transcript C:\Transcriptlog-Cleanup.txt #write log to this location
$p = Get-Process -Id $pid | select -Expand id # -Expand selcts the string from the object id out of the current proces.
Write-Host $p
$startTime = (Get-Date) # set start time
$startTime
$expiration = (Get-Date).AddSeconds(20) #program expires at this time
# you could change the expiration time by changing (Get-Date).AddSeconds(20) to (Get-Date).AddMinutes(10)or to hours whatever you like
#-----------------
#Timer update function setup
function UpdateTime
{
$LeftMinutes = ($expiration) - (Get-Date) | Select -Expand minutes # sets minutes left to left time
$LeftSeconds = ($expiration) - (Get-Date) | Select -Expand seconds # sets seconds left to left time
#Write time to console
Write-Host "------------------------------------------------------------------"
Write-Host "Timer started at : " $startTime
Write-Host "Current time : " (Get-Date)
Write-Host "Timer ends at : " $expiration
Write-Host "Time on expire timer : "$LeftMinutes "Minutes" $LeftSeconds "Seconds"
Write-Host "------------------------------------------------------------------"
}
#-----------------
do{ #start loop
Write-Host "Working"#start doing other script stuff
Start-Sleep -Milliseconds 5000 #add delay to reduce spam and processing power
UpdateTime #call upadate function to print time
}
until ($p.HasExited -or (Get-Date) -gt $expiration) #check exit time
Write-Host "done"
Stop-Transcript
if (-not $p.HasExited) { Stop-Process -ID $p -PassThru } # kill process after time expires