Exception Handling Question

Hi everyone.

I’ve been learning PowerShell off the side of my desk for awhile now, and I’m learning lots but just scratching the surface. I’ve been working on my first ‘real world’ project and I’ve run into a snag.

Our school just upgraded our ERP system and had to remove a lot of customization to allow us to complete the upgrade. Some of that customization affected a large account export that we use to populate Active Directory with student accounts. The idea is to add the groups the new version is creating into toe groups the old ERP had previously created in order to maintain student access. Unfortunately, some time in the past, the school wanted to denote certain programs, so they added extra characters into the name. So the naming convention isn’t the same for all groups. Here’s a basic synopsis.

Start by getting a list of the groups that have access associated with them. For each group in the list, do the following.

  • Add our standard Active Directory prefix for data access groups
  • Find the group.
  • If the group can't be found, add the special identifier and retry
  • Remove existing group members (as the import script no longer touches these groups, we don't want 1st year students seeing 2nd year exams)
  • Add the new group as a member of the old group
  • Find the new groups that associate with them and add them as members of the original group
I can catch the notfound exception using a Try/Catch, but not sure how to get the change managed and still trap other exceptions.

Here’s a sample of what I’m trying to do.

Import-Module ActiveDirectory

# Load the OUs we need into a variable.  The real script gets them from AD.
$OUs = 'OU1','OU2','OU3'

# Variable will hold groups that don't follow standard naming conventions
$NoGroup = @()

# Process the groups
$OUs | foreach {
    
    # Build the old group name
    $OldGroup = 'DtaG_Student_' + $_ + '_1Fa'

    # Build the new group name
    $NewGroup = 'DtaG_Student_' + $_ + '_190901Fa-'
    
    try
    {
        # Get-ADGroup $OldGroup

        # Get group members
        $Members = Get-ADGroupMember $OldGroup

        # Make sure last year's students aren't still in the old group
        Remove-ADGroupMember $Oldgroup -Members $Members -whatif

        # Add the new group to the old group
        Add-ADGroupMember $OldGroup -Members $NewGroup -whatif
    }
    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
    {
        $NoGroup += $OldGroup
        Write-Host $OldGroup NOT FOUND
        # if the old group was one of the TEP groups, change the Old Group format to DtaG_Student_TEP_$OU_1Fa, 
        # then remove existing members, add the new group before looping to the next OU

    }
}

I hope this is clear enough to give everyone an idea of what I’m trying to accomplish.

Thanks in advance for any help you can offer!

Derek

If I understand you correctly, you’re just looking to capture / log / handle additional exceptions, and just have special handling for the one exception type you’re expecting to have fairly commonly?

If so, you can cascade catch blocks one after another, adding extra ones for more exception types, and if you want a catch-all catch block for any exceptions that you’re not expecting / handling in a specific way, you just add a typeless catch block:

Thanks for your reply, Joel.

Sorry I wasn’t very clear. That’s what I get for posting a question on the way out the door :).

Basically, the intent is to add a string into the old group name before the variable part, then rerun the commands that empty the old group and add the new ones to it, but only once.

Is there a way to do this kind of thing with PowerShell?

Thanks.

Derek

Sounds like you’re just wanting to do recursion, which is definitely doable in Powershell, but you have to make a few changes.

  • Separate the repeatable part of the code into a function
  • If you hit the ADIdentityNotFoundException code, verify this is the first time you've executed the function for this group, modify the name, and call the function again.
  • Modify your loop to use the function instead of the code that is in the function
  • I've also added Joel's suggestions to put a generic catch block in your try..catch logic. That's always good practice, and it can alert you to other problems.

*** Note that I haven’t tested this code, so there are probably some things that need to be tweaked

Import-Module ActiveDirectory
# Load the OUs we need into a variable.  The real script gets them from AD.
$OUs = 'OU1','OU2','OU3'
# Variable will hold groups that don't follow standard naming conventions
$NoGroup = @()
# Process the groups
$OUs | foreach {
    # Build the old group name
    $OldGroup = 'DtaG_Student_' + $_ + '_1Fa'
    # Build the new group name
    $NewGroup = 'DtaG_Student_' + $_ + '_190901Fa-'
    
    CleanOldGroup -OldGroup $OldGroup -NewGroup $NewGroup
}


function CleanOldGroup
{
    param (
        [string]
        $OldGroup,

        [string]
        $NewGroup
    )
        
    try
    {
        # Get-ADGroup $OldGroup
        # Get group members
        $Members = Get-ADGroupMember $OldGroup
        # Make sure last year's students aren't still in the old group
        Remove-ADGroupMember $Oldgroup -Members $Members -whatif
        # Add the new group to the old group
        Add-ADGroupMember $OldGroup -Members $NewGroup -whatif
    }
    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
    {
        $NoGroup += $OldGroup
        Write-Host $OldGroup NOT FOUND
        # if the old group was one of the TEP groups, change the Old Group format to DtaG_Student_TEP_$OU_1Fa, 
        # then remove existing members, add the new group before looping to the next OU

        # This is assuming that if the value of $OldGroup doesn't already contain the extra string before the variable, 
        # then this is the first time you've executed the function for that group.
        if ($OldGroup -notlike "<modified $OldGroup name>")
        {
            $newGroupName = "<modified $OldGroup name>"

            CleanOldGroup -OldGroup $newGroupName -NewGroup $NewGroup
        }
    }
    catch {
        # Log additional errors, and either rethrow the error or pass it to Write-Error
        # depending on whether you want execution to terminate or keep going through the
        # rest of your OUs.
        
        # Rethrow (terminate execution) options:
        # 1. $PSCmdlet.ThrowTerminatingError($_)
        # 2. throw $_
        # 3. $_ | Write-Error -ErrorAction Stop
        
        # Otherwise just write-error:
        # $_ | Write-Error
    }
}

Thanks Charles! This looks like it’ll do what I need! I need to spend some more time on functions :).

I’m going to try this anyway, but can I use a wildcard pattern with -notlike? What I would need is something like -notlike ‘TEP

Thanks again for your help!

Yep, -like and -notlike are wildcard operators and should work as expected there. :slight_smile:

That’s what I get for not verifying my code when I pasted it in… I intended for that to be:

 if ($OldGroup -notlike "whatever the group name would be with the extra string")
        {
            $newGroupName = "whatever the group name would be with the extra string"
            CleanOldGroup -OldGroup $newGroupName -NewGroup $NewGroup
        }

You could also just wildcards in the conditional like “string to add to the old group name” so you wouldn’t have to build the group name before the conditional.

Hope it works out for you!

Thanks Charles.

That’s no problem. I knew what you meant the way you had it, so I filled I know I’m close, but it’s getting a little weird. It’s hitting the If statement, but after I insert the characters, it’s still hitting the Else as well. Since there are a few OUs whos names got completely changed in the conversion, I’m trying to write those somewhere so I can do them manually. I see the whatif saying it will update the group, but then I see the write-host I used to show me if it’s being hit.

Here’s the updated code.

Import-Module ActiveDirectory
# Load the OUs we need into a variable.  The real script gets them from AD.
$OUs = 'OU1','OU2','OU3','ETC'
# Variable will hold groups that don't follow standard naming conventions
$NoGroup = @()
# Process the groups
$OUs | foreach {
    # Build the old group name
    $OldGroup = 'DtaG_Student_' + $_ + '_1Fa'
    # Build the new group name
    $NewGroup = 'DtaG_Student_' + $_ + '_190901Fa-'
    
    NestGroup -OldGroup $OldGroup -NewGroup $NewGroup -OUName $_
}


function NestGroup
{
    param (
        [string]
        $OldGroup,

        [string]
        $NewGroup,

        [string]
        $OUName
    )
        
    try
    {
        # Get-ADGroup $OldGroup
        # Get group members
        $Members = Get-ADGroupMember $OldGroup
        # Make sure last year's students aren't still in the old group
        Remove-ADGroupMember $Oldgroup -Members $Members -whatif
        # Add the new group to the old group
        Add-ADGroupMember $OldGroup -Members $NewGroup -whatif
    }
    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
    {
        #$NoGroup += $OldGroup
        # Write-Host $OldGroup NOT FOUND
        # if the old group was one of the TEP groups, change the Old Group format to DtaG_Student_TEP_$OU_1Fa, 
        # then remove existing members, add the new group before looping to the next OU

        # This is assuming that if the value of $OldGroup doesn't already contain the extra string before the variable, 
        # then this is the first time you've executed the function for that group.
        $NewGroupName = ''
        
        if ($OldGroup -notlike "DtaG_Student_TEP_*")
        {
            $newGroupName = "DtaG_Student_TEP_" + $OUName + "_1Fa"

            NestGroup -OldGroup $newGroupName -NewGroup $NewGroup -OUName $OUName
        }
        else
        {
            write-host $OldGroup + "doesn't exist!"
            $NoGroup += $OldGroup
        }
    }
    catch {
        # Log additional errors, and either rethrow the error or pass it to Write-Error
        # depending on whether you want execution to terminate or keep going through the
        # rest of your OUs.
        
        # Rethrow (terminate execution) options:
        # 1. $PSCmdlet.ThrowTerminatingError($_)
        # 2. throw $_
        # 3. $_ | Write-Error -ErrorAction Stop
        
        # Otherwise just write-error:
        # $_ | Write-Error
    }
}

That’s pretty screwy, Derek… The code looks good, I think. Are you sure that the Write-Host isn’t for another execution of the function, (i.e. the next OU that you’re trying to modify)?

It may also be something with the -whatif on the other statements. If the Remove and Add statements don’t end with a real return code, the code may fall into that catch block… doesn’t really make sense to me, either, but I’ve seen some strange stuff with it.

A couple of things I would try in troubleshooting:

  • Add some more Write-Host/Write-Debug statements. You could put one in the Try block that executes every time you enter it or after the Add-ADGroupMember statement or both. And then put one in the Catch block before the If and one inside the If. That way you would know exactly which blocks are being executed and it's easier to follow the logic through the output. (FWIW, I've gotten to where I really like the Write-Debug option. I can add lots of output to help with troubleshooting, but I don't have to see them one every execution if I don't want to! It's just a matter of modifying the $DebugPreference session variable.)
  • Add a break statement in the Try block after the Add-ADGroupMember. That would force the code to jump out of the function if the Add-ADGroupMember statement executed successfully. If your problems are because of the -whatif, this may force the code to recognize everything is good and that it isn't supposed to execute the Catch block.
CMD

Thanks for your response, Charles! This is very helpful!

 

[quote quote=175210]That’s pretty screwy, Derek… The code looks good, I think. Are you sure that the Write-Host isn’t for another execution of the function, (i.e. the next OU that you’re trying to modify)?
[/quote]

I’m sure this is not the case, because I see the whatif for the Add, then I see the Write-Host with the same name.

[quote quote=175210]A couple of things I would try in troubleshooting:

  • Add some more Write-Host/Write-Debug statements. You could put one in the Try block that executes every time you enter it or after the Add-ADGroupMember statement or both. And then put one in the Catch block before the If and one inside the If. That way you would know exactly which blocks are being executed and it's easier to follow the logic through the output. (FWIW, I've gotten to where I really like the Write-Debug option. I can add lots of output to help with troubleshooting, but I don't have to see them one every execution if I don't want to! It's just a matter of modifying the $DebugPreference session variable.)
  • Add a break statement in the Try block after the Add-ADGroupMember. That would force the code to jump out of the function if the Add-ADGroupMember statement executed successfully. If your problems are because of the -whatif, this may force the code to recognize everything is good and that it isn't supposed to execute the Catch block.
[/quote]

I’ll play around with Write-debug. I haven’t used it much, as I’m just getting into PS, but it’s there for a reason :).

I’m going to try the break statement. That sounds like it’ll do the trick. I’ll let you know how it goes!

I figured out what’s going on!

Since the function is passing the TEP_ group in as $OldGroup, $OldGroup now contains the search text used in the If statement. Now to figure out how to fix it!

Thanks to both Charles and Joel for their help on this. Charles was right. Sopme of the errors were actually coming from the new group, because there were no first-years in those programs yet. For reference of anyone interested, here’s the final code.

Import-Module ActiveDirectory

Load the OUs we need into a variable. The real script gets them from AD.

$OUs = ‘OU1’,‘OU2’,‘OU3’,‘ETC’‘OOSD’,‘ISA’,‘DAAN’,‘IT’,‘BSN’,‘JAPO’,‘ITTS’,‘JAF’,‘IRM’,‘CS’

Variable will hold groups that don’t follow standard naming conventions

$NoGroup = @()

Process the groups

del $env:TEMP\FixBanner.txt
$OUs | foreach {

Build the old group name

$OldGroup = ‘DtaG_Student_’ + $_ + ‘_1Fa’

Build the new group name

$NewGroup = ‘DtaG_Student_’ + $_ + ‘_190901Fa-’

NestGroup -OldGroup $OldGroup -NewGroup $NewGroup -OUName $_
$NoGroup
}

function NestGroup
{
param (
[string]
$OldGroup,

[string]
$NewGroup,

[string]
$OUName
)

try
{

Get-ADGroup $OldGroup

Get group members

$Members = Get-ADGroupMember $OldGroup

Make sure last year’s students aren’t still in the old group

Remove-ADGroupMember $Oldgroup -Members $Members -whatif

Add the new group to the old group

Add-ADGroupMember $OldGroup -Members $NewGroup -whatif
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
{

if the old group was one of the TEP groups, change the Old Group format to DtaG_Student_TEP_$OU_1Fa,

then remove existing members, add the new group before looping to the next OU

This is assuming that if the value of $OldGroup doesn’t already contain the extra string before the variable,

then this is the first time you’ve executed the function for that group.

$NewGroupName = ‘’

if ($Error[0].CategoryInfo.TargetName -contains $OldGroup-and $OldGroup -notlike “DtaG_Student_TEP_*”)
{
$newGroupName = “DtaG_Student_TEP_” + $OUName + “_1Fa”

NestGroup -OldGroup $newGroupName -NewGroup $NewGroup -OUName $OUName
}
elseif ($Error[0].CategoryInfo.TargetName -contains $NewGroup)
{
Write-Host $NewGroup “doesn’t exist” -ForegroundColor Yellow

Out-File -FilePath $env:TEMP\FixBanner.txt -Append -InputObject $NewGroup

}
else
{

write-host $Error[0].CategoryInfo.TargetName -ForegroundColor Yellow

Out-File -FilePath $env:TEMP\FixBanner.txt -Append -InputObject (‘DtaG_Student_’ + $OUName + ‘_1Fa’)
}

}
catch {
Write-Host “Something’s not right!” -ForegroundColor Yellow

Log additional errors, and either rethrow the error or pass it to Write-Error

depending on whether you want execution to terminate or keep going through the

rest of your OUs.

Rethrow (terminate execution) options:

1. $PSCmdlet.ThrowTerminatingError($_)

2. throw $_

3. $_ | Write-Error -ErrorAction Stop

Otherwise just write-error:

$_ | Write-Error

}
}

 

w00t!

Well it turns out that both Charles and I are right. I did some more testing and found that sometimes the error was indeed coming from the new group. Apparently, we have some courses that don’t start September 1 :). So a little more work, and we have a working script!

Import-Module ActiveDirectory
# Load the OUs we need into a variable. The real script gets them from AD.
$OUs = 'OU1','OU3','OU2','ETC',
# Variable will hold groups that don't follow standard naming conventions
$NoGroup = @()
# Process the groups
del $env:TEMP\FixBanner.txt
$OUs | foreach {
# Build the old group name
$OldGroup = 'DtaG_Student_' + $_ + '_1Fa'
# Build the new group name
$NewGroup = 'DtaG_Student_' + $_ + '_190901Fa-'

NestGroup -OldGroup $OldGroup -NewGroup $NewGroup -OUName $_
$NoGroup
}

function NestGroup
{
param (
[string]
$OldGroup,

[string]
$NewGroup,

[string]
$OUName
)

try
{
# Get-ADGroup $OldGroup
# Get group members
$Members = Get-ADGroupMember $OldGroup
# Make sure last year's students aren't still in the old group
Remove-ADGroupMember $Oldgroup -Members $Members -whatif
# Add the new group to the old group
Add-ADGroupMember $OldGroup -Members $NewGroup -whatif
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
{
# if the old group was one of the TEP groups, change the Old Group format to DtaG_Student_TEP_$OU_1Fa, 
# then remove existing members, add the new group before looping to the next OU

# This is assuming that if the value of $OldGroup doesn't already contain the extra string before the variable, 
# then this is the first time you've executed the function for that group.
$NewGroupName = ''

if ($Error[0].CategoryInfo.TargetName -contains $OldGroup-and $OldGroup -notlike "DtaG_Student_TEP_*")
{
$newGroupName = "DtaG_Student_TEP_" + $OUName + "_1Fa"

NestGroup -OldGroup $newGroupName -NewGroup $NewGroup -OUName $OUName
}
elseif ($Error[0].CategoryInfo.TargetName -contains $NewGroup)
{
Write-Host $NewGroup "doesn't exist" -ForegroundColor Yellow
# Out-File -FilePath $env:TEMP\FixBanner.txt -Append -InputObject $NewGroup
}
else
{
# write-host $Error[0].CategoryInfo.TargetName -ForegroundColor Yellow
Out-File -FilePath $env:TEMP\FixBanner.txt -Append -InputObject ('DtaG_Student_' + $OUName + '_1Fa')
}

}
catch {
Write-Host "Something's not right!" -ForegroundColor Yellow
# Log additional errors, and either rethrow the error or pass it to Write-Error
# depending on whether you want execution to terminate or keep going through the
# rest of your OUs.

# Rethrow (terminate execution) options:
# 1. $PSCmdlet.ThrowTerminatingError($_)
# 2. throw $_
# 3. $_ | Write-Error -ErrorAction Stop

# Otherwise just write-error:
# $_ | Write-Error
}
}