Why aren't error thrown when I run this function as domain administrator? - powershell

This script is intended to recurse through a series of directories and when an error of type DirUnauthorizedAccessError,Microsoft or PowerShell.Commands.GetChildItemCommand is thrown it's supposed to call another function Take-Ownership which takes ownership of the directory and adds full permissions for the localAdmin and domain admin to the folder. (It's really a script used for easing the deletion of old user profiles):
function Test-Folder($FolderToTest, $localAdminName) {
# Remeber the old error preference...
$old_ErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = 'SilentlyContinue'
$error.Clear()
# Go through the directories...and capture errors in $error
Get-ChildItem $FolderToTest -Recurse -ErrorAction SilentlyContinue -ErrorVariable errz | Select FullName
Write-Host $errz.count
if ($errz.Count -eq 0) {
Write-Host "blah no errors"
foreach ($err in $errz) {
Write-Host "Error: $err"
if ($err.FullyQualifiedErrorId -eq "DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand") {
Write-Host Unable to access $err.TargetObject -Fore Red
Write-Host Attempting to take ownership of $err.TargetObject -Fore Yellow
Take-Ownership -Folder $err.TargetObject, -LocalAdminName $localAdminName
Test-Folder -FolderToTest $err.TargetObject -localAdminName $localAdminName
}
}
}
$ErrorActionPreference = $old_ErrorActionPreference
}
Unfortunately, it doesn't throw any errors when I run it as domain administrator. I've found a list of ErrorActionPreferences here, but the errors just seem to get ignored, and it outputs blah no errors What can I do to make sure I receive errors and that my Take-Ownership function is actually called?

Your code only enters the if block if $errz.Count is 0. With a count of 0 there are no elements in $errz, so there's nothing to do for the foreach loop.
Add an else branch to the conditional, move the foreach loop there, and the code should do what you want.
if ($errz.Count -eq 0) {
Write-Host "blah no errors"
} else {
foreach ($err in $errz) {
Write-Host "Error: $err"
...
}
}

Related

Exception Handling with AD PowerShell

I am building a PowerShell script to create AD Groups (Global and DomainLocal) by Importing their names from a Csv file.
I am having a hard time handling exceptions that will be generated in case Groups already exist.
What I want to achieve is if the Groups do not exist by the name in Csv then PS should create them and show message "Groups have been created" and if they already exist then it should display "Groups already exist" line by line so that if one exists and the other one doesn't then it should display the corresponding message.
What is happening is that PS doesn't display a message when it has created groups and when exception does occur it displays message only for Global Group not Local.
Please advise
Here's the code -
Try {
New-ADGroup -Name TestGlobal -GroupCategory Security -GroupScope Global -ManagedBy TEMP01 -Description "Owner is TEMP01" -Path (Some OU)
} Catch [Microsoft.ActiveDirectory.Management.ADException] {
if ($_ -like "The specified group already exists") {
Write-Host “!!! GLOBAL GROUP ALREADY EXISTS !!!”
} elseif ($_ -eq $null) {
Write-Host " GLOBAL GROUP CREATED SUCCESSFULLY "
}
}
Try {
New-ADGroup -Name TestLocal -GroupCategory Security -GroupScope DomainLocal -ManagedBy TEMP02 -Description "Owner is TEMP02" -Path (Some OU)
} Catch [Microsoft.ActiveDirectory.Management.ADException] {
if ($_ -like "The specified group already exists") {
Write-Host “!!! LOCAL GROUP ALREADY EXISTS !!!”
} elseif ($_ -eq $null) {
Write-Host " LOCAL GROUP CREATED SUCCESSFULLY "
}
}
PowerShell is a bit strange in that, by default, errors are non-terminating. That means that errors will be output to the console, but it will just continue on to the next line of code as if nothing happened.
Unfortunately, Try blocks only respond to terminating errors.
You can change this behaviour. On both of your New-ADGroup lines, add this to the end:
-ErrorAction Stop
That will tell PowerShell that you want it to treat errors on that line as terminating.
If you want, you can do some more reading about it here:
https://blogs.technet.microsoft.com/heyscriptingguy/2014/07/09/handling-errors-the-powershell-way/
https://blogs.technet.microsoft.com/heyscriptingguy/2014/07/05/weekend-scripter-using-try-catch-finally-blocks-for-powershell-error-handling/

Including common code in header/footer with PowerShell

I have common functions and formats to most of my scripts. Each script brings up a window for me to paste workstations and it performs basic tasks like checking connectivity before proceeding. Generally, I copy and paste this code and modify the body. What I would like to do is include a header and footer, but I get "Missing closing '}' in statement block." errors. Example:
<# Begin Header #>
if($canceled) {
write-host "Operation canceled."
}
else {
if($computers.length -gt 0) {
[array]$computers = $computers.split("`n").trim()
# Loop through computers entered
foreach($pc in $computers) {
# Skip zero length lines for computers
if(($pc.length -eq $null) -OR ($pc.length -lt 1)) {
continue
}
else {
# Try to connect to the computer, otherwise error and continue
write-host "Connecting to: $pc$hr"
if(test-connection -computername $pc -count 1 -ea 0) {
<# End Header #>
Body of script
<# Begin Footer #>
}
else {
utc # Unable to contact
}
}
write-host "`n"
}
}
}
<# End Footer #>
Rather than copying/pasting each time, I would prefer to do this...
."c:\scripts\header.ps1"
-- code --
."c:\scripts\footer.ps1"
Is that even possible when the header ends with an open bracket? I do this in PHP but I can't figure out a work-around in PowerShell.
Your approach could be changed into storing a function in one file and your custom script that runs for-each server in another. You can store a scriptblock to a variable in PowerShell and pass that as a parameter to a function. You can use Invoke-Command -scriptblock $Variable to execute that code.
Write your function like this:
function runAgainstServerList {
param ( [ScriptBlock]$ScriptBlock)
if($canceled) {
write-host "Operation canceled."
}
else {
if($computers.length -gt 0) {
[array]$computers = $computers.split("`n").trim()
# Loop through computers entered
foreach($pc in $computers) {
# Skip zero length lines for computers
if(($pc.length -eq $null) -OR ($pc.length -lt 1)) {
continue
}
else {
# Try to connect to the computer, otherwise error and continue
write-host "Connecting to: $pc$hr"
if(test-connection -computername $pc -count 1 -ea 0) {
Invoke-Command -ScriptBlock $ScriptBlock
}
else {
utc # Unable to contact
}
}
write-host "`n"
}
}
}
}
Now save that off to your include file like 'myFunctions.ps1'
Then create your custom script that you want to run per server like this:
. myFunctions.ps1
[ScriptBlock]$ScriptBlockToPass = {
## Insert custom code here
}
runAgainstServerList $ScriptBlockToPass
To get you a step closer to what might be your end goal, You may want to append the -ComputerName "ComputerNameHere" argument to your invoke-command statement inside your included function. This would cause your script to be executed on the remote system instead of locally.

Catching Cascading Errors in PowerShell

I'm working with a wrapper script that calls a function which queries some databases inside specified servers and inserts metadata to a specific database on a separate server. I use the $error.count variable to determine if the script was successful or not. I anticipate some permission/extraction errors to happen and want these to be caught and ignored (no increase in the $error.count variable but having a warning written in the job log). I can confirm one permission error is happening and is being properly caught. The $error.count variable is not increased but a warning is printed from the catch showing the database that could not be accessed.
My problem occurs after the extraction/insertion function is finished running. Immediately after this function returns to the wrapper script, I have the $error.count variable print again. This time, it returns a 1 as if the error previously caught cascades into the wrapper script. As I mentioned previously, I do not want this to be included in the error count. I'm not sure how or why the $error.count is increased from this function.
Should I use a different variable to determine if the script "failed" or not? Is there some underlying reason why the $error.count would increase outside of the function that has the error while not increasing after the error is caught? Any guidance on this issue would be appreciated.
Code for reference:
Wrapper function:
$errorCount = $error.count
Write-Warning ("$errorCount Before function")
Extraction/Insertion_Function -serverList $serverList -insertionDB $insertionDB -ErrorAction SilentlyContinue
$errorCount = $error.count
Write-Warning ("$errorCount After function")
} catch {
Write-Error "Error caught by wrapper: $_"
}
Extraction/Insertion_Function:
ForEach ($db in $dbList) {
Write-Warning "$errorCount database
.
.
.
try {
$totalProperties = Get-ServerDBMetadata -DBConnectionString ($connStr) -DatabaseName $dbName -EA SilentlyContinue
} catch {
Write-Warning "Unable to extract metadata from $dbname in $server"
}
}
I then have the error count printing out inside the loop that extracts/inserts the metadata from each database to the insertion database, as well as in the loop for each server that contains the databases:
WARNING: 0 Before function
WARNING: 0 database
.
.
.
WARNING: 0 database
WARNING: Unable to extract metadata from *database* in *server*
WARNING: 0 database
.
.
.
WARNING: 0 database
**WARNING: 1 After function**
The error (permission issue) is caught inside the function but cascades to my wrapper script. I want this particular error to be ignored while NOT ignoring other, more serious errors (like being unable to connect to the server I'm inserting the metadata into) so placing -EA Ignore on the driver function inside the wrapper script is out of the question.
Replace SilentlyContinue with Ignore to ignore the error and not have it increase the count.
Extraction/Insertion_Function -serverList $serverList -insertionDB $insertionDB -ErrorAction Ignore
To catch it inside the function, use -ErrorAction Stop as in thepip3r's answer as try/catch statements only catch terminating errors.
Your primary problem with the try-catch not catching the error (even though you don't supply all of the code) is that your cmdlet explicitly calls -ErrorAction SilentlyContinue. Try/Catch blocks REQUIRE the use of terminating errors so in the case of your function/cmdlet, you need to change to -ErrorAction Stop for try/catch to appropriately handle an error from that function/cmdlet.
This needs to be updated for any other function/cmdlet in the code we can't see.
Edit described in comments below:
$n = New-Object PSObject -property #{
'Test1' = ''
'Test2' = ''
'Test3' = ''
}
try {
get-process someprocess -ErrorAction Stop
$n.Test1 = $true
} catch {
$n.Test1 = $false
}
try {
Get-WmiObject win32_computersystem -ErrorAction Stop
$n.Test2 = $true
} catch {
$n.Test2 = $false
}
try {
Get-Content somefile.ext -ErrorAction Stop
$n.Test3 = $true
} catch {
$n.Test3 = $false
}
if ($n.Test1 -and $n.Test2 -and $n.Test3) {
## All procedures completed successfully -- do something magical
} else {
## At least one test procedure failed.
}
Logging the remaining errors since the last log:
While ($Global:ErrorCount -lt $Error.Count) {
$Err = $Error[$Error.Count - ++$Global:ErrorCount]
$ErrLine = "Error at $($Err.InvocationInfo.ScriptLineNumber),$($Err.InvocationInfo.OffsetInLine): $Err"
Write-Host $ErrLine -ForegroundColor Red # Log this
}

How to do a -Contain -Not : String contains "Anything other than"

How can I script "Does String -contains -not _" / "Does the string contain anything other than _"?
I'm not stuck as I've found a good enough work around. More curiosity than anything else.
Example:
$String = 1,1,1,2,5
$String -contains !(1)
This always comes up False
My solution at the moments is to remove the 1's and see if it's null like so:
$String2 = $String -ne 1
if ([String]::IsNullOrEmpty($String2)) {
Write-Host "True"
} else {
Write-Host "False"
}
Real World Example:
My script is designed to try a certain action until it works. In this case get-msoluser.
At the end of my script I want to count any errors (and list them later) but there will always be an error listed for "get-msoluser" as it fails until it works. So I'm trying to not include that certain error in the count.
$Errors = $Error.InvocationInfo.MyCommand.Name
if ($Errors -contains !("get-msoluser")) {
Write-Host "There was an error I actually care about"
}
INSTEAD I have to do this:
$Errors = $Error.InvocationInfo.MyCommand.Name
$ErrorsICareAbout = $Errors -ne "get-msoluser"
if ([String]::IsNullOrEmpty($ErrorsICareAbout)) {
Write-Host "$ErrorsICareAbout.Count"
} else {
Write-Host "There were errors you actually cared about"
}
Am I missing something that's right under my nose?
You simply need to use -notcontains or add the not operator around then entire -contains comparison like this:
If ($Errors -notcontains ("get-msoluser"))
or
If (!($Errors -contains ("get-msoluser")))
Rather than filtering out the error, try not producing an error in the first place. To suppress errors from a particular command, you can set the error action to SilentlyContinue.
Write-Error 'fail' -ErrorAction SilentlyContinue
So in the case of retrying until Get-MsOlUser works, you could use something like
while($msolUser -eq $null) {
$msolUser = Get-MsOlUser ... -ErrorAction SilentlyContinue
#Wait a second before retrying.
Start-Sleep -Seconds 1
}
#Now work with $msolUser
(You probably also want to put an upper limit on the number of retries)

Catch error and restart the if statement

I have a powershell script that adds a computer to a domain. Sometimes, when I run the script I get the following error and when I run it for the second time it works.
How can I make the script to check if I get this error, and if so then to retry adding it to the domain?
I have read that it is hard to try and catch errors like that. Is that correct? Is there a better/different way to catch the error?
Thank you!
Code:
if ($localIpAddress -eq $newIP)
{ # Add the computer to the domain
write-host "Adding computer to my-domain.local.. "
Add-Computer -DomainName my-domain.local | out-null
} else {...}
Error:
This command cannot be executed on target computer('computer-name') due to following error: The specified domain either does not exist or could not be contacted.
You can use the built in $Error variable. Clear it before executing code, then test if the count is gt 0 for post error code.
$Error.Clear()
Add-Computer -DomainName my-domain.local | out-null
if($Error.count -gt 0){
Start-Sleep -seconds 5
Add-Computer -DomainName my-domain.local | out-null}
}
You could setup a function to call itself on the Catch. Something like:
function Add-ComputerToAD{
Param([String]$Domain="my-domain.local")
Try{
Add-Computer -DomainName $Domain | out-null
}
Catch{
Add-ComputerToAD
}
}
if ($localIpAddress -eq $newIP)
{ # Add the computer to the domain
write-host "Adding computer to my-domain.local.. "
Add-ComputerToAD
} else {...}
I haven't tried it to be honest, but I don't see why it wouldn't work. It is not specific to that error, so it'll infinitely loop on repeating errors (i.e. another computer with the same name exists in AD already, or you specify an invalid domain name).
Otherwise you could use a While loop. Something like
if ($localIpAddress -eq $newIP)
{ # Add the computer to the domain
write-host "Adding computer to my-domain.local.. "
While($Error[0].Exception -match "The specified domain either does not exist or could not be contacted"){
Add-Computer -DomainName my-domain.local | out-null
}
}