Prorgess bar for `New-MailboxExportRequest` - powershell

I'm trying to create a script that can export a user's mailbox to a PST, remotely (Exchange Server 2010 console is installed on the server we're running this from, and the module is loaded correctly). It's being done using a script so our L2 admins do not have to manually perform the task. Here's the MWE.
$UserID = Read-Host "Enter username"
$PstDestination = "\\ExServer\Share\$UserID.pst"
$Date = Get-Date -Format "yyyyMMddhhmmss"
$ExportName = "$UserID" + "$Date"
try {
New-MailboxExportRequest -Mailbox $UserID -FilePath $PstDestination -Name $ExportName -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null
# Loop through the process to track its status and write progress
do {
$Percentage = (Get-MailboxExportRequest -Name $ExportName | Get-MailboxExportRequestStatistics).PercentComplete
Write-Progress "Mailbox export is in progress." -Status "Export $Percentage% complete" -PercentComplete "$Percentage"
}
while ($Percentage -ne 100)
Write-Output "$UserID`'s mailbox has been successfully exported. The archive can be found at $PstDestination."
}
catch {
Write-Output "There was an error exporting the mailbox. The process was aborted."
}
The problem is, as soon as we initiate the export, the task gets Queued. Sometimes, the export remains queued for a very long time, and the script is currently unable to figure out when the task begins, and when it does, is unable to display the progress correctly. The export happens in the background, but the script remains stuck there. So anything after the export, does not get executed, and the whole thing then has to be done manually.
Please suggest a way to handle this?
I tried adding a wait timer and then a check to see if the export has begun. It didn't quite work as expected.

Two things. First one is more about performance/hammering Exchange with unnesacary requests in do/while loop. Start-Sleep -Seconds 1 (or any other delay that makes sense depending on the mailbox size(s)) inside the loop is a must.
Second: rather than wait for job to start, just resume it yourself:
if ($request.Status -eq 'Queued') {
$request | Resume-MailboxExportRequest
}

Related

PowerShell Remoting, Eventing, Runspace - sticky Event Subscriber

I am currently stuck at a problem involving a WMI Event subscriber on a remote session running in a background runspace.
Below is the representative code that reproduces the issue (the real code is too long to share here) but, essentially, through a PS script I remotely install advertised WSUS updates with reboots when necessary. It takes about 90 minutes end to end.
The issue I am trying to solve at the moment is that during the course of patching, the support staff inadvertently log in to the the server being remotely patched and do their admin activities. To remind them, I want to display a pop up message as soon as the user logs in when my remote script is running. I am trying to do it using a background runspace plugged into the main patching script. It makes use of WMI eventing on the target server (which is being patched) to monitor user logons and display message as soon as it detects one. The below code is working as I expect. It even survives target server reboots.
$RemoteServerName = 'Server1.contoso.com'
$UserLogonAlertScriptBlock = {
param ($SyncedHashTable)
$RemoteServerName = $SyncedHashTable.target
try {
$Session = New-PSSession -ComputerName $RemoteServerName -ErrorAction Stop
} catch {}
while($true){
if($Session.State -eq 'Opened') {
$RemoteMonitoringJob = Invoke-Command -Session $Session -AsJob -ScriptBlock {
$null = Register-WMIEvent -Query "SELECT * FROM __InstanceCreationEvent WITHIN 3 WHERE TargetInstance ISA 'Win32_LogonSession'" -SourceIdentifier 'User.Logon'
Wait-Event -SourceIdentifier "User.Logon" -Timeout 7200 | ForEach-Object {
msg * /TIME:7200 /V "User logon detected" | Out-Null
$_ | Remove-Event
}
}
while($RemoteMonitoringJob.State -in #('NotStarted','Running')) {
Start-Sleep -Seconds 1
}
} else {
while($true) {
try {
$Session = New-PSSession -ComputerName $RemoteServerName -ErrorAction Stop
} catch {}
if($Session.State -eq 'Opened') {
break
}
Start-Sleep -Seconds 1
}
}
}
}
$Runspace = [runspacefactory]::CreateRunspace()
$PowerShell = [powershell]::Create()
$PowerShell.runspace = $Runspace
$SyncedHashTable = [hashtable]::Synchronized(#{})
$SyncedHashTable.host = $host
$SyncedHashTable.target = $RemoteServerName
$Runspace.Open()
$handle = $PowerShell.AddScript($UserLogonAlertScriptBlock).AddArgument($SyncedHashTable).BeginInvoke()
Write-Host '$(Get-Date): Long running script execution targeting $RemoteServerName has started'
Start-Sleep -Seconds 120 # it usually runs for upto 90 minutes, with remote reboots of $RemoteServerName
Write-Host "$(Get-Date): The script execution has completed"
### The code that cleans up the sticky event subscriber on the target server needs to be added here
The part I am stuck at is after the script completes its execution. The wsmanprovhost.exe running on the target server continues to stick around and shows alert messages when new users log on. I think it's because of the WMI event listener still being active on the box, not releasing the remote PS session.
In the above code, I need help close that remote listener so wsmanprovhost.exe disappears.
Could you please help?
PS. I have referred to #mklement0 's response in the following post but still no joy: The RunSpace and its closure
Update:
I have managed to address the challenge by adding a Boolean flag into the SyncedHashtable which is passed to the background runspace. When I want to stop the remote logon monitoring, in the main script I flip the flag. Since it's inside a synced hashtable, I can monitor that inside the runspace and terminate the remote invoke command job in the run space. But I still had to forcibly kill the remote wsmprovhost.exe as it refuses to go. I could do it by getting the pid of the remote PS session in advance. Not the most elegant way to close a remote PS session but it does the job for me. It's just that since the remote session is continuously monitoring for user logon event, there does not appear to be a way to run a piece of code in that session to unsubscribe the WMI event source. Will do more testing to see if there is any side effect.

How to get the proper PID of a newly created Firefox window in Powershell?

Here is a very simple example of the problem I am experiencing:
$process = Start-Process 'C:\Program Files\Mozilla Firefox\firefox.exe' -argumentlist "-new-window https://google.com -foreground" -PassThru
Write-Host $process.Id
The firefox window will start and work as expected, it will return a process id, but when I actually check the running processes, I see no results for that PID.
I tried adding this just to see,
while (-not $process.HasExited){
Write-Host "..."
}
Write-Host $process.HasExited
And it looks like the process does run for maybe a couple milliseconds before it exits.
I'm thinking this may have something to do with how Firefox handles it's own processes. Because I tested a similar setup with some other random apps and they all worked as expected.
Any ideas on how to work around this when it comes to Firefox?
There are several challenges to overcome:
The firefox process that ends up presenting the actual browser window is different from the one that is initially launched. That is, as you've observed, the launched process spawns other processes and itself exits quickly.
As Olaf points out, modern browsers typically launch multiple non-transient processes, so the challenge is how to identify the one that represent the browser window.
Browsers may reuse existing processes, so a single process can present multiple windows / tabs, and closing one of them won't terminate the process as a whole.
If you need to ensure that a dedicated, new process is used, you have two options:
(a) Make sure that no preexisting Firefox instance is running, either by erroring out, or - if acceptable for your use case - by forcefully terminating all existing instances first (Stop-Process -Name firefox).
(b) With significantly more effort, create a dedicated, temporary Firefox profile that you can launch with the -new-instance option, which allows multiple independent Firefox instances to run concurrently and whose lifetime can be tracked separately.
The following - cumbersome - solution implements option (b):
If no firefox process is found, there is no concern about creating independent instances, and Firefox can be launched normally.
Otherwise, a temporary profile is created, and launched via the -new-instance and -profile options to ensure that a new process will be used to present the new browser window.
After launching the initial process, loop until a firefox process appears that was launched later and has a nonempty window title, which is then presumed to be the real process of interest.
You can then wait for the termination of this process to know when the dedicated browser window has been closed. If a temporary profile had to be created, it is cleaned up afterwards.
# Comment this statement out to silence the verbose messages below.
$VerbosePreference = 'Continue'
$now = Get-Date
$url = 'https://example.org' # URL to open.
# Launch a (new) Firefox instance.
if ($alreadyRunning = [bool] (Get-Process -ErrorAction Ignore firefox)) {
# Determine the path for a temporary profile with a unique name.
$tempProfilePath = Join-Path ([IO.Path]::GetTempPath()) ([datetime]::utcnow.tostring('o') -replace '\D')
Write-Verbose "Firefox is already running. Creating temp. profile $tempProfilePath..."
# Note: Creating an empty directory for the profile is seemingly enough.
$null = New-Item -Type Directory $tempProfilePath
Write-Verbose "and starting a new instance with it..."
Start-Process firefox "-new-instance -profile $tempProfilePath $url"
} else {
Write-Verbose 'Firefox isn''t running. Starting normally...'
Start-Process firefox $url
}
# Find the newly launched process that is the actual browser window.
Write-Verbose 'Waiting for a recently launched Firefox process with a nonempty window title to appear...'
while (-not (
$ps = Get-Process firefox |
Where-Object StartTime -gt $now |
Where-Object MainWindowTitle
)) {
Write-Host -NoNewLine .
Start-Sleep -MilliSeconds 500
}
Write-Host
Write-Verbose "Found. Waiting for process to exit..."
$ps.WaitForExit()
Write-Verbose 'Process has exited.'
if ($alreadyRunning) {
Write-Verbose "Cleaning up temporary profile $tempProfilePath..."
do {
# The profile dir. is typically held on to for a little while longer by associated processes that may not have terminated yet.
Start-Sleep -MilliSeconds 200
Remove-Item -ErrorAction Ignore -Literalpath $tempProfilePath -Recurse -Force
}
while (Test-Path -LiteralPath $tempProfilePath)
}
Thanks to #mklement0 work, in your case you can use the parent Process ID.
I use WMI to get the parent process, but it works for the very first launch.
$parentProcess = Start-Process 'C:\Program Files\Mozilla Firefox\firefox.exe' -argumentlist "-new-window https://google.com -foreground" -PassThru
$childProcess = get-process -id $(Get-CimInstance -Class Win32_Process -Filter "Name = 'firefox.exe'" | where {$_.ParentProcessId -eq $parentProcess.id}).ProcessId
# effectively stop the child
$childProcess | Stop-Process

Can I queue mailbox export to PST on Exchange Server 2010 SP3?

I am in a situation, when I would like to queue a few mailboxes to export (I don't want them to be processed at the same time) to PST files. I know, how to export them it with a command get-mailboxexportrequest, but when I do it, they almost instantly begin. Can I somehow queue another mailbox, so it would automatically start, when the previous one is completed?
I would do the following:
Build a powershell script which is checking if there is a running export happening (via Get-MailboxExportRequest) if that isn´t the case start to export x mailfiles you specify inside a CSV file
Use the Windows taskmanager on your Exchange Server to trigger that script and define a timeframe here how often and when your script should run
Once the powershell script runs, it should remove the exported mailfile from the CSV file and then quit
The next run from the powershell script via the taskmanager will then check if the current job is still ongoing, if it is, it should quit until its time to pick up the next entry from your list
Update:
As a starting point something like the following should be fine (untested but should give you a starting point):
# Get current Export Requests
$ExportStats = Get-MailboxExportRequest
#Check if there are completed questes
If ($ExportStats.Status -eq "Completed")
{
Write-Host "Export done"
Get-MailboxExportRequest -Status Completed -Name "$ObjectName-Export" | Remove-MailboxExportRequest -Confirm:$false
#Disable-Mailbox -identity "AD\$ObjectName"
# Create a new CSV file, which isn´t including the current export name we just marked as finish via above's section.
# CODE MISSING HERE!
# Now import our CSV list and proceed it
Import-CSV <Filepath of CSV file(\\server\folder\file.csv)> | ForEach-Object {
# Perform the export
New-MailboxExportRequest -Mailbox $_.MailboxAlias -FilePath $_.PSTpath
New-MailboxExportRequest -Mailbox $_.MailboxAlias -FilePath $_.ArchivePath
# Once done exit here, this will ensure we proceed only the first entry
Exit
}
}
elseif ($ExportStats.Status -eq "InProgress")
{
Write-Host "Export still ongoing"
}

Powershell: Brute-forcing password-protected .zip file (speeding up the process)

First-time questioner, so here's hoping I'm doing it right. :)
A co-worker and I have been playing around with Powershell, getting the lay of the land and see what you can do with it. Using info we found online (mostly here), we've managed to whip together a script to brute-force a password-protected .zip file using a .txt containing a list of passwords:
# Stopwatch for measurement
$stopWatch = [System.Diagnostics.Stopwatch]::startNew()
$7zipExec = """-7z.exe (7zip) location-"""
$input = """-.zip location-"""
$output = """-where to drop contents of .zip file-"""
$passwordfile = "-location of .txt file containing passwords-"
$windowStyle = "Hidden"
[long] $counter = 0
# Correct password is 12341234
foreach ($password in (get-content $passwordfile)) {
$counter++
Write-Host -NoNewLine "Attempt #($counter): $password"
$arguments = "x -o$output -p$password -aoa $input"
$p = Start-Process $7zipExec -ArgumentList $arguments -Wait -PassThru -WindowStyle $windowStyle
if ($p.ExitCode -eq 0) {
# Password OK
Write-Host " ...OK!"
Write-Host ""
Write-Host "Password is $password, found it after $counter tries."
break
}
elseif ($p.ExitCode -eq 2) {
# Wrong password
Write-Host " ...wrong"
}
else {
# Unknown
Write-Host " ...ERROR"
}
}
# Halt the stopwatch and display the time spent for this process
$stopWatch.Stop()
Write-Host
Write-Host "Done in $($stopWatch.Elapsed.Hours) hour(s), $($stopWatch.Elapsed.Minutes) minute(s) and $($stopWatch.Elapsed.Seconds) seconds(s)"
Read-Host -Prompt "Press Enter to exit"
It actually works! Probably not as clean as it could be, but we've managed to reach our goal to make a functioning script.
However! It takes about 1 second for each password try, and if you have a file with, say, the 10,000 most common passwords...that could take a while.
So now we're trying to figure out how to speed up the process, but we've hit a wall and need help. I'm not asking for someone to get 'er done, but I would really appreciate some tips/tricks/hints for someone who has only recently started getting into Powershell (and loving it so far!).
Took a while to get back to this, real life and all that, but while I did not manage to speed up the script, I did manage to speed up the process.
What I do now is run 4 instances of the script simultaneously (using an extra PS script to start them, which itself can be started with a batch file).
All of them have their own lists of passwords, and their own output directory (I found that when they use the same location, the file extracted by the script that found the password becomes unusable).
This way, it takes about 7-8 hours to attempt 100,000 of the most commonly used passwords! While I'm sure there are quicker scripts/programs out there, I'm pretty happy with the result.
Thanks all for the input!

Execute code if a PowerShell script is terminated

Is it possible to force the execution of some code if a PowerShell script is forcefully terminated? I have tried try..finally and Traps, but they both don't seem to work, at least when I press Ctrl-C from PowerShell ISE.
Basically, I have a Jenkins build that executes a PowerShell script. If for any reason I want to stop the build from within Jenkins, I don't want any subprocess to lock the files, hence keeping my build project in a broken state until an admin manually kill the offending processes (nunit-agent.exe in my case). So I want to be able to force the execution of a code that terminates nunit-agent.exe if this happens.
UPDATE: As #Frode suggested below, I tried to use try..finally:
$sleep = {
try {
Write-Output "In the try block of the job."
Start-Sleep -Seconds 10
}
finally {
Write-Output "In the finally block of the job."
}
}
try {
$sleepJob = Start-Job -ScriptBlock $sleep
Start-Sleep -Seconds 5
}
finally {
Write-Output "In the finaly block of the script."
Stop-Job $sleepJob
Write-Output "Receiving the output from the job:"
$content = Receive-Job $sleepJob
Write-Output $content
}
Then when I executed this and broke the process using Ctrl-C, I got no output. I thought that what I should got is:
In the finally block of the script.
Receiving the output from the job:
In the try block of the job.
In the finally block of the job.
I use try {} finally {} for this. The finally-block runs when try is done or if you use ctrl+c, so you need to either run commands that are safe to run either way, ex. it doesn't matter if you kill a process that's already dead..
Or you could add a test to see if the last command was a success using $?, ex:
try {
Write-Host "Working"
Start-Sleep -Seconds 100
} finally {
if(-not $?) { Write-Host "Cleanup on aisle 5" }
Write-Host "Done"
}
Or create your own test (just in case the last command in try failed for some reason):
try {
$IsDone = $false
Write-Host "Working"
Start-Sleep -Seconds 100
#.....
$IsDone = $true
} finally {
if(-not $IsDone) { Write-Host "Cleanup on aisle 5" }
Write-Host "Done"
}
UPDATE: The finally block will not work for output as the pipeline is stopped on CTRL+C.
Note that pressing CTRL+C stops the pipeline. Objects that are sent to
the pipeline will not be displayed as output. Therefore, if you
include a statement to be displayed, such as "Finally block has run",
it will not be displayed after you press CTRL+C, even if the Finally
block ran.
Source: about_Try_Catch_Finally
However, if you save the output from Receive-Job to a global variable like $global:content = Receive-Job $sleepJob you can read it after the finally-block. The variable is normally created in a different local scope and lost after the finally-block.