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 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
}
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.
I want to output a custom error message to a log, when a GWMI query fails. I can catch the exception, but apparently only the last one, because the output in my error file only has the name of the last computer in the list. I am using a list of computers I know do not have WMI enabled. There should be an entry for each one.
I have a list of domain computers in a text file, each on a single line, no trailing characters. I loop through the file to get network information, using GWMI. Some of the computers do not have WMI enabled, and I want to know which ones. My current script just throws a:
gwmi : The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)
At line:12 char:17
+ $base = gwmi win32_networkadapterconfiguration -computername $comp | whe ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Get-WmiObject], COMException
+ FullyQualifiedErrorId : GetWMICOMException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
whenever it loops through a machine that does not have wmi enabled.
This does not identify the computer that threw the exception. What I would like is for every time the RPC server is unavailable... error is thrown, for a custom error message to be written to a log with the name of the computer that threw the exception.
My script:
$computers = Get-Content -path f:\scripts\docs\computer_list_test.txt
if (F:\scripts\wmi_mac_output.txt){
rm F:\scripts\wmi_mac_output.txt
}
foreach ($comp in $computers) {
try
{
$base = gwmi win32_networkadapterconfiguration -computername $comp -ErrorAction Stop | where {$_.dnsdomain -eq "mydomain.com"}
$machine = $base.DNSHostName
$mac = $base.MACAddress
$ip = $base.IPAddress
"<COMPUTER>`n`tname: $machine`n`tMAC: $mac`n`tIP: $ip`n</COMPUTER>" | Out-File F:\scripts\wmi_mac_output.txt
}
catch [Exception]
{
if ($_.Exception.GetType().Name -eq "COMException")
{
"$comp has no winrm" > f:\scripts\docs\error.txt
}
}
}
Thank you.
I can catch the exception, but apparently only the last one, because the output in my error file only has the name of the last computer in the list.
The issue is that the error.txt is being overwritten instead of appended.
"$comp has no winrm" > f:\scripts\docs\error.txt
Change to:
"$comp has no winrm" >> f:\scripts\docs\error.txt