I'm trying to test out a script that creates exchange mailboxes for existing users. At the end I want to have a little piece that tests and informs the user whether or not it was successful. What I have is below.
$mail = Get-Mailbox user#domain.com
$checkmail = #($mail).count
if($checkmail -eq 0)
{
write-host "Does not exist"
}
else
{
write-host "exists"
}
This actually works just fine, but when the object doesn't exist, it also spits out a huge Powershell error to boot. I just don't want that part to be there. I've tried a try/catch block on the whole thing, and it for some reason just ignored it. The error is as follows:
Get-Mailbox : The operation could not be performed because object 'user#domain.com' could not
be found on the domain controller 'domainnamehere'
+ $mail = Get-Mailbox user#domain.com
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Get-Mailbox], ManagementObjectNotFoundException
+ FullyQualifiedErrorID : 3AAE54AC,Microsoft.Exchange.Management.RecipientTasks.Getmailbox
Any help would be appreciated.
One thing you could do is set the -ErrorAction for Get-Mailbox
$mailbox = Get-Mailbox asdf#ba.net -ErrorAction SilentlyContinue
The error for that command will be surpressed from the console ( but still occur ). You will have to check the value of $mailbox in case it is empty with a simple If.
If($mailbox){
Write-Host "Good mailbox"
} Else {
Write-Host "Bad mailbox"
}
The try block might not have worked if the error was non-terminating. If you have a try block setting -ErrorAction Stop might have made that work as well.
Related
I'm just beginning to dip into PowerShell with AD so I apologize if the question seems obvious.
I am trying to check if which of the devices provided in a list are in AD. So far I've used the code from:
Powershell - verify object exists in AD
It works just fine, but the "-ErrorAction SilentlyContinue" does not actually suppress the error messages. I get the below:
Get-ADComputer : Cannot find an object with identity: 'test' under:
'DC=test,DC=dom'.
At C:\Users\testaccount\Desktop\test.ps1:171
char:19
+ if (#(Get-ADComputer $target -ErrorAction SilentlyContinue).Count)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (test:ADComputer) [Get-ADComputer], ADIdentityNotFoundException
+ FullyQualifiedErrorId : ActiveDirectoryCmdlet:Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException,Microsoft.ActiveDirectory.Management.Commands.GetADComputer
The code I am using is as follows:
foreach ($target in $devicelist)
{
if (#(Get-ADComputer $target -ErrorAction SilentlyContinue).Count)
{
$existingdevices += $target
}
else
{
#display error notification
}
}
What I am looking for is suppressing the error message to no longer show in console - for the script to actually silently continue on error.
Any and all help will be appreciated!
So lets talk about whats happening.
There are 2 types of errors Terminating and Non-Terminating.
Terminating stops the execution of a command and throws an Exception. A non-terminating returns a write-out error message.
-ErrorAction takes care of Non-Terminating errors
Try{}Catch{} takes care of Terminating errors.
In your case
foreach ($target in $devicelist)
{
try{
if (#(Get-ADComputer $target -ErrorAction SilentlyContinue).Count)
{
$existingdevices += $target
}
else
{
#display non-terminating error notification
}
}catch{
#display Terminating error notification
}
}
Use output redirection: 2> $Null
Get-ADComputer -Server BlahBlah -Identity ComputerThatDoesntExist 2> $Null
Update 1:
Originally, I posted this with the title: "Scripts ignoring error handling in PowerShell module" as that is the current issue, however it seems more of a module issue, so I have renamed the title.
Update 2:
After a comment that made me question Azure cmdlets, I've tested with the most basic of scripts (added to the module) and the findings are the same, in that the error is not passed to the calling script, however, adding -errorVariable to Get-Service does return something (other than WriteErrorException) that I could probably harness in the handling of the error:
function Test-MyError($Variable)
{
Try
{
Get-Service -Name $variable -ErrorAction Stop -ErrorVariable bar
#Get-AzureRmSubscription -SubscriptionName $variable -ErrorAction Stop
}
Catch
{
Write-Error $error[0]
$bar
}
}
returns:
Test-MyError "Foo"
Test-MyError : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
At line:3 char:1
+ Test-MyError "Foo"
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Test-MyError
The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: Cannot find any service with service name 'foo'.
However, if I run "Test-MyError" in ISE, then call the function, I get:
Test-MyError "Foo"
Test-MyError : Cannot find any service with service name 'Foo'.
At line:3 char:1
+ Test-MyError "Foo"
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Test-MyError
The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: Cannot find any service with service name 'Foo'.
So I am not sure what is happening when running "Test-MyError" in ISE and calling it, against it being dot-sourced in the PSM1 file and then calling it?
Do I now have to use -ErrorVariable and handle on that?
Original Question:
I have two functions in a module: Get-Subscription and Get-AllSubscriptions. Each function sits in its own PS1 file and the PSM1 file dot-sources them. The module seems fine as the module scripts are accessible using intelisense and the module loads without issue. I've used this structure in many modules and I haven't come across this problem before. (Although I wonder if MS have changed the way modules work in PS 5.1 as I have noticed using FunctionsToExport='x','y','Z' and Export-ModuleMember don't seem to behave the same way as they used to.)
Get-AllSubscriptions calls Get-Subscription.
If I am not logged into Azure, Get-Subscription should throw an error which is handled, prompting me to log in. This works as expected, if I call Get-Subscription from the Get-Subscription.ps1.
However, when I call Get-Subscription from the a new PS1 file, Get-AllSubscriptions or from the powershell console, it doesn't work. It iterates all the way through the do..until loop, without "handling" the errors as I would expect. On each iteration, it seems to throw a generic error:
Get-Subscription : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
However, I do see the last error, Get-Subscription : Unable to find requested subscription after 3 login attempts.
If I execute Get-Subscription in ISE, then call Get-Subscription in a new PS1 file or from Get-AllSubscriptions, it works as expected, however, once I re-import the module (Import-Module AzureVnetTools -Force -Verbose), it goes back to the incorrect behaviour.
If I dot-source Get-Subscription, inside the caller script, it works, but why? This is what should happen with the module's PSM1.
Can anyone help me work out what I am doing wrong here?
(PS 5.1, Windows 7)
Get-Subscription:
function Get-Subscription
{
[cmdletbinding()]
Param
(
[string]$SubscriptionName,
[string]$UserName,
[string]$code
)
$c=1
Write-Verbose "Checking access to '$SubscriptionName' with user '$UserName'..."
Do
{
Write-Verbose "Attempt $c"
Try
{
$oSubscription = Get-AzureRmSubscription -SubscriptionName $SubscriptionName -ErrorAction Stop -WarningAction SilentlyContinue
Write-Verbose "Subscription found: $($oSubscription.SubscriptionName)."
}
Catch
{
if($error[0].Exception.Message -like "*Please verify that the subscription exists in this tenant*")
{
Write-Verbose "Cannot find subscription '$SubscriptionName' with provided credentials."
$account = Login-AzureRmAccount -Credential (Get-Credential -UserName $Username -Message "Subscription '$SubscriptionName' user' password:")
}
elseif($error[0].Exception.Message -like "*Run Login-AzureRmAccount to login*")
{
Write-Verbose "No logged in session found. Please log in."
$account = Login-AzureRmAccount -Credential (Get-Credential -UserName $Username -Message "Subscription '$SubscriptionName' user' password:")
}
else
{
Write-Error $error[0]
}
}
$c++
}
until(($oSubscription) -or ($c -eq 4))
if($c -eq 4)
{
Write-Error "Unable to find requested subscription after $($c-1) login attempts."
break
}
$oSubscription | Add-Member -MemberType NoteProperty -Name Code -Value $code
$oSubscription
}
Get-AllSubscriptions:
function Get-AllSubscriptions
{
[cmdletbinding()]
param
(
[string]$MasterSubscription,
[string]$MasterSubscriptionCode,
[string]$MasterSubscriptionUsername,
[string]$ChildSubscription,
[string]$ChildSubscriptionCode,
[string]$ChildSubscriptionUsername
)
Write-Verbose "Getting all subscriptions..."
$oAllSubscriptions = #()
$oMasterSubscription = Get-Subscription -SubscriptionName $MasterSubscription -UserName $MasterSubscriptionUsername -code $MasterSubscriptionCode -Verbose
$oChildSubscription = Get-Subscription -SubscriptionName $ChildSubscription -UserName $ChildSubscriptionUsername -code $ChildSubscriptionCode -Verbose
$oAllSubscriptions = ($oMasterSubscription,$oChildSubscription)
$oAllSubscriptions
}
Test:
$splat2 = #{
SubscriptionName = "SomeSubscription"
Code = "S02"
Username = "some.user#somewhere.com"
}
#Write-Output "Dot-source:"
#. "D:\Temp\PS.Modules\AzureVnetTools\functions\public\Get-Subscription.ps1"
Get-Subscription #splat2 -verbose
Output:
Get-Subscription #splat2 -verbose
VERBOSE: Checking access to 'SomeSubscription' with user 'some.user#somewhere.com'...
VERBOSE: Attempt 1
Get-Subscription : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
At line:7 char:1
+ Get-Subscription #splat2 -verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-Subscription
VERBOSE: Attempt 2
Get-Subscription : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
At line:7 char:1
+ Get-Subscription #splat2 -verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-Subscription
VERBOSE: Attempt 3
Get-Subscription : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
At line:7 char:1
+ Get-Subscription #splat2 -verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-Subscription
Get-Subscription : Unable to find requested subscription after 3 login attempts.
At line:7 char:1
+ Get-Subscription #splat2 -verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-Subscription
AzureVnetTools.psm1
#Get public and private function definition files.
$Public = #( Get-ChildItem -Path $PSScriptRoot\Functions\Public\*.ps1 -ErrorAction SilentlyContinue )
$Private = #( Get-ChildItem -Path $PSScriptRoot\Functions\Private\*.ps1 -ErrorAction SilentlyContinue )
#Dot source the files
Foreach($import in #($Public + $Private))
{
#write-error $import.fullname
Try
{
#Write-Host "Dot-sourcing file: $($import.fullname)."
. $import.fullname
}
Catch
{
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
}
Export-ModuleMember -Function $Public.Basename
AzureVnetTools.psd1 (Relevant section):
FunctionsToExport = '*'
-ErrorAction Stop -WarningAction SilentlyContinue
Is it a warning that's being thrown instead of an error?
So my specific problem was that I was relying on handling the error and doing something based on that. The problem was caused by the way PowerShell's Write-Error works (or not) as I learned from the reply here, given by #Alek.
It simply wasn't passing the actual error back to the calling script. As #Alex suggested, I replaced Write-Error with $PSCmdlet.WriteError(). Although this didn't totally work.
In the Catch{} block, I then changed $error[0] to $_ and the full error was returned to the calling script / function.
I went one further and wrote a reusable function, added to my module:
function Write-PsError
{
[cmdletbinding()]
Param
(
[Exception]$Message,
[Management.Automation.ErrorCategory]$ErrorCategory = "NotSpecified"
)
$arguments = #(
$Message
$null #errorid
[Management.Automation.ErrorCategory]::$ErrorCategory
$null
)
$ErrorRecord = New-Object -TypeName "Management.Automation.ErrorRecord" -ArgumentList $arguments
$PSCmdlet.WriteError($ErrorRecord)
}
Which seems to be working well at the moment. I especially like the way intellisense picks up all the ErrorCategories. Not sure what or how ISE (PS 5.1 / Win 7) does that. I thought I was going to have to add my own dynamic parameter.
HTH.
I'm trying to check for an existing computer name in AD before renaming the local computer. Below is the code that I started with, but don't know why it's failing.
When I enter a computer name (MNBLAP) that I know is in AD, it jumps down to the else statement. If I put in a computer name (RJKLAP) that I know doesn't exist, it throws an error.
$checkname = Get-ADComputer $newcomputername -ErrorAction SilentlyContinue
if ($checkname -eq $newcomputername){
Write-Host "The computer is already in AD."
}
else {
Write-Host "The computer is not in AD."
}
Any help would be greatly appreciated. Below is the error I receive
get-adcomputer : Cannot find an object with identity: 'RJKLAP' under: 'DC=domain,DC=domain'.
At C:\Scripts\CheckComputerName.ps1:25 char:14
+ ... checkname = get-adcomputer $newcomputername -ErrorAction SilentlyCont ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (RJKLAP:ADComputer) [Get-ADComputer], ADIdentityNotFoundException
+ FullyQualifiedErrorId : Cannot find an object with identity: 'RJKLAP' under: 'DC=domain,DC=domain'.,Microsoft.ActiveDirectory.Management.C
ommands.GetADComputer
The reason your if() statement fails is that Get-ADComputer returns an ADComputer object, not just the computer name.
To trap the error in case the machine name doesn't exist, use try/catch:
try{
$checkname = #(Get-ADComputer $newcomputername)
if($checkname.Count -eq 1){
# newcomputername found
Write-Host "The computer is already in AD."
}
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]{
# newcomputername not found
Write-Host "The computer is not in AD."
}
I have created a basic script to add a PC to the domain. Although this works there is room for error, and I want to put in some error handling.
do {
Add-Computer -DomainName $Domain -Credential(get-credential)
} while (!$?)
Using !$? runs the while loop while the last command is not successful.
However, there are various errors that return. Whether the PC is off the network, incorrect user ID or password, or domain specification, I want to be able to handle those errors and display something meaningful.
One of the errors returned
Add-Computer : This command cannot be executed on target computer('PCName') due to
following error: Logon failure: unknown user name or bad password.
At line:1 char:13
+ Add-Computer <<<< -DomainName $Domain -Credential(get-credential);
+ CategoryInfo : InvalidOperation: (PCNAME:String) [Add-Computer], InvalidOperationException
+ FullyQualifiedErrorId : InvalidOperationException,Microsoft.PowerShell.Commands.AddComputerCommand
Working with error handlers in lets say VBA, an error ID is given, and using if statements you can do something with it.
The FullyQualifiedErrorID in the error above is the same across all errors received for different reasons, so I do not believe I can use that.
How can I capture the specific error "Logon failure: unknown user name or bad password." or other errors and display a meaningful message so the admin can take appropriate action?
If nothing else you should be able to use the error message for distinguishing between errors:
do {
$joined = $true
$cred = Get-Credential
try {
Add-Computer -DomainName $Domain -Credential $cred -ErrorAction Stop
} catch {
$joined = $false
switch -regex ($_.Exception.Message) {
'.*unknown user name.*' { ... }
'.*domain does not exist.*' { ... }
...
default { 'Unexpected error' }
}
}
} until ($joined)
Note that you'll need to set the error action to Stop (-ErrorAction Stop), because otherwise the errors would be non-terminating and thus not catchable.
Use it with the -ErrorAction parameter:
Add-Computer ... -ErrorAction SilentlyContinue -ErrorVariable computerError
The ErrorVariable is an array, so the resulting error will be stored in:
$computerError[0]
To use the same variable over and over again, use a + in front of the var name:
Add-Computer -ErrorVariable +manyErrors
And the last error will always be:
$manyErrors[$manyerrors.count - 1]
To get the last error code if it has a corresponding Win32 error code then run the following
$manyErrors[$manyerrors.count - 1].Exception.InnerException.NativeErrorCode
And then if you gather the potential error codes you could do the following
if ($manyErrors[$manyerrors.count - 1].Exception.InnerException.NativeErrorCode -eq 1)
{
Write-Host error happened
}
elseif ($manyErrors[$manyerrors.count - 1].Exception.InnerException.NativeErrorCode -eq 2)
Write-Host other error happened
}
I'm trying to determine if a firewall rule exists or not, with Powershell.
If the rule does not exist, I get an ugly error message. If it does exist, all is good :)
How can I check if the rule exists without any ugly red error message occuring?
eg.
Import-Module NetSecurity
# Check if the firewall rule exists.
$existingRule = Get-NetFirewallRule -DisplayName $name
Error message (when the rule doesn't exist)...
Get-NetFirewallRule : No MSFT_NetFirewallRule objects found with property 'DisplayName' equal to 'Blah Blah Port 44444'. Verify the value of the property and retry. At C:\projects\xwing\Setup.ps1:67 char:21
+ $existingRule = Get-NetFirewallRule -DisplayName $name
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Blah Blah Port 44444:String) [Get-NetFirewallRule], CimJobException
+ FullyQualifiedErrorId : CmdletizationQuery_NotFound_DisplayName,Get-NetFirewallRule
Anyone know how to safely check for a rule, please?
Populate the -ErrorAction parameter for the cmdlet
Get-NetFirewallRule -DisplayName $name -ErrorAction SilentlyContinue
At this point you can test the result of the last command using $?. If the rule exists, this will return $true.
Alternatively, you can use a try / catch block:
try {
Get-NetFirewallRule -DisplayName blah -ErrorAction Stop
Write-Host "Rule found"
}
catch [Exception] {
write-host $_.Exception.message
}