Invoke-Expression how check if successful on Exchange CmdLet? - powershell

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?

Related

Send confirmation message if command ran with Invoke-Expression completes successfully

I'm writing a PowerShell script to add / remove people from a distribution group. I want to send a message if the action was successful and another if it failed. This is part of the script:
foreach ($x in Get-Content $pathfile\inputfile.txt) {
$USER = $x.Split(',')[0]
$ACTION = $x.Split(',')[1]
$COMMAND = (Write-Output "$ACTION-DistributionGroupMember -Identity 'Group Name' -Member $USER")
if ($ACTION -ieq "remove") {
$COMMAND = $COMMAND + ' -Confirm:$false'
Invoke-Expression $COMMAND
}
else {
Invoke-Expression $COMMAND
}
}
inputfile.txt, for the sake of information is:
jdoe#example.com,Add
jsmith#example.com,Remove
I've tried using $? and $lasExitCode but neither of those worked as expected as they only consider the output of "Invoke-Expression" and that is always successful.
What I am expecting is:
foreach ($x in Get-Content $pathfile\inputfile.txt) {
$USER = $x.Split(',')[0]
$ACTION = $x.Split(',')[1]
$COMMAND = (Write-Output "$ACTION-DistributionGroupMember -Identity 'Group Name' -Member $USER")
if ($ACTION -ieq "remove") {
$COMMAND = $COMMAND + ' -Confirm:$false'
Invoke-Expression $COMMAND
#if $COMMAND successful: Write-Output "$ACTION on $USER succeeded."
#if $COMMAND unsuccessful: Write-Output "$ACTION on $USER failed."
}
else {
Invoke-Expression $COMMAND
#if $COMMAND successful: Write-Output "$ACTION on $USER succeeded."
#if $COMMAND unsuccessful: Write-Output "$ACTION on $USER failed."
}
}
$? won't work because even if the command fails, Invoke-Expression was invoked successfully.
Use the & call operator to invoke the call directly instead, and $? will work. For the conditional parameter argument, use splatting!
foreach ($x in Get-Content $pathfile\inputfile.txt) {
$user,$action,$null = $x.Split(',')
# construct command name
$command = "$ACTION-DistributionGroupMember"
# organize the parameter arguments in a hashtable for splatting later
$paramArgs = #{
Identity = 'Group Name'
Member = $USER
}
# conditionally add the `-Confirm` parameter
if ($ACTION -ieq "remove") {
$paramArgs['Confirm'] = $false
}
# invoke the command with the call operator
& $command #paramArgs
if($?){
# command invocation suceeded
}
else {
# command invocation failed
}
}

Retry PowerShell script (.ps1) if failing with specific error message

We have a PowerShell build script (Cake bootstrapper) that occasionally fails to download/restore nuget packages. How would I create a generic .ps1 script that can be used to make calls to another .ps1 script, and retry if failed and the output contained a specific string (I don't want to retry if it's a build error)?
Possible example usage:
.\ExecuteWithRetry.ps1 -Script "Build.ps1 -target Restore" -OutputTrigger "RemoteException"
Created a script that does the trick.
Example usage:
./Execute-With-Retry.ps1 -RetryDelay 1 -MaxRetries 2 { & ./Build.ps1 -target Restore } -RetryFilter RemoteException
Based on this gist from Alex Bevilacqua.
<#
This script can be used to pass a ScriptBlock (closure) to be executed and returned.
The operation retried a few times on failure, and if the maximum threshold is surpassed, the operation fails completely.
Params:
Command - The ScriptBlock to be executed
RetryDelay - Number (in seconds) to wait between retries
(default: 5)
MaxRetries - Number of times to retry before accepting failure
(default: 5)
VerboseOutput - More info about internal processing
(default: false)
RetyFilter - Only retry if retry filter value is contained in the command output
Examples:
./Execute-With-Retry.ps1 -RetryDelay 1 -MaxRetries 2 { & ./Build.ps1 -target Restore } -RetryFilter RemoteException
#>
param(
[Parameter(ValueFromPipeline, Mandatory)]
$Command,
$RetryDelay = 5,
$MaxRetries = 5,
$VerboseOutput = $false,
$RetryFilter
)
$currentRetry = 0
$success = $false
$cmd = $Command.ToString()
do {
try {
$result = & $Command
$success = $true
if ($VerboseOutput -eq $true) {
$Host.UI.WriteDebugLine("Successfully executed [$cmd]")
}
return $result
}
catch [System.Exception] {
$currentRetry = $currentRetry + 1
$exMessage = $_.Exception.Message;
if ($RetryFilter -AND !$exMessage.Contains($RetryFilter)) {
throw $exMessage
}
if ($VerboseOutput -eq $true) {
$Host.UI.WriteErrorLine("Failed to execute [$cmd]: " + $_.Exception.Message)
}
if ($currentRetry -gt $MaxRetries) {
throw "Could not execute [$cmd]. The error: " + $_.Exception.ToString()
}
else {
if ($VerboseOutput -eq $true) {
$Host.UI.WriteDebugLine("Waiting $RetryDelay second(s) before attempt #$currentRetry of [$cmd]")
}
Start-Sleep -s $RetryDelay
}
}
} while (!$success);

PowerShell Output to text

I have the below PowerShell script to check if a file is locked - how do I also output the 0 or 999 to a text file, for example C:\stuff\PSOutput.txt?
$file = "\\xxxxx\xxxxxx\xxxxxxx\test.log"
try {
[IO.File]::OpenWrite($file).Close();
exit 0
} catch {
exit 999
}
$exit | Out-File -FilePath "C:\stuff\PSOutput.txt"
Don't exit the script. Save your exit value into a variable and write it to a file.
$file = "\xxxx\xxxxx\xxxxxxx\test.log"
try { [IO.File]::OpenWrite($file).close(); $exit = 0 } catch { $exit = 999}
$exit | Out-File -FilePath "C:\Path\to\myFile.txt"
exit stops your script and returns your exit value. To do more stuff with your script (like saving the value into a file), you should not use exit.
You will have to edit the path for your file in first line of script:
$fileName = "test.txt"
$file = New-Object -TypeName System.IO.FileInfo -ArgumentList $fileName
[System.IO.FileStream] $fs = $file.OpenWrite();
if (!$?) {
"0" | Out-File "C:\PSOutput.txt"
}
else {
$fs.Dispose()
"999" | Out-File "C:\PSOutput.txt"
}
This should work:
$file = "\\xxxx\xxxxx\xxxxxxx\test.log"
$exitCode = 0
if (Test-Path -Path $file -PathType Leaf) {
$fileInfo = New-Object System.IO.FileInfo $file
try {
$stream = $fileInfo.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
if ($stream) { $stream.Close() }
}
catch {
$exitCode = 999 # the file is locked by a process.
}
}
$exitCode | Out-File -FilePath "C:\stuff\PSOutput.txt"
The exit statements in your code cause your script to exit before it gets to the point where $exit would be written to the output file. But even if it got there it wouldn't actually record the exit code, because the exit keyword doesn't magically fill the (undefined) variable $exit with the exit code. You may be confusing it with the automatic variable $LastExitCode, which gets updated on the caller side after the callee exited.
What you actually want to do is define a custom function that properly sets the exit code and does everything else you want to do before actually exiting:
function ExitWithCode($exitcode) {
$host.SetShouldExit($exitcode)
$exit | Out-File -FilePath 'C:\stuff\PSOutput.txt'
exit $exitcode
}
then use it in your code like this:
try {
[IO.File]::OpenWrite($file).Close()
ExitWithCode 0
} catch {
ExitWithCode 999
}
Using $host.SetShouldExit() in addition to exit ensures that your script always sets the proper exit code, regardless of how it's invoked.

Best way to terminate a PowerShell function based on parameters

I have a few functions that get called either from Jenkins as part of a pipeline, they also get called from a pester test or lastly they can get called from the powershell console. The issue I have really stems from Jenkins not seeming to handle write-output in the way I think it should.
So what I am doing is creating a Boolean param that will allow my to choose if I terminate my function with a exit code or a return message. The exit code will be used by my pipeline logic and the return message for the rest ?
Is there a alternate approach I should be using this seems to be a bit of a hack.
function Get-ServerPowerState
{
[CmdletBinding()]
param
(
[string[]]$ilo_ip,
[ValidateSet('ON', 'OFF')]
[string]$Status,
[boolean]$fail
)
BEGIN
{
$here = Split-Path -Parent $Script:MyInvocation.MyCommand.Path
$Credentials = IMPORT-CLIXML "$($here)\Lib\iLOCred.xml"
}
PROCESS
{
foreach ($ip in $ilo_ip)
{
New-LogEntry -Message ("Getting current powerstate " + $ip)
If (Test-Connection -ComputerName $ip.ToString() -Count 1 -Quiet)
{
$hostPower = Get-HPiLOhostpower -Server $ip -Credential
$Credentials -DisableCertificateAuthentication
}
}
}
END
{
If($fail){
New-LogEntry -Message "Script been set to fail with exit code" -Log Verbose
New-LogEntry -Message "The host is powered - $($HostPower.Host_Power)" -Log Verbose
If($hostPower.HOST_POWER -match $Status)
{
Exit 0
}
else {
Exit 1
}
}
else {
New-LogEntry -Message "Script been set to NOT fail with exit code" -Log Verbose
New-LogEntry -Message "The host is powered - $($HostPower.Host_Power)" -Log Verbose
If($hostPower.HOST_POWER -match $Status)
{
return 0
}
else {
return 1
}
}
}
}
Like this
function Get-Output {
param ([switch]$asint)
if ($asint) {
return 1
}
else {
write-output 'one'
}
}
Get-Output
Get-Output -asint
If you intend to use the output in the pipeline then use Write-Output. If you intend to only send it to the host process then use Write-Host. I typically use the return keyword if I want to assign a return value to a variable.
[int]$result = Get-Output -asint

Running multiple commands in a if statement

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
}