I'm using PowerShell to start a bat file that wipes a USB drive that connected.
If I use the script without Start-Process it works fine, but I'm wanting to connect multiple drives and have it wipe them simultaneously.
The script:
Register-WmiEvent -Class Win32_VolumeChangeEvent -SourceIdentifier VolumeChange
Write-Host (Get-Date -Format s) " Beginning script..."
do {
$newEvent = Wait-Event -SourceIdentifier volumeChange
$eventType = $newEvent.SourceEventArgs.NewEvent.EventType
$eventTypeName = switch ($eventType) {
1 {"Configuration changed"}
2 {"Device arrival"}
3 {"Device removal"}
4 {"docking"}
}
Write-Host (Get-Date -Format s) " Event detected = " $eventTypeName
if ($eventType -eq 2) {
$driveLetter = $newEvent.SourceEventArgs.NewEvent.DriveName
$driveLabel = ([wmi]"Win32_LogicalDisk='$driveLetter'").VolumeName
Write-Host (Get-Date -Format s) " Drive name = " $driveLetter
Write-Host (Get-Date -Format s) " Drive label = " $driveLabel
# Execute process if drive matches specified condition(s)
if ($driveLabel -eq 'BBIFREE_01' -or $drivelabel -eq 'HD10') {
Write-Host (Get-Date -Format s) " Starting task in 3 seconds..."
Start-Sleep -Seconds 3
Start-Process -FilePath D:\wipe.bat $driveLetter, $driveLabel
Copy-Item -Path D:\Utilities1 -Destination $driveLetter -Recurse
$driveEject = New-Object -ComObject Shell.Application
$driveEject.Namespace(17).ParseName($driveLetter).InvokeVerb("Eject")
}
}
Remove-Event -SourceIdentifier VolumeChange
} while (1 -eq 1) #Loop until next event
Unregister-Event -SourceIdentifier VolumeChange
The bat file contents:
set arg1=%1
set arg2=%2
format %args1% /FS:NTFS /p:1 /V:%args2% /x /y
EDIT
To clarify: the script is to run continously on a specific PC where it should start the bat file (as in wipe the disk securely) every time it detects a disk being connected.
If I use:
D:\wipe.bat -ArgumentList `"$driveLetter",`"$driveLabel"
then it starts the wiping on 1 disk, and on 1 disk only.
I need it to detect multiple disks, that's why I used Start-Process, seeing as I thought it would run on the background and keep watching for new events.
EDIT2
I changed the code to avoid using -ArgumentList, see above.
If I put the echo command in my batch file as requested:
set arg1=E:
set arg2=BBIFREE_01
ECHO ECHO IS ON
ECHO ECHO IS ON
So I see the commands in the bat file, but it doesn't execute and goes straight for the copy command.
This is a slightly modified version of a Script I wrote a while back, I don't have time right now to confirm it works 100% but it should at least point you in the right direction, it just threads the actual wiping so it can handle other jobs in the background, then uses a global popup to warn when one is done to prevent having to block while the job is finishing.
Should be able to handle any number of devices at once, it uses PowerShell's Format-Volume command instead, but you could put a call to the BAT file inside the job instead.
$USBWhiteList = #( #Add wildcard items here, if a USB matches one it will be wiped.
"USB0*"
"*WIPE"
)
Enum EventNames{ Changed = 1 ; Inserted = 2 ; Removed = 3 ; Docking = 4 } #Names for events
Register-WmiEvent -Class win32_VolumeChangeEvent -SourceIdentifier volumeChange -ErrorAction SilentlyContinue #register the event
do{
Write-Host "Monitoring for Disk events..." -Fore Yellow
$Event = Wait-Event -SourceIdentifier volumeChange #wait for a disk event
$EventType = [EventNames]$Event.SourceEventArgs.NewEvent.EventType #get the type of the event
Write-Host "Drive $($EventType), Processing..." -Fore Yellow -NoNewline
$Volume = Get-Volume -DriveLetter $Event.SourceEventArgs.NewEvent.DriveName -ErrorAction SilentlyContinue #get the volume details
$IsMatch = ($USBWhiteList|? {$Volume.FileSystemLabel -like $_}).Count -gt 0 #does it match our whitelist?
if (($EventType -eq [EventNames]::Inserted) -and $IsMatch){ #if a disk was inserted which matches the whitelist...
Write-Host "Volume $($Volume.DriveLetter): '$($Volume.FileSystemLabel)', Found, Wiping!" -Fore Green
Start-Job -ScriptBlock { param ($Volume) #Perform the wipe inside a job
$Disk = Get-Partition -DriveLetter $Volume.DriveLetter | Get-Disk
Clear-Disk -Number $Disk.Number -RemoveData -Confirm:$false
New-Partition -DiskNumber $Disk.Number -UseMaximumSize -IsActive -DriveLetter $Volume.DriveLetter
Format-Volume -FileSystem NTFS -DriveLetter $Volume.DriveLetter -Confirm:$false
Add-Type -AssemblyName 'System.Windows.Forms' #warn (globally) when it is finished, don't need to run wait/recieve job.
[System.Windows.Forms.MessageBox]::Show("Finished Wiping Disk $($Volume.DriveLetter)","Please Remove Disk")
} -ArgumentList $Volume | Out-Null
} else {
Write-Host "Ignoring" -Fore Red
}
Remove-Event -SourceIdentifier volumeChange
} while (1) #this should be modified to quit after x disks or something, the below commands won't get exec'd - could also use a Try/Finally and Ctrl+C the script.
Get-Job | Remove-Job -Force
Unregister-Event -SourceIdentifier volumeChange
Related
I'm trying to run Core Keeper Dedicated Server, everything seems fine, but from time to time CoreKeeperServer.exe crashes. So main Powershell script what launches it not tracking is it crashed or not. How do i need to modify this script, so it constantly (every 1-5 seconds or so) monitored if process still running or not, and start it again if it is? Also i want to keep "press q to exit" functionality what already included in stock script.
Stock script:
# Feel free to change these (see README), but keep in mind that changes to this file might be overwritten on update
$CoreKeeperArguments = #("-batchmode", "-logfile", "CoreKeeperServerLog.txt") + $args
$script:ckpid = $null
function Quit-CoreKeeperServer {
if ($script:ckpid -ne $null) {
taskkill /pid $ckpid.Id
Wait-Process -InputObject $ckpid
Write-Host "Stopped CoreKeeperServer.exe"
}
}
try {
if (Test-Path -Path "GameID.txt") {
Remove-Item -Path "GameID.txt"
}
$script:ckpid = Start-Process -PassThru -FilePath %0\..\CoreKeeperServer.exe -ArgumentList $CoreKeeperArguments
Write-Host "Started CoreKeeperServer.exe"
# Wait for GameID
while (!(Test-Path -Path "GameID.txt")) {
Start-Sleep -Milliseconds 100
}
Write-Host -NoNewline "Game ID: "
Get-Content "GameID.txt"
Write-Host "Press q to quit, DON'T close the window or the server process will just keep running"
While ($KeyInfo.VirtualKeyCode -eq $Null -or $KeyInfo.VirtualKeyCode -ne 81) {
$KeyInfo = $Host.UI.RawUI.ReadKey("NoEcho, IncludeKeyDown")
}
}
finally {
Quit-CoreKeeperServer
pause
}
Solved problem like this:
# Feel free to change these (see README), but keep in mind that changes to this file might be overwritten on update
$CoreKeeperArguments = #("-batchmode", "-logfile", "CoreKeeperServerLog.txt") + $args
$script:ckpid = $null
function Quit-CoreKeeperServer {
if ($script:ckpid -ne $null) {
taskkill /pid $ckpid.Id
Wait-Process -InputObject $ckpid
Write-Host "Stopped CoreKeeperServer.exe"
}
}
try {
if (Test-Path -Path "GameID.txt") {
Remove-Item -Path "GameID.txt"
}
$script:ckpid = Start-Process -PassThru -FilePath %0\..\CoreKeeperServer.exe -ArgumentList $CoreKeeperArguments
Write-Host "Started CoreKeeperServer.exe"
# Wait for GameID
while (!(Test-Path -Path "GameID.txt")) {
Start-Sleep -Milliseconds 100
}
Write-Host -NoNewline "Game ID: "
Get-Content "GameID.txt"
Write-Host "Server is runnig. Press CTRL + C to stop."
While ($true) {
if (-not (Get-Process -Id $ckpid.Id -ErrorAction SilentlyContinue)) {
Write-Host “Server is died restarting...”
Get-Date
$script:ckpid = Start-Process -PassThru -FilePath %0\..\CoreKeeperServer.exe -ArgumentList $CoreKeeperArguments
Write-Host "Started CoreKeeperServer.exe"
Write-Host -NoNewline "Game ID: "
Get-Content "GameID.txt"
Write-Host "Server is runnig. Press CTRL + C to stop."
sleep 1
}
}
}
finally {
Quit-CoreKeeperServer
pause
}
Is there a way to "invoke-command" to a remote computer such that I can reboot my computer and the job will still be running, and I can check the output log whenever I want?
PS> invoke-command -Computer Remote1 -File "ScriptThatRunsFor7days.ps1"
PS> restart-computer
PS> # Hey where's my job on remote computer? Can i see it running and connect to
# its output after rebooting my computer?
Isn't it easier to just register a scheduled task that runs the script on the remote computer?
For logging just use the cmdlet Start-Transcript at the top of the script.
I made a script not to long ago to easely register scheduled tasks on a remote computer. Maybe you can try out and see if it works for you?
[CmdletBinding()]
param(
[parameter(Mandatory=$true)]
[string]
$PSFilePath,
[parameter(Mandatory=$true)]
[string]
$TaskName,
[parameter(Mandatory=$true)]
[string]
$ComputerName
)
$VerbosePreference="Continue"
New-Variable -Name ScriptDestinationFolder -Value "Windows\PowershellScripts" -Option Constant -Scope Script
New-Variable -Name ScriptSourcePath -Value $PSFilePath -Option Constant -Scope Script
Write-Verbose "Script sourcepath: $ScriptSourcePath"
New-Variable -Name PSTaskName -Value $TaskName -Option Constant -Scope Script
Write-Verbose "TaskName: $TaskName"
$File = Split-Path $ScriptSourcePath -leaf
Write-Verbose "Filename: $File"
New-Variable -Name PSFileName -Value $File -Option Constant -Scope Script
Write-Verbose "PSFileName: $PSFileName"
$ExecutionTime = New-TimeSpan -Hours 8
Write-Verbose "Execution time: $ExecutionTime hours"
Invoke-Command -ComputerName $ComputerName -ScriptBlock {
$VerbosePreference="Continue"
#Removing old Scheduled Task
Write-Verbose "Unregistering old scheduled task.."
Stop-ScheduledTask -TaskName $Using:PSTaskName -ErrorAction SilentlyContinue
Unregister-ScheduledTask -TaskName $Using:PSTaskName -Confirm:$false -ErrorAction SilentlyContinue
#Creating destination directory for Powershell script
$PSFolderPath = "C:" , $Using:ScriptDestinationFolder -join "\"
Write-Verbose "Creating folder for script file on client: $PSFolderPath"
New-Item -Path $PSFolderPath -ItemType Directory -Force
#Scheduled Task definitions
$Trigger = New-ScheduledTaskTrigger -Daily -At "8am"
$PSFilePath = "C:", $Using:ScriptDestinationFolder , $Using:PSFileName -join "\"
Write-Verbose "Setting path for script file to destination folder on client: $PSFilePath"
$Action = New-ScheduledTaskAction -Execute PowerShell -Argument "-File $PSFilePath"
$Principal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U
$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -ExecutionTimeLimit $Using:ExecutionTime -StartWhenAvailable
$Task = Register-ScheduledTask -TaskName $Using:PSTaskName -Principal $Principal -Action $Action -Settings $Settings -Trigger $Trigger
$Task = Get-ScheduledTask -TaskName $Using:PSTaskName
$Task.Triggers[0].EndBoundary = [DateTime]::Now.AddDays(90).ToString("yyyyMMdd'T'HH:mm:ssZ")
Write-Verbose "Trigger expiration date set to: $Task.Triggers[0].EndBoundary"
$Task.Settings.DeleteExpiredTaskAfter = 'P1D'
Write-Verbose "Scheduled task will be deleted after $Task.Settings.DeleteExpiredTaskAfter after expiry."
$Task | Set-ScheduledTask -ErrorAction SilentlyContinue
} #End Invoke-Command
#Copy script file from source to the computer
$ScriptDestination = "\" , $ComputerName , "C$", $ScriptDestinationFolder -join "\"
Write-Verbose "Script destination is set to: $ScriptDestination"
Write-Verbose "Copying script file: `"$ScriptSourcePath`" to `"$ScriptDestination`""
Copy-Item -Path $ScriptSourcePath -Destination $ScriptDestination -Force
Usage:
Create-ScheduledTask-Test.ps1 -ComputerName MyRemoteComputer -PSFilePath "ScriptToRun.ps1" -TaskName DoSomeWork
Something with scheduled jobs. I'm copying the script to the remote computer using a pssession.
$s = new-pssession remote1
copy-item script.ps1 c:\users\admin\documents -tosession $s
invoke-command $s { Register-ScheduledJob test script.ps1 -Runnow }
And then later, only when it starts running, it will automatically appear as a regular job on the remote computer:
invoke-command remote1 { get-job | receive-job -keep }
vonPryz provided the crucial pointer:
On Windows, PowerShell offers disconnected remote sessions that allow you to reconnect and collect output later, from any client session, even after a logoff or reboot - assuming that the disconnected session on the remote computer hasn't timed out.
See the conceptual about_Remote_Disconnected_Sessions help topic.
The following sample script demonstrates the approach:
Save it to a *.ps1 file and adapt the $computerName and $sessionName variable values.
The script assumes that the current user identity can be used as-is to remote into the target computer; if that is not the case, add a -Credential argument to the Invoke-Command and Get-PSSession calls.
Invoke the script and, when prompted, choose when to connect to the disconnected remote session that was created - including after a logoff / reboot, in which case the script is automatically reinvoked in order to connect to the disconnected session and retrieve its output.
See the source-code comments for details, particularly with respect to the idle timeout.
One aspect not covered below is output buffering: a disconnected session that runs for a long time without having its output retrieved can potentially accumulate a lot of output. By default, if the output buffer fills up, execution is suspended. The OutputBufferingMode session option controls the behavior - see the New-PSSessionOption cmdlet.
The gist of the solution is:
An Invoke-Command call with the -InDisconnectedSession switch that starts an operation on a remote computer in an instantly disconnected session. That is, the call returns as soon as the operation was started without returning any results from the operation yet (it returns information about the disconnected session instead).
A later Receive-PSSession call - which may happen after a reboot - implicitly connects to the disconnected session and retrieves the results of the operation.
$ErrorActionPreference = 'Stop'
# ADAPT THESE VALUES AS NEEDED
$computer = '???' # The remote target computer's name.
$sessionName = 'ReconnectMe' # A session name of your choice.
# See if the target session already exists.
$havePreviousSession = Get-PSSession -ComputerName $computer -Name $sessionName
if (-not $havePreviousSession) {
# Create a disconnected session with a distinct custom name
# with a command that runs an output loop indefinitely.
# The command returns instantly and outputs a session-information object
# for the disconnected session.
# Note that [int]::MaxValue is used to get the maximum idle timeout,
# but the effective value is capped by the value of the remote machine's
# MaxIdleTimeoutMs WSMan configuration item, which defaults to 12 hours.
Write-Verbose -vb "Creating a disconnected session named $sessionName on computer $computer..."
$disconnectedSession =
Invoke-Command -ComputerName $computer -SessionName $sessionName -InDisconnectedSession -SessionOption #{ IdleTimeout=[int]::MaxValue } { while ($true) { Write-Host -NoNewLine .; Start-Sleep 1 } }
# Prompt the user for when to connect and retrieve the output
# from the disconnected session.
do {
$response = Read-Host #"
---
Disconnected session $sessionName created on computer $computer.
You can connect to it and retrieve its output from any session on this machine,
even after a reboot.
* If you choose to log off or reboot now, this script re-runs automatically
when you log back in, in order to connect to the remote session and collect its output.
* To see open sessions on the target computer on demand, run the following
(append | Remove-PSSession to remove them):
Get-PSSession -ComputerName $computer
---
Do you want to (L)og off, (R)eboot, (C)onnect right now, or (Q)uit (submit with ENTER)? [l/r/c/q]
"#
} while (($response = $response.Trim()) -notin 'l', 'r', 'c', 'q')
$autoRelaunchCmd = {
Set-ItemProperty registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce 'ReconnectDemo' "$((Get-Command powershell.exe).Path) -noexit -command `"Set-Location '$PWD'; . '$PSCommandPath'`""
}
switch ($response) {
'q' { Write-Verbose -vb 'Aborted.'; exit 2 }
'c' { break } # resume below
'l' {
Write-Verbose -vb "Logging off..."
& $autoRelaunchCmd
logoff.exe
exit
}
'r' {
Write-Verbose -vb "Rebooting..."
& $autoRelaunchCmd
Restart-Computer
exit
}
}
}
# Getting here means that a remote disconnection session was previously created.
# Reconnect and retrieve its output.
# Implicitly reconnect to the session by name,
# and receive a job object representing the remotely running command.
# Note: Despite what the docs say, -OutTarget Job seems to be the default.
# Use -Output Host to directly output the results of the running command.
Write-Verbose -vb "Connecting to previously created session $sessionName on computer $computer and receiving its output..."
$job = Receive-PSSession -ComputerName $computer -Name $sessionName -OutTarget Job
# Get the output from the job, timing out after a few seconds.
$job | Wait-Job -Timeout 3
$job | Remove-Job -Force # Forcefully terminate the job with the indefinitely running command.
# Remove the session.
Write-Host
Write-Verbose -Verbose "Removing remote session..."
Get-PSSession -ComputerName $computer -Name $sessionName | Remove-PSSession
function remote_nohup {
param(
[string]$Machine,
[string]$Cmd
)
$job_tstamp = $(get-date -f MMdd_HHmm_ss)
$job_name = "${job_tstamp}"
$job_dir_start = (Get-Location).Path
$job_dir_sched = "$env:userprofile/Documents/jobs"
$job_file = "${job_dir_sched}/${job_name}.run.ps1"
$job_log = "${job_dir_sched}/${job_name}.log"
$job_computer = $Machine
$job_cmd = $Cmd
# Create Job File
$job_ps1 = #(
"`$ErrorActionPreference = `"Stop`""
""
"Start-Transcript -path $job_log -append"
""
"try {"
" write-host 'job_begin:($job_name)'"
" "
" set-location $job_dir_start -EA 0"
" "
" write-host 'job_cmd:($job_cmd)'"
" $job_cmd | out-host"
""
" write-host 'job_end:($job_name)'"
"}"
"catch {"
" `$msg = `$_"
" write-host `$msg"
" write-error `$msg"
"}"
"finally {"
" Stop-Transcript"
"}"
""
"Exit-PSSession"
)
try {
New-Item -ItemType Directory -Force -EA:0 -Path $job_dir_sched | out-null
copy-Item $remote_profile_ps1 $job_profile
write-host "Creating File: $job_file"
$f1 = open_w $job_file -fatal
foreach ($line in $job_ps1) {
$f1.WriteLine($line)
}
}
finally {
$f1.close()
}
# Create Jobs Dir
write-host "Creating remote job Directory"
Invoke-Command -Computer $job_computer -ScriptBlock {
New-Item -ItemType Directory -Force -EA:0 -Path $using:job_dir_sched | out-null
}
# Copy Job File
write-host "copy-Item -recurse -ToSession `$s2 $job_file $job_file"
$s2 = New-PSSession -Computer $job_computer
copy-Item -ToSession $s2 $job_file $job_file
Receive-PSSession -Session $s2
Remove-PSSession -Session $s2
# Create Persistent Background Job
write-host "Submitting job to remote scheduler"
Invoke-Command -Computer $job_computer -ScriptBlock {
Register-ScheduledJob -RunNow -Name $using:job_name -File $using:job_file
Exit-PSSession
}
# NOTE: Log file from run is placed on
# remote computer under jobs dir
}
function open_w {
param([string]$path, [switch]$fatal)
try {
write-host "path: $path"
$pathfix = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path)
$handle = [System.IO.StreamWriter]::new($pathfix, $false) #false:over-write, true:append
}
catch {
if ($fatal) {
write-host -ForegroundColor Red "EXCEPTION: " + $PSItem.ToString()
exit 1
}
return $null
}
return $handle
}
UPDATE
I'm not struggeling with this error An event with the name 'StateChanged' does not exist - See the full code at TRYOUTS (the last code piece) below
original text
As mentioned before, I'm doing a code for automatically format and transfer files to multiple USB thumbdrives. And it´s done in parallel/async
Now the issue is that I want to output the drive letter of each USB as they completes / job is done. That letter is stored in a var/param inside the job function, but I don't know how to write it out when job is done.
I have an Register-ObjectEvent that fires when each USB is completed in formatting and transfering.
The script works really well and writes out the line USB slot = Job...
But I want to have my $driveLetter from inside $formatDrive written out in the line USB slot = $driveLetter.
Heres my code. Look for the lines $formatDrive and $jobEvent.
#Requires -version 2.0
ipmo storage
Register-WmiEvent -Class win32_VolumeChangeEvent -SourceIdentifier volumeChange
$formatDrive = {
Param($driveLetter)
Write-Host (Get-Date -Format s) "Erase disk..."
$source = "C:\Users\myname\Desktop\test"
Format-Volume -DriveLetter $driveLetter[0] -NewFileSystemLabel "test30" -FileSystem exFAT -Confirm:$false
robocopy $source $driveLetter /S
return $driveLetter
}
Write-Host (Get-Date -Format s) " Beginning script..."
do {
$newEvent = Wait-Event -SourceIdentifier volumeChange
$eventType = $newEvent.SourceEventArgs.NewEvent.EventType
$eventTypeName = switch ($eventType) {
1 {"Configuration changed"}
2 {"Device arrival"}
3 {"Device removal"}
4 {"docking"}
}
if ($eventType -eq 2) {
$driveLetter = $newEvent.SourceEventArgs.NewEvent.DriveName
$driveLabel = ([wmi]"Win32_LogicalDisk='$driveLetter'").VolumeName
Write-Host (Get-Date -Format s) "USB igang = " $driveLetter
# Execute process if drive matches specified condition(s)
$formatDrivejob = Start-Job -ScriptBlock $formatDrive -ArgumentList $driveLetter
$jobEvent = Register-ObjectEvent $formatDrivejob StateChanged -Action {
Write-Host (Get-Date -Format s) (' USB slot = Job #{0} ({1}) complete.' -f $sender.Id, $sender.Name)
[media.SystemSounds]::("Hand").Play()
$jobEvent | Unregister-Event
}
}
Remove-Event -SourceIdentifier volumeChange
} while (1-eq1) #Loop until next event
Unregister-Event -SourceIdentifier volumeChange
I've tried just writing it out inside the end of $formatDrive - which didn't work as it's running in background.
Also tried with return as you can see in $formatDrive
TRYOUTS:
#Requires -version 2.0
ipmo storage
Register-WmiEvent -Class win32_VolumeChangeEvent -SourceIdentifier volumeChange
$formatDrive = {
param($driveLetter)
write-host (get-date -format s) "Erase disk..."
$source = "C:\Users\jbh\Desktop\test"
Format-Volume -Driveletter $driveLetter[0] -NewFileSystemLabel "test30" -FileSystem exFAT -Confirm:$false
robocopy $source $driveLetter /S
return $driveLetter
}
write-host (get-date -format s) " Beginning script..."
do{
$newEvent = Wait-Event -SourceIdentifier volumeChange
$eventType = $newEvent.SourceEventArgs.NewEvent.EventType
$eventTypeName = switch($eventType)
{
1 {"Configuration changed"}
2 {"Device arrival"}
3 {"Device removal"}
4 {"docking"}
}
# initialize the array
$formatDrivejob = #()
if ($eventType -eq 2) {
$driveLetter = $newEvent.SourceEventArgs.NewEvent.DriveName
$driveLabel = ([wmi]"Win32_LogicalDisk='$driveLetter'").VolumeName
Write-Host (Get-Date -Format s) "USB igang = " $driveLetter
# Execute process if drive matches specified condition(s)
$formatDrivejob += Start-Job -ScriptBlock $formatDrive -ArgumentList $driveLetter
$jobEvent = Register-ObjectEvent $formatDrivejob StateChanged -Action {
Write-Host (Get-Date -Format s) (' USB slot = Job #{0} ({1}) complete.' -f $sender.Id, $sender.Name)
[media.SystemSounds]::("Hand").Play()
foreach ($i in $formatDrivejob){
if ($i.State -eq "Completed")
{
$letter = $formatDrivejob | receive-job | select -Last 1
Write-Host "Drive has finished:" $letter
$formatDrivejob.Remove($i)
}
}
$jobEvent | Unregister-Event
}
}
Remove-Event -SourceIdentifier volumeChange
} while (1-eq1) #Loop until next event
Unregister-Event -SourceIdentifier volumeChange
You can use Receive-job to get the results:
# initialize the array
[System.Collections.ArrayList]$formatDrivejob
if ($eventType -eq 2) {
$driveLetter = $newEvent.SourceEventArgs.NewEvent.DriveName
$driveLabel = ([wmi]"Win32_LogicalDisk='$driveLetter'").VolumeName
Write-Host (Get-Date -Format s) "USB igang = " $driveLetter
# Execute process if drive matches specified condition(s)
$formatDrivejob += Start-Job -ScriptBlock $formatDrive -ArgumentList $driveLetter
$jobEvent = Register-ObjectEvent $formatDrivejob StateChanged -Action {
Write-Host (Get-Date -Format s) (' USB slot = Job #{0} ({1}) complete.' -f $sender.Id, $sender.Name)
[media.SystemSounds]::("Hand").Play()
foreach ($i in $formatDrivejob){
if ($i.State -eq "Completed")
{
$letter = $formatDrivejob | receive-job | select -Last 1
Write-Host "Drive has finished:" $letter
$formatDrivejob.Remove($i)
}
}
$jobEvent | Unregister-Event
}
}
Maybe you can try an approach like this:
$DrivesToHandle = 5
$formatDrivejob = #()
Do {
if ($eventType -eq 2) {
$DrivesToHandle -= 1
$driveLetter = $newEvent.SourceEventArgs.NewEvent.DriveName
$driveLabel = ([wmi]"Win32_LogicalDisk='$driveLetter'").VolumeName
write-host (Get-Date -format s) "USB igang = $driveLetter"
# Execute process if drive matches specified condition(s)
$formatDrivejob += Start-Job -ScriptBlock $formatDrive -ArgumentList $driveLetter
}
} while ($DrivesToHandle -ne 0)
# Wait for all jobs to finish
$formatDrivejob | Wait-Job | Out-Null
# Retrieve the job results
$JobResults = $formatDrivejob | Receive-Job
foreach ($Job in $JobResults) {
Write-Host (get-date -format s) "Job result of job ID $($Job.Id): " $Job
}
# Remove all jobs
$formatDrivejob | Remove-Job -Force
When you work with jobs you can use Wait-Job to wait for the job to finish. But you also have to retrieve the job results, that's done by using the Receive-Job CmdLet.
I need to output several logfiles - while they are written - to the shell.
In the unix version of my script this is achieved by tail -f output\worker*.log. Note the wildcard.
In Powershell I tried Get-Content -Path "output\worker*.log" -Wait, but this only prints the first logfile it can find to the shell.
For completion, here is my code where i call my program:
foreach ($worker in $workers)
{
Write-Host " Start $worker in background"
$block = {& $args[0] $args[1] $args[2] $args[3] $args[4] $args[5] $args[6] 2> $args[7] > $args[8]}
start-job -name $worker -scriptblock $block -argumentlist `
"$strPath\worker\bin\win32\php.exe", `
"-q", `
"-c", `
"$strPath\worker\conf\php_win32.ini", `
"$strPath\worker\bin\os-independant\logfilefilter\logfilefilter.php", `
"-f", `
"$strPath\worker\$worker\conf\logfilefilter-$worker.xml", `
"$strPath\output\$worker-error.log", `
"$strPath\output\$worker.log"
}
Get-Content -Path "output\worker*.log" -Wait
In my test case there are 8 workers and logfiles( output\worker01.log, output\worker02.log, output\worker03.log, output\worker04.log, output\worker05.log, output\worker06.log, output\worker07.log, output\worker08.log )
Is there a workaround to output all these logfiles? Or is it possible to duplicate the stdout stream from the background process to print it in the shell?
You can duplicate the the output streams by reading them directly from the output buffers of the child jobs.
Here's a demo script. Note that for the Verbose output, I'm removing output from the buffer as it's read, so that it doesn't get re-displayed on subsequent reads. This doesn't appear to have any affect on the Receive Job buffers for the job. If you do a Receive Job on it after the script completes, you'll still get the Verbose output all over again.
$sb = {
$VerbosePreference = 'Continue'
for ($i = 1; $i -le 100; $i++ )
{
start-sleep -Milliseconds 150
Write-Verbose "$(get-date)"
write-progress -activity "Search in Progress" -status "$i% Complete:" -percentcomplete $i;}
}
$job = start-job -ScriptBlock $sb
$verbose = ($job.ChildJobs[0].Verbose)
While ($job.State -ne 'Completed')
{
$job.ChildJobs |
foreach {
Start-Sleep -seconds 1
$Pct_Complete = $_.Progress | select -last 1 | select -ExpandProperty PercentComplete
Write-Host "`rBackground job $($_.ID) is $Pct_Complete percent completed." -ForegroundColor Cyan
While ($verbose.count){
Write-Host $verbose[0] -ForegroundColor Gray
$verbose.removeat(0)}
}
}
write-host "`nDone!"
I have power shell script which will perform multiple tasks like uninstalling,removing and again installing wsp solutions.I am facing an issue like the first process is taking too long time for uninstalling solution so that other process has to wait till the first action has to get completed fully.Now i am giving sleep time but it has some problem while different machines speed gets varies.. I also aware about calling notepad like an external function but i dont want that has to happen here.Apart from that any solutions are availabe like I need to wait for first process to get complete before starting the second process.
$InstallDIR = "F:\new\source\UpdatedWSPFiles"
$Dir = get-childitem $InstallDIR -Recurse
$WSPList = $Dir | where {$_.Name -like "*.wsp*"}
Foreach ($wsp in $WSPList )
{
$WSPFullFileName = $wsp.FullName
$WSPFileName = $wsp.Name
try
{
Write-Host -ForegroundColor White -BackgroundColor Blue "Working on $WSPFileName"
Write-Host -ForegroundColor Green "Retracting Solution"
Uninstall-SPSolution -AllWebApplications -Identity "$WSPFileName" -Confirm:$false
sleep 100
Write-Host -ForegroundColor Green "Removing Solution from farm"
Remove-SPSolution -Identity "$WSPFileName" -Confirm:$false -Force
sleep 60
Write-Host -ForegroundColor Green "Adding solution to farm"
Add-SPSolution "$WSPFullFileName" -Confirm:$false
sleep 60
}
you can try start-process with the -wait switch :
PS> $p="C:\windows\System32\WindowsPowerShell\v1.0\powershell.exe"
PS> $params="-command &{ if ((Get-PSSnapin `"Microsoft.SharePoint.PowerShell`" -ErrorAction SilentlyContinue) -eq $null)
{
Add-PSSnapin `"Microsoft.SharePoint.PowerShell`"
}
Uninstall-SPSolution -AllWebApplications -Identity `"$WSPFileName`" -Confirm:$false}"
PS> Start-Process $p $params -Wait
Guess you could try
Start-Job -Name "jobname" -ScriptBlock { Uninstall-SPSolution -AllWebApplications -Identity "$WSPFileName" -Confirm:$false }
and
Wait-Job -Name "jobname" -Timeout "maximum wait time"