I am pretty new to powershell (About 1 week in) and I am trying to create a tool for our helpdesk to import and export printers. The tool is running great except for the form is freezing when the code is being run.
To mitigate the freezing, I found that running it as a job gets the task done, however I am having 2 issues with it.
I am not able to get the progress bar to increase 1 step as a result of the job completing.
I am not able to pass variables to it. (I am not as worried about this as there is a ton of information on it, I just need to figure out the syntax for it. If you could help with that as well, that would be great though.)
start-job -scriptblock {
C:\Windows\system32\spool\tools\PrintBrm.exe -b -f \\filestore\$EXPORTPRINTERS.printerExport
$progressbarexportprinters.PerformStep()
$progressbarexportprinters.TextOverlay = "Printer Export Complete"
}
I found a solution for this. the form is still freezing, but I can show movement on the progress bar. Which will be good enough.
C:\Windows\system32\spool\tools\PrintBrm.exe -r -f \\filestore\$EXPORTPRINTERS.printerExport | out-string -Stream | foreach-object {
$richTextBox1.lines = $richTextBox1.lines + $_
$richTextBox1.Select($richTextBox1.Text.Length, 0)
$richTextBox1.ScrollToCaret()
$progressbaraddprinters.PerformStep()
}
Related
I have a bunch of PDF files that I would like to print in sequence on a windows 7 computer using Powershell.
get-childItem "*.pdf" | sort lastWriteTime | foreach-object {start-process $._Name -verb 'print'}
The printed files are sometimes out of order like 1) A.pdf, 2) C.pdf, 3) B.pdf 4) D.pdf.
Different trials printed out a different sequence of files, thus, I fear the error is related to the printing queue or the start-process command. My guess is that each printing process is fired without waiting for the previous printing process to be completed.
Is there a way to consistently print out PDF files in a sequence that I specify?
You are starting the processes in order, but by default Start-Process does not wait until the command completes before it starts the next one. Since the commands take different amounts of time to complete based on the .PDF file size they print in whatever order they finish in. Try adding the -wait switch to your Start-Process, which will force it to wait until the command completes before starting the next one.
EDIT: Found an article elsewhere on Stack which addresses this. Maybe it will help. https://superuser.com/questions/1277881/batch-printing-pdfs
Additionally, there are a number of PDF solutions out there which are not Adobe, and some of them are much better for automation than the standard Reader. Adobe has licensed .DLL files you can use, and the professional version of Acrobat has hooks into the back end .DLLs as well.
If you must use Acrobat Reader DC (closed system or some such) then I would try opening the file to print and getting a pointer to the process, then waiting some length of time, and forcing the process closed. This will work well if your PDF sizes are known and you can estimate how long it takes to finish printing so you're not killing the process before it finishes. Something like this:
ForEach ($PDF in (gci "*.pdf"))
{
$proc = Start-Process $PDF.FullName -PassThru
Start-Sleep -Seconds $NumberOfSeconds
$proc | Stop-Process
}
EDIT #2: One possible (but untested) optimization is that you might be able use the ProcessorTime counters $proc.PrivilegedProcessorTime and $proc.UserProcessorTime to see when the process goes idle. Of course, this assumes that the program goes completely idle after printing. I would try something like this:
$LastPrivTime = 0
$LastUserTime = 0
ForEach ($PDF in (gci "*.pdf"))
{
$proc = Start-Process $PDF.FullName -PassThru
Do
{
Start-Sleep -Seconds 1
$PrivTimeElapsed = $proc.PrivilegedProcessorTime - $LastPrivTime
$UserTimeElapsed = $proc.UserProcessorTime - $LastUserTime
$LastPrivTime = $proc.PrivilegedProcessorTime
$LastUserTime = $proc.UserProcessorTime
}
Until ($PrivTimeElapsed -eq 0 -and $UserTimeElapsed -eq 0)
$proc | Stop-Process
}
If the program still ends too soon, you might need to increase the # of seconds to sleep inside the inner Do loop.
I'm just wondering if I can clear Out-Gridview on every loop like I can in the console:
while (1) { ps | select -first 5; sleep 1; clear-host }
Unfortunately this doesn't clear out-gridview every time:
& { while (1) { ps | select -first 5; sleep 1; clear-host } } | out-gridview
Clear-Host clears the display of the host, which is the console window's content in a regular PowerShell console.
By contrast, Out-GridView is a separate GUI window, over which PowerShell offers no programmatic display once it is being displayed.
Notably, you can neither clear no refresh the window's content after it is displayed with the initial data.
The best approximation of this functionality is to close the old window and open a new one with the new data in every iteration - but note that this will be visually disruptive.
In the simplest case, move the Out-GridView into the loop and call it with -Wait, which requires you to close it manually in every iteration, however:
# NOTE: Doesn't move to the next iteration until you manually close the window.
while (1) { ps | select -first 5 | Out-GridView -Wait }
This answer shows how to implement an auto-closing Out-GridView window, but it is a nontrivial effort - and with a sleep period as short as 1 second it will be too visually disruptive.
Ultimately, what you're looking for is a GUI version of the Unix watch utility (or, more task-specifically, the top utility).
However, since you're not looking to interact with the Out-GridView window, there's little benefit to using Out-GridView in this case.
Instead, you could just spawn a new console window that uses Clear-Host to display the output in the same screen position periodically:
The following defines helper function Watch-Output to facilitate that:
# Simple helper function that opens a new console window and runs
# the given command periodically, clearing the screen beforehand every time.
function Watch-Output ([scriptblock] $ScriptBlock, [double] $timeout = 1) {
$watchCmd = #"
while (1) {
Clear-Host
& { $($ScriptBlock -replace '"', '\"') } | Out-Host
Start-Sleep $timeout
}
"# #'
Start-Process powershell.exe "-command $watchCmd"
}
# Invoke with the command of interest and a timeout.
Watch-Output -ScriptBlock { ps | Select -First 5 } -Timeout 1
Note that this will still flicker every time the window content is refreshed.
Avoiding that would require substantially more effort.
The PowerShellCookbook module offers the sophisticated Watch-Command cmdlet, which not only avoids the flickering but also offers additional features.
The big caveat is that - as of version 1.3.6 - the module has several cmdlets that conflict with built-in ones (Format-Hex, Get-Clipboard, New-SelfSignedCertificate, Send-MailMessage, Set-Clipboard), and the only way to import the module is to allow the module's commands to override the built-in ones (Import-Module PowerShellCookbook -AllowClobber).
I have a script that runs several loops of code and relies on specific input at various phases in order to advance. That functionality is working. My current issue revolves around extraneous input being supplied by the user displaying on screen in the console window wherever I have the cursor position currently aligned.
I have considered ignoring this issue since the functionality of the script is intact, however, I am striving for high standards with the console display of this script, and I would like to know a way to disable all user input period, unless prompted for. I imagine the answer has something to do with being able to command the Input Buffer to store 0 entries, or somehow disabling and then re-enabling the keyboard as needed.
I have tried using $HOST.UI.RawUI.Flushinputbuffer() at strategic locations in order to prevent characters from displaying, but I don't think there's anywhere I could put that in my loop that will perfectly block all input from displaying during code execution (it works great for making sure nothing gets passed when input is required, though). I've tried looking up the solution, but the only command I could find for manipulating the Input Buffer is the one above. I've also tried strategic implementation of the $host.UI.RawUI.KeyAvailable variable to detect keystrokes during execution, then $host.UI.RawUI.ReadKey() to determine if these keystrokes are unwanted and do nothing if they are, but the keystrokes still display in the console no matter what.
I am aware that this code is fairly broken as far as reading the key to escape the loop goes, but bear with me. I hashed up this example just so that you could see the issue I need help eliminating. If you hold down any letter key during this code's execution, you'll see unwanted input displaying.
$blinkPhase = 1
# Set Coordinates for cursor
$x = 106
$y = 16
$blinkTime = New-Object System.Diagnostics.Stopwatch
$blinkTime.Start()
$HOST.UI.RawUI.Flushinputbuffer()
do {
# A fancy blinking ellipses I use to indicate when Enter should be pressed to advance.
$HOST.UI.RawUI.Flushinputbuffer()
while ($host.UI.RawUI.KeyAvailable -eq $false) {
if ($blinkTime.Elapsed.Milliseconds -gt 400) {
if ($blinkPhase -eq 1) {
[console]::SetCursorPosition($x,$y)
write-host ". . ." -ForegroundColor gray
$blinkPhase = 2
$blinkTime.Restart()
} elseif ($blinkPhase -eq 2) {
[console]::SetCursorPosition($x,$y)
write-host " "
$blinkPhase = 1
$blinkTime.Restart()
}
}
start-sleep -m 10
}
# Reading for actual key to break the loop and advance the script.
$key = $host.UI.RawUI.ReadKey()
} while ($key.key -ne "Enter")
The expected result is that holding down any character key will NOT display the input in the console window while the ellipses is blinking. The actual result, sans error message, is that a limited amount of unwanted/unnecessary input IS displaying in the console window, making the script look messy and also interfering with the blinking process.
What you're looking for is to not echo (print) the keys being pressed, and that can be done with:
$key = $host.UI.RawUI.ReadKey('IncludeKeyDown, NoEcho')
Also, your test for when Enter was pressed is flawed[1]; use the following instead:
# ...
} while ($key.Character -ne "`r")
Caveat: As of at least PSReadLine version 2.0.0-beta4, a bug causes $host.UI.RawUI.KeyAvailable to report false positives, so your code may not work as intended - see this GitHub issue.
Workaround: Use [console]::KeyAvailable instead, which is arguably the better choice anyway, given that you're explicitly targeting a console (terminal) environment with your cursor-positioning command.
As an aside: You can simplify and improve the efficiency of your solution by using a thread job to perform the UI updates in a background thread, while only polling for keystrokes in the foreground:
Note: Requires the ThreadJob module, which comes standard with PowerShell Core, and on Windows PowerShell can be installed with Install-Module ThreadJob -Scope CurrentUser, for instance.
Write-Host 'Press Enter to stop waiting...'
# Start the background thread job that updates the UI every 400 msecs.
# NOTE: for simplicity, I'm using a simple "spinner" here.
$jb = Start-ThreadJob {
$i=0
while ($true) {
[Console]::Write("`r{0}" -f '/-\|'[($i++ % 4)])
Start-Sleep -ms 400
}
}
# Start another thread job to do work in the background.
# ...
# In the foreground, poll for keystrokes in shorter intervals, so as
# to be more responsive.
While (-not [console]::KeyAvailable -or ([Console]::ReadKey($true)).KeyChar -ne "`r" ) {
Start-Sleep -Milliseconds 50
}
$jb | Remove-Job -Force # Stop and remove the background UI thread.
Note the use of [Console]::Write() in the thread job, because Write-Host output wouldn't actually be passed straight through to the console.
[1] You tried to access a .Key property, which only the [SystemConsoleKeyInfo] type returned by [console]::ReadKey() has; the approximate equivalent in the $host.UI.rawUI.ReadKey() return type, [System.Management.Automation.Host.KeyInfo], is .VirtualKeyCode, but its specific type differs, so you can't (directly) compare it to "Enter"; The latter type's .Character returns the actual [char] instance pressed, which is the CR character ("`r") in the case of Enter.
I'm putting together a little demo where I want one console window to show a current status. Here's my first iteration:
clear-host; while (1 -eq 1) { git log --graph --oneline; start-sleep -s 1; clear}
Unfortunately, this results in the text "flashing" on the screen every second as the screen clears, then is replaced with the next result. No only is the aesthetically unpleasing, it is albeit rare, potentially dangerous for some unsuspecting viewers. So my next try was this:
clear-host; while (1 -eq 1) { git log --graph --oneline; start-sleep -s 1; [Console]::CursorTop=0;[Console]::CursorLeft=0;}
There is no flash now, but longer lines are not cleared just drag down the screen.
Can anyone think of a way to get the best of both worlds? Clear the screen when I have too, but no repeating blink?
You might be able to continue along the path of console cursor manipulation, but PowerShell is not specifically designed for this scenario.
A far better alternative is to use Write-Progress to show real progress information.
$activity = "Git 'er done"
for() {
$result = & git log --graph --oneline
Write-Progress -Activity $activity -Id 1 -Status ($result -join ', ')
Start-Sleep -Seconds 1
}
Write-Progress -Activity $activity -Id 1 -Completed
(season to taste)
The reason I'm -joining $result is that on a repo of mine, the git command itself returns 3 lines, and $result is an array. You may want to decide how you handle that for your specific case, but be aware that $result can be an array and Write-Progress won't accept one.
Other Considerations
Write-Progress shows a graphical progress bar in ISE, but shows a text-based progress bar in the console host (powershell.exe) and in PowerShell Web Access. To put that another way, the specific PowerShell host application determines how the information is displayed, but it's available in a supported, defined way.
That means that Write-Progress should work in any environment (it may result in nothing happening, or display nothing, but it shouldn't crash).
In contrast, direct console manipulation probably only works in ISE and console host (admittedly they are probably the only hosts you care about), but if your code were suddenly hosted in a runspace in some C# application, it could just error out (I actually have no idea).
I am recovering files from a hard drive wherein some number of the files are unreadable. I'm unable to change the hardware level timeout / ERC, and it's extremely difficult to work around when I have several hundred thousand files, any tens of thousands of which might be unreadable.
The data issues were the result of a controller failure. Buying a matching drive (all the way down), I've been able to access the drive, and can copy huge swaths of it without issues. However, there are unreadable files dotted throughout the drive that, when accessed, will cause the SATA bus to hang. I've used various resumable file copy applications like robocopy, RichCopy, and a dozen others, but they all have the same issue. They have a RETRY count that is based on actually getting an error reported from the drive. The issue is that the drive is taking an extremely long time to report the error, and this means that a single file may take up to an hour to fail officially. I know how fast each file SHOULD be, so I'd like to build a powershell CMDLET or similar that will allow me to pass in a source and destination file name, and have it try to copy the file. If, after 5 seconds, the file hasn't copied (or if it has - this can be a dumb process), I'd like it to quit. I'll write a script that fires off each copy process individually, waiting for the process before it to finish, but I'm so far unable to find a good way of putting a time limit on the process.
Any suggestions you might have would be greatly appreciated!
Edit: I would be happy with spawning a Copy-Item in a new thread, with a new PID, then counting down, then killing that PID. I'm just a novice at PowerShell, and have seen so many conflicting methods for imposing timers that I'm lost on what the best practices way would be.
Edit 2: Please note that applications like robocopy will utterly hang when encountering the bad regions of the disk. These are not simple hangs, but bus hangs that windows will try to preserve in order to not lose data. In these instances task manager is unable to kill the process, but Process Explorer IS. I'm not sure what the difference in methodology is, but regardless, it seems relevant.
I'd say the canonical way of doing things like this in PowerShell are background jobs.
$timeout = 300 # seconds
$job = Start-Job -ScriptBlock { Copy-Item ... }
Wait-Job -Job $job -Timeout $timeout
Stop-Job -Job $job
Receive-Job -Job $job
Remove-Job -Job $job
Replace Copy-Item inside the scriptblock with whatever command you want to run. Beware though, that all variables you want to use inside the scriptblock must be either defined inside the scriptblock, passed in via the -ArgumentList parameter, or prefixed with the using: scope qualifier.
An alternative to Wait-Job would be a loop that waits until the job is completed or the timeout is reached:
$timeout = (Get-Date).AddMinutes(5)
do {
Start-Sleep -Milliseconds 100
} while ($job.State -eq 'Running' -and (Get-Date) -lt $timeout)