Making a PowerShell more efficient other than if statements - powershell

I have a script here that does its job for the most part. I am new to PowerShell scripting, so I am trying to get an outside view of what I should change.
The first part of the script asks the user if they would like to install the program or not.
$<application> = Read-Host -Prompt 'Would you like to install <>? Please type Yes or No'
This then leads to an if else statement
if ( $<application> -eq 'Yes' )
{
start-process <application.exe>
Start-Sleep -s 30
}
else
{
Write-Host "Installation of <Application> was skipped"
}
The reason I have start sleep is because it opens up an application one at a time. You have 30 seconds to setup the application which doesn't seem efficient
My questions are
Is there any way to do this without a sea of if statements? I know there is a way with a csv file but I am looking for alternatives. I like how the script asks if you should install a program or not
Is there anyway to stop the Start- Sleep process when the application is done? So the user doesn't feel rushed on one application?
Thank you.

Is there any way to do this without a sea of if statements?
Sure - organize your applications into an ordered dictionary and loop through the entries:
$applications = [ordered]#{
"App One" = "path\to\application1.exe"
"App Two" = "path\to\application2.exe"
# ...
}
foreach($appName in $applications.Keys){
$response = Read-Host -Prompt "Would you like to install '${appName}'? Please type Yes or No"
if($response -eq 'yes'){
Start-Process -Path $applications[$appName]
Start-Sleep -Seconds 30
} else {
Write-Host "Installation of '${appName}' was skipped"
}
}
Is there anyway to stop the Start- Sleep process when the application is done?
Yes, use Start-Process -Wait instead of sleeping for a set duration:
Start-Process -Path $applications[$appName] -Wait

Related

Continue script, once search will be done

I have command in the script to do compliance search(Microsoft Exchange) and it takes ~20 minutes and I need to wait until it will be done to use powershell. I need to continue script automatically, once search will be done. Here is example:
ShowSearchResults
Write-Host "You can find search results under the following path: C:\ExportResults" -ForegroundColor Green
Write-Host "Do you want to delete found mails? (y/n)" -ForegroundColor Green
New-ComplianceSearchAction -SearchName $FinalSearchName -PurgeType softdelete -Purge
And when I start "ShowSearchResults" I need to wait once it will be done. I need to show "Write-Host..." once search will be done, until it, I need my session.
I tried with Start-Job, but I didn't find the solution with this way
Your code seems good to me except for 1 misrepresented statement.
PowerShell, like most scripting languages, will execute the commands/statements in your script from top to bottom by default.
So your Write-Host will be executed only after the previous command is completed.
As for the "misrepresented" statement, you might've wanted to use Read-Host to get a user choice to soft delete your last search. So after small corrections, your code looks like the below,
#1
$ComplianceSearchJob = Start-Job -ScriptBlock {
ShowSearchResults
Write-Host "You can find search results under the following path: C:\ExportResults" -ForegroundColor Green
# Invoke-Item "C:\ExportResults\SearchResults.csv"
}
#2
Receive-Job $ComplianceSearchJob -Keep
#3
$DeleteChoice = Read-Host "Do you want to delete found mails? (y/n): " -ForegroundColor Green
if($DeleteChoice -eq "Y"){ # case insensitive
New-ComplianceSearchAction -SearchName $FinalSearchName -PurgeType softdelete -Purge
}
EDIT 1: Added Invoke-Item to open the C:\ExportResults\SearchResults.csv file in a comment.
EDIT 2: Used Start-Job to allow running as a background process.

Using -Confirm in Powershell

I am trying to understand what the relationship between the $? and $lastexitcode variables versus the -Confirm flag in Powershell cmdlets.
Say for example you run a command with -confirm it will prompt you accordingly for action:
PS C:\temp> rm .\foo.txt -confirm
Confirm
Are you sure you want to perform this action?
Performing the operation "Remove Directory" on target "C:\temp\foo.txt".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help
(default is "Y"):n
PS C:\temp> $?
True
I understand that technically the command ran successfully, but if the user chose no then the command did not run.
My question is how to I obtain the user's answer to the -Confirm flag?
$?, $LastExitCode, and -Confirm are completely unrelated to each other.
$? is an automatic variable with a boolean value indicating whether or not the last (PowerShell) operation was executed successfully.
$LastExitCode is an automatic variable with the exit code of the external command that was last executed (an integer value).
-Confirm is a common parameter controlling whether or not a cmdlet prompts the user for confirmation of its action.
To my knowledge PowerShell does not store the answer given to a -Confirm prompt anywhere, so if you need that response for something else you'll have to prompt the user yourself, e.g. like this:
function Read-Confirmation {
Param(
[Parameter(Mandatory=$false)]
[string]$Prompt,
[Parameter(Mandatory=$false)]
[string]$Message
)
$choices = New-Object Collections.ObjectModel.Collection[Management.Automation.Host.ChoiceDescription]
$choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList '&Yes'))
$choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList '&No'))
-not [bool]$Host.UI.PromptForChoice($Message, $Prompt, $choices, 1)
}
$doRemove = if ($PSBoundParameters['Confirm'].IsPresent) {
Read-Confirmation -Prompt 'Really delete'
} else {
$true
}
if ($doRemove) {
Remove-Item .\foo.txt -Force
}
AFAIK, it is not possible to capture the user's response to the confirmation prompt; it is not a part of PowerShell's command history, and while you might be able to get the information from the buffer somehow that would only be supported in the default PowerShell host as other hosts will use different buffers. In this case it is probably best to either do a separate confirmation within your script using an if statement.
$userAnswer = Read-Host "Are you sure you wish to proceed?"
if($userAnswer -eq "yes"){
rm .\foo.txt
}
Then just use the $userAnswer variable to know what your user responded with. Alternatively you could determine their answer by checking to see if the operation was completed. This would be my preferred method as this way you are SURE that the file has been deleted rather than assuming so because the cmdlet successfully executed and the user confirmed (the reliability is probably not any different here considering that remove-item is incredibly well tested but it could make a difference if you are using a third-party library of some kind) that would look something like the below.
rm .\foo.txt -Confirm
if(Test-Path .\foo.txt){
$success = $false
} else {
$success = $true
}
and if you really need to know whether it failed to delete due to an error or the user saying no you could do something like
rm .\foo.txt -Confirm
if(Test-Path .\foo.txt){
$success = $false
} else {
$success = $true
}
if(!($success) -and (!($?))){
$status = "Previous command failed"
} elseif (!($success) -and $?){
$status = "User cancelled operation"
}
Hope that helps.

Prorgess bar for `New-MailboxExportRequest`

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
}

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.