Powershell try block not working on VSTS build - powershell

I'm running a command in Powershell through VSTS to affect Azure. The command works, but it gives an error after. The params and connection are working as evidenced by the fact that the group gets permission from the command. I figure a workaround is to put the command in a try block, have it run, and then when the error comes up go to the catch block and end without throwing the error.
When I run this script, I still get the same error, like the try block is ignored. Do I have the syntax wrong?
Try
{
New-AzureRmRoleAssignment -ObjectId "xxxx" -RoleDefinitionName $roleName -ResourceGroupName pentest-$featureName
}
Catch
{
Write-Output "Whoops"
}
Edit: I added $ErrorActionPreference = "Stop" before the try-block and it caught the error properly.

The try block was ignored because of the wrong $ErrorActionPreference. Changing it from "Continue" to "Stop" fixed it.

Related

How do I check if a specific exception type has been thrown in Pester 5.4.0?

I want to write a test in PowerShell with Pester 5.4.0 to check whether a specific exception type has been thrown inside a script block. It should be fairly easy, but I am somehow not doing it right. Following along the Pester v5 documentation for the Should keyword and this pre-release Pester blogpost about using Should - Throw I wrote the following (very basic) script containing one test:
Import-Module Pester -RequiredVersion 5.4.0
Describe "Testing if a service is registed." {
It "This service should not be registered in the windows services app." {
{ Get-Service -Name "this service does not exist" } | Should -Throw -ExceptionType ([Microsoft.PowerShell.Commands.ServiceCommandException])
}
}
I would expect the test to catch the ServiceCommandException that is thrown when the Get-Service Cmdlet looks for a service that does not exist, and the test to pass. Instead the error is still thrown, Pester tells me that Expected an exception, with type [Microsoft.PowerShell.Commands.ServiceCommandException] to be thrown, but no exception was thrown., and the test fails:
I am probably doing something that's very obvious wrong here, but I looked at it for a while and did some research, and I cannot see what is wrong with my script.
the type of error you get from get-service is not a throwing/operation halting error. This means that while there is an error, Powershell will continue to process your script until completed.
to make sure that any command actually halts operation and triggers pesters should -throw, you can add -erroraction stop or the alias -ea stop to the get-service command:
The issue here, however you want to look at it, is that you are testing if a command is throwing an error, and handling it this way. however the command does not throw anything. it returns an error. its not a operation stopping thing that happens (even though you can force it by setting -erroraction).
what you might want to do instead is this:
It "Testing other method" {
Get-Service -Name "this service does not exist" -ErrorVariable er -ErrorAction SilentlyContinue
$er|should -not -HaveCount 0
$er.Exception|Should -BeOfType ([Microsoft.PowerShell.Commands.ServiceCommandException])
}

How can I change the default PowerShell error for when a command needs elevation?

I have a lot of code in a PowerShell script that are mix of commands that need elevation to run and commands that don't, those that need elevation show errors in PowerShell console like:
"You don't have enough permissions to perform the requested operation"
and
"Requested registry access is not allowed."
is there a way to globally suppress only the kinds of errors that PowerShell shows due to lack of necessary privileges?
I thought about a function that checks for elevation and performs actions based on the result, like this:
https://devblogs.microsoft.com/scripting/use-function-to-determine-elevation-of-powershell-console/
Function Test-IsAdmin
{
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal $identity
$principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
and
if(-NOT (Test-IsAdmin))
{ write-host "Skipping Admin command" }
else { $code }
but I don't know how to apply it globally to the whole script, so that commands that don't need elevation run, and those that need elevation show a custom message or skip that part silently.
another thing that can help my situation would be to find out if a PowerShell command needs elevation before actually running it and causing it to show errors in console due to lack of privileges.
It seems that errors stemming from a lack of privileges typically - but not necessarily - involve a System.UnauthorizedAccessException or System.Security.SecurityException .NET exception behind the scenes, whose name is then reflected as part of the .FullyQualifiedErrorId property of the resulting PowerShell error record, which is of type System.Management.Automation.ErrorRecord.
Assuming that this applies to all errors you care about, you can use a (rarely used anymore) trap statement as follows:
trap {
if ($_.FullyQualifiedErrorId -match 'UnauthorizedAccessException|SecurityException') {
Write-Warning "Skipping admin command ($($_.InvocationInfo.Line.Trim()))"
continue # Suppress the original error and continue.
}
# If the error was created with `throw`, emit the error and abort processing.
# SEE CAVEAT BELOW.
elseif ($_.Exception.WasThrownFromThrowStatement) { break }
# Otherwise: emit the error and continue.
}
# ... your script
Caveat:
If your script implicitly raises script-terminating errors - via -ErrorAction Stop or $ErrorActionPreference = 'Stop' - the above solution in effect turns them into statement-terminating errors and continues execution (only explicit script-terminating errors created with a throw statement are recognized as such in the code above, and result in the script getting aborted).
Unfortunately, as of PowerShell 7.2.x, there is no way to generally discover whether a given error is (a) non-terminating, (b) statement-terminating or (c) script-terminating (fatal).
See GitHub issue #4781 for a proposal to add properties to [System.Management.Automation.ErrorRecord] to allow such discovery in the future.

Powershell code not exiting after try catch block

I have a code that downloads a file from SharePoint, edits it, uploads it back to SharePoint and finally sends a confirmation email. I have functions for each of the tasks. The code works fine.
However, I want to add error exception for a condition when if the file is open in SharePoint by some user, show error message and exit code. The issue I am experiencing is the code continues to run even if there is an exception. In the below code, the sendMail function is called even when the getSharePointFile function fails.
I have tried $ErrorActionPreference = "Stop" but with that, the catch block is not executed and my custom error MessageBox is not displayed. Thanks in advance.
Here is the relevant code:
function getSharePointFile {
Connect-PnPOnline $SharepointURL -UseWebLogin
Get-PnPFile -Url $fileRelativeURL -Path $localFilePath -FileName $fileName -AsFile -Force
}
function runCatch {
$showMessage = [System.Windows.Forms.MessageBox]::Show($_)
exit
}
try {getSharePointFile}
catch{runCatch}
try {updateAuditResults}
catch{runCatch}
try {uploadToSharePoint}
catch{runCatch}
try {sendMail}
catch{runCatch}
Two issues:
First, you have four independent try catch blocks, and an error handled in one has no impact on the others.
Try something like this:
try {
getSharePointFile
updateAuditResults
uploadToSharePoint
sendMail
}
catch{runCatch}
The first line to generate an error will end the batch of commands and jump to the catch block. The rest of the batch will be skipped.
The second issue you might run into is not all PowerShell cmdlets return errors when they fail. You will need to test yours to verify. Some will just display error text and continue.
More info here: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_try_catch_finally
As #Mike Smith said, with the code you provided, it seems better to merge your function calls inside one try catch block.
Depending on your PowerShell version, you also need to add -ErrorAction Stop parameter to trap errors into the catch block (add it for the cmdlets, not the function call)
function getSharePointFile {
Connect-PnPOnline $SharepointURL -UseWebLogin -ErrorAction Stop
Get-PnPFile -Url $fileRelativeURL -Path $localFilePath -FileName $fileName -AsFile -Force -ErrorAction Stop
}

Script stops execution when faces an error

I have a master script master.ps1 which calls two scripts One.ps1 and Two.ps1 like:
&".\One.ps1"
&".\Two.ps1"
When the One.ps1 script has an error, the execution gets stopped without continuing the execution of Two.ps1
How to continue execution of Two.ps1 even if there is an error in One.ps1?
You have to set the $ErrorActionPreference to continue:
Determines how Windows PowerShell responds to a non-terminating
error (an error that does not stop the cmdlet processing) at the
command line or in a script, cmdlet, or provider, such as the
generated by the Write-Error cmdlet.
You can also use the ErrorAction common parameter of a cmdlet to
override the preference for a specific command.
Source.
$ErrorActionPreference = 'continue'
Note: As a best practice I would recommend to first determine the current error action preference, store it in a variable and reset it after your script:
$currentEAP = $ErrorActionPreference
$ErrorActionPreference = 'continue'
&".\One.ps1"
&".\Two.ps1"
$ErrorActionPreference = $currentEAP
#Martin is correct assuming that the success or failure of .\One.ps1 does not impact .\Two.ps1 and if you don't care about logging or otherwise dealing with the error. but if you would prefer to handle the error rather than just continue past it you could also use a Try{}Catch{} block as below to log the error (or take any other action you would like in the Catch{})
Try{
&".\One.ps1"
} Catch {
$error | Out-File "OneError.txt"
}
Try{
&".\Two.ps1"
} Catch {
$error | Out-File "TwoError.txt"
}
Other ways to format this but you get the idea.

Handling errors with ADSI

I'm working on a PowerShell script to change a local account name. Of course, the first step is to check that the account exists:
$user=[ADSI]"WinNT://$server/$oldName,user"
If the account exists, then no problem. But if it doesn't, then I get this error:
format-default : The following exception occurred while retrieving member >"distinguishedName": "The user name could not be found."
+ CategoryInfo : NotSpecified: (:) [format-default], ExtendedTypeSystemException
+ FullyQualifiedErrorId :
CatchFromBaseGetMember,Microsoft.PowerShell.Commands.FormatDefaultCommand
I can't figure out how to look for that error, report something like "$oldName not found" and continue on. From what I can tell, it isn't being thrown into an error variable, so I can't search for a "user name could not be found" string. Try-Catch-Finally seems to ignore the error.
I admit I'm weak on error-handling. It seems there's countless ways for something to fail, and my users always find new ones when using my scripts.
It seems like the command is actually throwing a terminating error. From about_preference_variables
"Neither $ErrorActionPreference nor the ErrorAction common parameter
affect how Windows PowerShell responds to terminating errors (those
that stop cmdlet processing)."
So when the command runs it is terminating the script even before it can move on to try and process a catch block.
Interestingly if you put it into a variable this behavior stops happening. I'd be curious to see if anyone has a better answer, but it looks like the solution from what I can see, would be an if statement based on the results of the variable.
$User = [ADSI]"WinNT://badserver/Name,user"
If (! $User.Name)
{
Throw "Issue retrieving user"
}
#Rest of script can continue here
You can check whether a username exists in this way
[ADSI]::Exists("WinNT://$Server/$UserName")
It returns a Boolean value. If user exists, you get true, otherwise false.
I solved a similar issue by wrapping the command in a script block and using Invoke-Command.
$ChangePassword = {([adsi]"WinNT://domain/$Username,user").ChangePassword($CurrentPassword, $NewPassword)}
try {
Invoke-Command -ScriptBlock $ChangePassword -ErrorAction Stop
}
catch {
# Error handling code
}