Running multiple commands in a if statement - powershell

I'm looking to modify the below powershell script to do two things on an if statement:
if ($PsCmdlet.ParameterSetName -ieq "DumpCreds")
{
$ExeArgs = "privilege::debug"
}
elseif ($PsCmdlet.ParameterSetName -ieq "DumpCerts")
{
$ExeArgs = "crypto::cng crypto::capi `"crypto::certificates /export`" `"crypto::certificates /export /systemstore:CERT_SYSTEM_STORE_LOCAL_MACHINE`" exit"
}
else
{
$ExeArgs = $Command
}`
The line where it reads - $exeargs = "privilege::debug", I need run that and I also need to run - $ExeArgs = "sekurlsa::logonpasswords" The privilege one needs to run first followed by the logonpasswords one.
How do I run 2 commands in 1 if statement in a powershell script?

A if statement has no restriction about how much commands you execute in it, so just execute it...
if ($PsCmdlet.ParameterSetName -ieq "DumpCreds")
{
$ExeArgs = "privilege::debug"
$ExeArgs = "sekurlsa::logonpasswords"
}
elseif ($PsCmdlet.ParameterSetName -ieq "DumpCerts")
{
$ExeArgs = "crypto::cng crypto::capi `"crypto::certificates /export`" `"crypto::certificates /export /systemstore:CERT_SYSTEM_STORE_LOCAL_MACHINE`" exit"
}
else
{
$ExeArgs = $Command
}

Related

Invoke-Expression how check if successful on Exchange CmdLet?

I have to execute this Exchange command:
$command="Disable-Remotemailbox -Identitiy x.y#corp.com -Confirm:$false -Archive"
...and need to check if it was successfully executed.
try {
$result = Invoke-Expression $command
$success = 1
catch {
$success = 0
}
sf
$result = Invoke-Expression $command
if ($?) {
$success = 1
} else {
$success = 0
}
However, this is not working like expected. It returns 1 anyways.
It seems that it only shows if the Invoke-Expression command was successful. Which is all the time.
How to archive this?

How to run a module within a Scriptblock in PowerShell?

I am currently trying to import a .psm1 file dynamically into a script block to execute it.
I am using parallelisation along with jobs as I need to trigger several modules simultaneously as different users.
This is the code:
$tasksToRun | ForEach-Object -Parallel {
$ScriptBlock = {
param ($scriptName, $Logger, $GlobalConfig, $scriptsRootFolder )
Write-Output ("hello $($scriptsRootFolder)\tasks\$($scriptName)")
Import-Module ("$($scriptsRootFolder)\tasks\$($scriptName)")
& $scriptName -Logger $Logger -GlobalConfig $GlobalConfig
}
$job = Start-Job -scriptblock $ScriptBlock `
-credential $Cred -Name $_ `
-ArgumentList ($_, $using:Logger, $using:globalConfig, $using:scriptsRootFolder) `
Write-Host ("Running task $_")
$job | Wait-job -Timeout $using:timeout
if ($job.State -eq 'Running') {
# Job is still running, stop it
$job.StopJob()
Write-Host "Stopped $($job.Name) task as it took too long"
}
else {
# Job completed normally, get the results
$job | Receive-Job
Write-Host "Finished task $($job.Name)"
}
}
The logger variable is a hashtable as defined here:
$Logger = #{
generalLog = $function:Logger
certificateLog = $function:LoggerCertificate
alertLog = $function:LoggerAlert
endpointServiceLog = $function:LoggerEndpointService
}
Currently, it is erroring with the following:
ObjectNotFound: The term
' blah blah blah, this is the code straight from the logger function '
is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
The logger function servers the purpose of logging to a file in a specific way, it is generalised to that it can be used across many tasks.
A cut down example of a logger (probably won't compile, just deleted a bunch of lines to give you the general idea):
function LoggerEndpointService {
param (
# The full service name.
[string]$ServiceFullName,
# The unique identifier of the service assigned by the operating system.
[string]$ServiceId,
# The description of the service.
[string]$Description,
# The friendly service name.
[string]$ServiceFriendlyName,
# The start mode for the service. (disabled, manual, auto)
[string]$StartMode,
# The status of the service. (critical, started, stopped, warning)
[string]$Status,
# The user account associated with the service.
[string]$User,
# The vendor and product name of the Endpoint solution that reported the event, such as Carbon Black Cb Response.
[string]$VendorProduct
)
$ServiceFullName = If ([string]::IsNullOrEmpty($ServiceFullName)) { "" } Else { $ServiceFullName }
$ServiceId = If ([string]::IsNullOrEmpty($ServiceId)) { "" } Else { $ServiceId }
$ServiceFriendlyName = If ([string]::IsNullOrEmpty($ServiceFriendlyName)) { "" } Else { $ServServiceFriendlyNameiceName }
$StartMode = If ([string]::IsNullOrEmpty($StartMode)) { "" } Else { $StartMode }
$Status = If ([string]::IsNullOrEmpty($Status)) { "" } Else { $Status }
$User = If ([string]::IsNullOrEmpty($User)) { "" } Else { $User }
$Description = If ([string]::IsNullOrEmpty($Description)) { "" } Else { $Description }
$VendorProduct = If ([string]::IsNullOrEmpty($VendorProduct)) { "" } Else { $VendorProduct }
$EventTimeStamp = Get-Date -Format "yyyy-MM-ddTHH:mm:ssK"
$Delay = 100
For ($i = 0; $i -lt 30; $i++) {
try {
$logLine = "{{timestamp=""{0}"" dest=""{1}"" description=""{2}"" service=""{3}"" service_id=""{4}""" `
+ "service_name=""{5}"" start_mode=""{6}"" vendor_product=""{7}"" user=""{8}"" status=""{9}""}}"
$logLine -f $EventTimeStamp, $env:ComputerName, $Description, $ServiceFullName, $ServiceId, $ServiceFriendlyName, $StartMode, $VendorProduct, $User, $Status | Add-Content $LogFile -ErrorAction Stop
break;
}
catch {
Start-Sleep -Milliseconds $Delay
}
if ($i -eq 29) {
Write-Error "Alert logger failed to log, likely due to Splunk holding the file, check eventlog for details." -ErrorAction Continue
if ([System.Diagnostics.EventLog]::SourceExists("SDOLiveScripts") -eq $False) {
Write-Host "Doesn't exist"
New-EventLog -LogName Application -Source "SDOLiveScripts"
}
Write-EventLog -LogName "Application" -Source "SDOLiveScripts" `
-EventID 1337 `
-EntryType Error `
-Message "Failed to log to file $_.Exception.InnerException.Message" `
-ErrorAction Continue
}
}
}
Export-ModuleMember -Function LoggerEndpointService
If anyone could help that'd be great, thank you!
As mentioned in the comments, PowerShell Jobs execute in separate processes and you can't share live objects across process boundaries.
By the time the job executes, $Logger.generalLog is no longer a reference to the scriptblock registered as the Logger function in the calling process - it's just a string, containing the definition of the source function.
You can re-create it from the source code:
$actualLogger = [scriptblock]::Create($Logger.generalLog)
or, in your case, to recreate all of them:
#($Logger.Keys) |ForEach-Object { $Logger[$_] = [scriptblock]::Create($Logger[$_]) }
This will only work if the logging functions are completely independent of their environment - any references to variables in the calling scope or belonging to the source module will fail to resolve!

How to send input to [Console]::In.ReadLine() from parent process?

Starter is used for starting target script process:
# STARTING PS (TARGET) SCRIPT COMPILED TO EXE
$processStartInfo = New-Object System.Diagnostics.ProcessStartInfo
$processStartInfo.FileName = $somePath
$processStartInfo.WorkingDirectory = (Get-Location).Path
$processStartInfo.RedirectStandardInput = $true
$processStartInfo.RedirectStandardError = $true
$processStartInfo.UseShellExecute = $false
$process = [System.Diagnostics.Process]::Start($processStartInfo)
# SOME OTHER CODE ...
# HERE I'M SENDING "EXIT" TO RUNSPACE RUNNING INSIDE TARGET SCRIPT
$process.StandardInput.WriteLineAsync("exit") | Out-Null
Target script (compiled to *.exe) creates runspace that synchonously waits for ReadLine data from starter
function main {
. createRunspace
while ($true) {
# PARENT LOOP RUNS IN PARALLEL TO RUNSPACE LOOP
sleep -s 1
try {
if ($hash.flags.exit) {
# CLEAN UP AND BREAK
} else {
# RUN OTHER CODE
}
} catch {
# CAN NOT NOTIFY RUNSPACE ABOUT ERROR USING SYNCHRONIZED HASTABLE,
# BECAUSE RUNSPACE IS STUCK ON `ReadLine`.
# ALSO CAN NOT WRITE TO STANDRAD INPUT (DON'T KNOW HOW).
}
}
}
function createRunspace {
#CREATING RUNSPACE WITH SYNCHRONIZED HASTABLE
$hash = [hashtable]::Synchronized(#{ flags: #{} })
$runspace= [runspacefactory]::CreateRunspace()
$runspace.Open()
$runspace.SessionStateProxy.SetVariable('hash', $hash)
$powershell= [powershell]::Create()
$powershell.Runspace = $runspace
$powershell.AddScript({
# RUNSPACE LOOP
while ($true) {
$value = [Console]::In.ReadLine()
if ($value -eq "exit") {
$hash.flags.exit = $true
break
} elseif ($value -eq "valueFromParent") {
# DO STUFF
}
}
}) | Out
}
# OTHER CODE
. main
Is there a way to send standard input data from parent to runspace?
The PowerShell-script-packaged-as-an-*.exe packaging script you're using for some reason doesn't pass stdin input through to the wrapped script, so your script never receives the "exit" line you send from the caller.
I don't know your exact requirements, but here's a much simplified solution that shows that your approach works in principle:
# The code to execute in the background.
$backgroundScript = {
while ($true) {
$value = [Console]::In.ReadLine()
if ($value -eq "exit") {
"Background: Exiting."
break
}
else {
"Background: Performing task: $value"
}
}
}
# Start the background script.
$processStartInfo = [System.Diagnostics.ProcessStartInfo] #{
FileName = "powershell.exe"
Arguments = '-NoProfile', '-Command', $backgroundScript -replace '"', '\"'
WorkingDirectory = $PWD.ProviderPath
RedirectStandardInput = $true
RedirectStandardError = $true
RedirectStandardOutput = $true
UseShellExecute = $false
}
$process = [System.Diagnostics.Process]::Start($processStartInfo)
# Ask the background script to perform a task.
"Submitting task 'doStuff'"
$process.StandardInput.WriteLine("doStuff")
# Ask the background script to exit.
"Submitting exit request."
$process.StandardInput.WriteLine("exit")
# Wait for the background script's process to exit,
# then print its stdout.
$process.WaitForExit()
$process.StandardOutput.ReadToEnd()
The above yields:
Submitting task 'doStuff'
Submitting exit request.
Background: Performing task: doStuff
Background: Exiting.

How to loop through a script to let a user selection different options?

Fairly new to Powershell. I'm working on a script that will let a user install different pieces of software that will be needed. This process is currently being done manually and it can take 30-45 minutes to install everything that is needed. However, not everything needs to be installed on each workstation so I need flexibility for the user. This is what I have come up with so far. (Modified for brevity)
$Software1Path = "Path to installer"
$Software2Path = "Path to installer"
$Software3Path = "Path to installer"
function Software1 {
$Software1Arguments = "/S"
Start-Process -FilePath $Software1Installer $Software1Arguments -Wait
InstallOptions
}
function Software2 {
$Software2Arguments = "/silent"
Start-Process -FilePath $Software2Installer $Software2Arguments -Wait
InstallOptions
}
function Software3 {
$Software3Arguments = "/passive"
Start-Process -FilePath $Software3Installer $Software3Arguments -Wait
InstallOptions
}
function InstallOptions {
Do {
Clear-Host
Write-Host('1. Install Software1')
Write-Host('2. Install Software1')
Write-Host('3. Install Software1')
Write-Host('4. Install All Three')
Write-Host('0. Exit')
$value = Read-Host 'Input your selection (0-3)'
}
Until ($value -eq "o"){
switch ($value) {
"0" {exit}
"1" { Software 1}
"2" { Software 2}
"3" { Software 3}
"4" { Software1
Software2
Software3}
}
}
}
It does not give the desired result. I can either install one piece of software but then the script exits and I also cannot install all three. I've played with the InstallOptions and wrote it ten different ways but I am still not getting the desired result. Any suggestions?
You can call InstallOptions in a while($true) {} (as long as you keep 0 to exit) or another type of loop to make it return to the menu. If you call the menu from the software installation-function it will never get to 2 and 3 when you use "install all". Try:
$Software1Path = "Path to installer"
$Software2Path = "Path to installer"
$Software3Path = "Path to installer"
function Software1 {
$Software1Arguments = "/S"
Start-Process -FilePath $Software1Path -ArgumentList $Software1Arguments -Wait
}
function Software2 {
$Software2Arguments = "/silent"
Start-Process -FilePath $Software2Path -ArgumentList $Software2Arguments -Wait
}
function Software3 {
$Software3Arguments = "/passive"
Start-Process -FilePath $Software3Path -ArgumentList $Software3Arguments -Wait
}
function InstallOptions {
Clear-Host
Write-Host('1. Install Software1')
Write-Host('2. Install Software2')
Write-Host('3. Install Software3')
Write-Host('4. Install All Three')
Write-Host('0. Exit')
$value = Read-Host 'Input your selection (0-3)'
switch ($value) {
"0" {exit}
"1" { Software1}
"2" { Software2}
"3" { Software3}
"4" {
Software1
Software2
Software3
}
}
}
#Start the train
while($true) { InstallOptions }
You might also want to clean this up. Ex. $Sofware1Path is outside the function while the $Software1Arguments are inside. For simple installations, you could clean this up to use ex. a csv-stored array (can be read stored in a separate file if needed). Something like:
$Installations = #(#"
Name,Step,Path,Arguments
Software1,1,"c:\Install Files\Product1\Setup.exe",/S
Software2,2,"c:\Install Files\Product2\SetupPart2.exe",/silent
Software2,1,"c:\Install Files\Product2\Setup.exe","/silent ""space test with ,"""
Software3,1,"c:\Install Files\Product3\Setup.exe",/passive "space test"
"# | ConvertFrom-Csv)
function InstallSoftware ($Name) {
Write-Host "Installing $Name..."
$Installations | Where-Object { $_.Name -eq $Name } | Sort-Object { $_.Step -as [int] } | ForEach-Object {
ExecuteStep -Path $_.Path -Arguments $_.Arguments
}
}
function ExecuteStep ($Path, $Arguments) {
Write-Host "Executing '$Path' '$Arguments'"
Start-Process -FilePath $Path -ArgumentList $Arguments -Wait
}
function Menu {
$UniqueSoftware = $Installations | Select-Object -ExpandProperty Name -Unique | Sort-Object
$NumOfSoftware = $UniqueSoftware.Count
#Generate menu
Clear-Host
for($i=0;$i -lt $NumOfSoftware; $i++) {
Write-Host ("{0}. Install {1}" -f ($i+1), $Software[$i].Name)
}
Write-Host ("{0}. Install all" -f ($NumOfSoftware+1))
Write-Host "0. Exit"
do {
#Get input
$value = (Read-Host "Input your selection (0-$($NumOfSoftware+1))") -as [int]
#Execute
switch ($value) {
0 { exit }
{ $_ -gt 0 -and $_ -le $NumOfSoftware } { InstallSoftware -Name $UniqueSoftware[($_-1)] }
($NumOfSoftware+1) { 0..($NumOfSoftware-1) | ForEach-Object { InstallSoftware -Name $UniqueSoftware[($_)] } }
default { Write-Host "Invalid input..." }
}
#Validate input or retry
} until ( $value -ge 0 -and $value -le $NumOfSoftware+1 )
}
#Start the train
while($true) { Menu }

Powershell: Capture output from cmd at run time

is it possible to capture the Output from a commandline while a cmd is running? I´ve a small exe, which displays various Messages that should be processed with a script and displays the user some informations while the script is running.
My script starts the program (the exe) with some Parameters und checks if the process is still running. While the program is running i want to capture all messages to a variable to process it. I can´t find any solution, some tests with "run.exe 2>&1" etc... fails.
Any ideas?
$oInfo = New-Object System.Diagnostics.ProcessStartInfo
$oInfo.FileName = "ping"
$oInfo.Arguments = "localhost"
$oInfo.UseShellExecute = $False
$oInfo.RedirectStandardOutput = $True
$oProcess = New-Object System.Diagnostics.Process
$oProcess.StartInfo = $oInfo
[Void]$oProcess.Start()
$bDone = $False
while (!$bDone)
{
$char = $oProcess.StandardOutput.Read()
if ($char -eq -1)
{
if ($oProcess.HasExited)
{
$bDone = $True
}
else
{
Wait-Event 1
}
}
else
{
Write-Host -NoNewline "".PadLeft(1, $char)
}
}