STtools.psm1

Import-Module NTFSSecurity
function Get-STGroupsFromCSV {
    <#
    .SYNOPSIS
    Gets/outputs group names and associated/unassociated users.
    Good for adding/removing year level or class groups for staff/students.
 
    .DESCRIPTION
    Gets user and group information from a CSV file that has at least two columns; one with usernames, the other with group names.
    The command will output to the pipeline:
    * A generated group name made up of the group field and your prefix/postfix/both
    * A list of Active Directory User objects (like you would get from Get-ADUser) who should be in the group based on their username
    * A list of Active Directory User objects (like you would get from Get-ADUser) who should NOT be in the group (all OTHER users from AD)
 
    .PARAMETER csvfile
    The source CSV file that contains a field for usernames and a field for groups
 
    .PARAMETER username_header
    The header of the field that contains usernames (Defaults to "Student Code")
 
    .PARAMETER group_header
    The header of the field that contains group (Defaults to "Roll Class Code")
 
    .PARAMETER group_name
    The group name to assign ALL users that result from this list. Use if there is no group_header that makes sense, but a filter does.
 
    .PARAMETER prefix
    A Prefix to prepend to the group name to generate the desired group name (Eg. "Year" to generate "Year7"). Can also be used with postfix.
 
    .PARAMETER postfix
    A Postfix to append to the group name to generate the desired group name (Eg. "Students" to generate "7Students"). Can also be used with prefix.
 
    .PARAMETER onlynumbers
    Only the numbers are kept in the group field. Useful when you're trying to create year groups from fields that have letters (Eg. "7B" -> "7")
 
    .PARAMETER stripzeros
    Strips leading zeros on for example a group year (Eg. "07" -> "7")
 
    .EXAMPLE
    Get-STGroupsFromCSV -csvfile \\path\to\csv\file.csv -prefix "Year" |ForEach-Object {
        Remove-ADGroupMember -Identity $_.Identity -Members $_.NonMembers -Confirm:$false
        Add-ADGroupMember -Identity $_.Identity -Members $_.Members -Confirm:$false
    }
 
    Get year groups and associated users from file.csv, for each "YearX" group, remove the users who don't appear in the CSV file, and add users who do
 
    .EXAMPLE
    Get-STGroupsFromCSV -csvfile \\path\to\csv\file.csv -postfix "Teachers" -username_header "Teacher Code" |ForEach-Object {
        Remove-ADGroupMember -Identity $_.Identity -Members $_.NonMembers -Confirm:$false
        Add-ADGroupMember -Identity $_.Identity -Members $_.Members -Confirm:$false
    }
 
 
    Get year groups and associated users from file.csv, for each "XTeachers" group, remove the users who aren't in the CSV file, add users who are in the CSV file
 
    .EXAMPLE
    Get-STGroupsFromCSV -csvfile \\CASES\share\ST_XXXX.csv
 
 
    #>

    Param(
        [Parameter(Mandatory=$true)]
        [string]
        $csvfile,
        [Parameter(Mandatory=$false,HelpMessage="The CSV table header for student code (default is 'Student Code' for TTv7)")]
        [string]
        $username_header = "Student Code",
        [Parameter(Mandatory=$false,HelpMessage="The CSV table header for year level (default is 'Roll Class Code' for TTv7)")]
        [string]
        $group_header,
        [Parameter(Mandatory=$false,HelpMessage="Prepends this to the beggining of each year level (eg. Year -> Year7)")]
        [string]
        $group_name,
        [Parameter(Mandatory=$false,HelpMessage="Prepends this to the beggining of each year level (eg. Year -> Year7)")]
        [string]
        $prefix = "",
        [Parameter(Mandatory=$false,HelpMessage="Appends this to the beggining of each year level (eg. students -> 7students)")]
        [string]
        $postfix = "",
        [Parameter(Mandatory=$false,HelpMessage="Strips leading zeros on group year")]
        [switch]
        $stripzeros,
        [Parameter(Mandatory=$false,HelpMessage="Selects only numbers from the group field (eg. '7B' -> '7')")]
        [switch]
        $onlynumbers,
        [Parameter(Mandatory=$false,HelpMessage='Filters records in CSV according to this filter (eg. -filter {$_.STATUS -eq "ACTV"')]
        [scriptblock]
        $filter
    )
    Begin{
        if ($group_name -and $($group_header -or $prefix -or $postfix -or $stripzeros -or $onlynumbers)) {
            Throw "Group Name must be used on its own!"
        } elseif ($group_name) {
            $groups = @{}
            if ($filter) {
                Import-Csv $csvfile |Where-Object $filter |ForEach-Object {$groups.$($group_name) += @($($_.$username_header))}
            } else {
                Import-Csv $csvfile |ForEach-Object {$groups.$($group_name) += @($($_.$username_header))}
            }
        } else {
            $groups = @{}
            if ($filter) {
                if ($onlynumbers) {
                    if ($stripzeros) {
                        Import-Csv $csvfile |Where-Object $filter |ForEach-Object {$groups.$($($_.$group_header -replace '\D+').trimstart('0')) += @($($_.$username_header))}
                    } else {
                        Import-Csv $csvfile |Where-Object $filter |ForEach-Object {$groups.$($_.$group_header -replace '\D+') += @($($_.$username_header))}
                    }
                } else {
                    if ($stripzeros) {
                        Import-Csv $csvfile |Where-Object $filter |ForEach-Object {$groups.$($($_.$group_header).trimstart('0')) += @($($_.$username_header))}
                    } else {
                        Import-Csv $csvfile |Where-Object $filter |ForEach-Object {$groups.$($_.$group_header) += @($($_.$username_header))}
                    }
                }
            }
            else {
                if ($onlynumbers) {
                    if ($stripzeros) {
                        Import-Csv $csvfile |ForEach-Object {$groups.$($($_.$group_header -replace '\D+').trimstart('0')) += @($($_.$username_header))}
                    } else {
                        Import-Csv $csvfile |ForEach-Object {$groups.$($_.$group_header -replace '\D+') += @($($_.$username_header))}
                    }
                } else {
                    if ($stripzeros) {
                        Import-Csv $csvfile |ForEach-Object {$groups.$($($_.$group_header).trimstart('0')) += @($($_.$username_header))}
                    } else {
                        Import-Csv $csvfile |ForEach-Object {$groups.$($_.$group_header) += @($($_.$username_header))}
                    }
                }
            }
        }
    }
    Process{
        foreach ($group in $groups.Keys) {
            $unique_users = @()
            $adusers = @()
            $unique_users += $groups.$group |Select-Object -Unique
            foreach ($user in $unique_users) {
                try {
                    $adusers += $(Get-ADUser -Identity $user)
                }
                catch {
                    Write-Warning "Couldn't find AD User account for $user"
                }
            }
            $non_members = Get-ADUser -Filter * |Where-Object {$_.samaccountname -notin $unique_users}
            Write-Debug "Group: $group"
            Write-Debug "Members: $adusers"
            Write-Debug "Non-members: $non_members"
            $props = @{
                'Identity' = "$prefix$group$postfix";
                'Members' = $adusers;
                'NonMembers' = $non_members
            }
            $obj = New-Object -TypeName PSObject -Property $props
            $obj.PSObject.TypeNames.Insert(0,"ST.GroupUsers")
            Write-Output $obj
        }
    }
}
function ConvertFrom-STEduHubSF {
    <#
    .SYNOPSIS
    Converts EduHUB Staff CSV file to objects suitable for consumption by New-ADUser and Set-ADUser
 
    .DESCRIPTION
    Gets staff information from a piped in EduHUB CSV file, and outputs objects
    that can be used by New-ADUser, Set-ADUser, Add-STUser, and Set-STUser
    The command will output objects to the pipeline with the following properties:
    * DisplayName
    * EmailAddress
    * Enabled
    * GivenName
    * HomeDirectory
    * HomeDrive
    * Name
    * PasswordNotRequired
    * SamAccountName
    * Surname
    * UserPrincipalName
 
    .PARAMETER UserList
    The source imported EduHUB CSV
 
    .PARAMETER HomeDirBase
    Path to a shared directory in which to create the user's home drive
 
    .PARAMETER Domain
    Domain name used to create EmailAddress and UPN properties
 
    .PARAMETER HomeDrive
    Drive letter for the user's home directory including the colon (eg. "H:", not "H")
 
    .PARAMETER Enabled
    True by default, but can be set to false to create accounts in a disabled state, or disable existing accounts
 
    .EXAMPLE
    Create new staff user accounts from EduHUB records that have an "ACTV" status
 
    Import-Csv -Path "\\path\to\eduhub\SF_0000.csv" |
        Where-Object {$_.STAFF_STATUS -eq "ACTV"} |
        ConvertFrom-STEduHubSF -HomeDirBase "\\my\network\share" -Domain "mydomain.com" -HomeDrive "H:" |
        New-ADUser
 
    .EXAMPLE
    Deactivate staff accounts that don't have an "ACTV" status
 
    Import-Csv -Path "\\path\to\eduhub\SF_0000.csv" |
        Where-Object {$_.STAFF_STATUS -ne "ACTV"} |
        ConvertFrom-STEduHubSF -HomeDirBase "\\my\network\share" -Domain "mydomain.com" -HomeDrive "H:" -Enabled:$false |
        Set-ADUser
 
    .EXAMPLE
    Update accounts that have an "ACTV" status to use a different home drive letter ("U:" instead of "H:")
 
    Import-Csv -Path "\\path\to\eduhub\SF_0000.csv" |
        Where-Object {$_.STAFF_STATUS -eq "ACTV"} |
        ConvertFrom-STEduHubSF -HomeDirBase "\\my\network\share" -Domain "mydomain.com" -HomeDrive "U:" |
        Set-ADUser
 
 
    #>

    [CmdletBinding()]
    param (
        # list of users
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [System.Object[]]
        $UserList,
        # Directory (share) where user home dirs are created
        [Parameter(Mandatory=$true)]
        [string]
        $HomeDirBase,
        # Domain name (to generate email address)
        [Parameter(Mandatory=$true)]
        [string]
        $Domain,
        # Home drive letter (not including colon)
        [Parameter(Mandatory=$true)]
        [string]
        $HomeDrive,
        # Wether the account should be enabled or not
        [Parameter(Mandatory=$false)]
        [bool]
        $Enabled=$true
    )

    process {
        foreach ($user in $UserList) {
            $converteduser = $user |
            Select-Object -Property `
                @{Name="DisplayName"; Expression={"$($_.FIRST_NAME) $($_.SURNAME)"}},
                @{Name="EmailAddress"; Expression={"$($_.SFKEY)@$Domain"}},
                @{Name="Enabled"; Expression={$Enabled}},
                @{Name="GivenName"; Expression={"$($_.FIRST_NAME)"}},
                @{Name="HomeDirectory"; Expression={"$(Join-Path $HomeDirBase $_.SFKEY)"}},
                @{Name="HomeDrive"; Expression={"$HomeDrive"}},
                @{Name="Name"; Expression={"$($_.SFKEY)"}},
                @{Name="PasswordNotRequired"; Expression={$false}},
                @{Name="SamAccountName"; Expression={$_.SFKEY}},
                @{Name="Surname"; Expression={$_.SURNAME}},
                @{Name="UserPrincipalName"; Expression={"$($_.SFKEY)@$Domain"}}
            Write-Output $converteduser
        }
    }
}


function ConvertFrom-STEduHubST {
    <#
    .SYNOPSIS
    Converts EduHUB Student CSV file to objects suitable for consumption by New-ADUser and Set-ADUser
 
    .DESCRIPTION
    Gets student information from a piped in EduHUB CSV file, and outputs objects
    that can be used by New-ADUser, Set-ADUser, Add-STUser, and Set-STUser
    The command will output objects to the pipeline with the following properties:
    * DisplayName
    * EmailAddress
    * Enabled
    * GivenName
    * HomeDirectory
    * HomeDrive
    * Name
    * SamAccountName
    * Surname
    * UserPrincipalName
 
    .PARAMETER UserList
    The source imported EduHUB CSV
 
    .PARAMETER HomeDirBase
    Path to a shared directory in which to create the user's home drive
 
    .PARAMETER Domain
    Domain name used to create EmailAddress and UPN properties
 
    .PARAMETER HomeDrive
    Drive letter for the user's home directory including the colon (eg. "H:", not "H")
 
    .PARAMETER Enabled
    True by default, but can be set to false to create accounts in a disabled state, or disable existing accounts
 
    .EXAMPLE
    Create new student user accounts from EduHUB records that have an "ACTV" status
 
    Import-Csv -Path "\\path\to\eduhub\ST_0000.csv" |
        Where-Object {$_.STATUS -eq "ACTV"} |
        ConvertFrom-STEduHubST -HomeDirBase "\\my\network\share" -Domain "mydomain.com" -HomeDrive "H:" |
        New-ADUser
 
    .EXAMPLE
    Deactivate student accounts that don't have an "ACTV" status
 
    Import-Csv -Path "\\path\to\eduhub\ST_0000.csv" |
        Where-Object {$_.STATUS -ne "ACTV"} |
        ConvertFrom-STEduHubST -HomeDirBase "\\my\network\share" -Domain "mydomain.com" -HomeDrive "H:" -Enabled:$false |
        Set-ADUser
 
    .EXAMPLE
    Update accounts that have an "ACTV" status to use a different home drive letter ("U:" instead of "H:")
 
    Import-Csv -Path "\\path\to\eduhub\ST_0000.csv" |
        Where-Object {$_.STATUS -eq "ACTV"} |
        ConvertFrom-STEduHubST -HomeDirBase "\\my\network\share" -Domain "mydomain.com" -HomeDrive "U:" |
        Set-ADUser
 
 
    #>

    [CmdletBinding()]
    param (
        # list of users
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [System.Object[]]
        $UserList,
        # Directory (share) where user home dirs are created
        [Parameter(Mandatory=$true)]
        [string]
        $HomeDirBase,
        # Domain name (to generate email address)
        [Parameter(Mandatory=$true)]
        [string]
        $Domain,
        # Home drive letter (not including colon)
        [Parameter(Mandatory=$true)]
        [string]
        $HomeDrive,
        # Wether the account should be enabled or not
        [Parameter(Mandatory=$false)]
        [bool]
        $Enabled=$true
    )

    process {
        foreach ($user in $UserList) {
            $converteduser = $user |
            Select-Object -Property `
                @{Name="DisplayName"; Expression={"$($_.FIRST_NAME) $($_.SURNAME)"}},
                @{Name="EmailAddress"; Expression={"$($_.STKEY)@$Domain"}},
                @{Name="Enabled"; Expression={$Enabled}},
                @{Name="GivenName"; Expression={"$($_.FIRST_NAME)"}},
                @{Name="HomeDirectory"; Expression={"$(Join-Path $HomeDirBase $_.STKEY)"}},
                @{Name="HomeDrive"; Expression={"$HomeDrive"}},
                @{Name="Name"; Expression={"$($_.STKEY)"}},
                @{Name="DOB"; Expression={"$(NormaliseBirthDate -date $($_.BIRTHDATE))"}},
                @{Name="SamAccountName"; Expression={$_.STKEY}},
                @{Name="Surname"; Expression={$_.SURNAME}},
                @{Name="UserPrincipalName"; Expression={"$($_.STKEY)@$Domain"}}
            Write-Output $converteduser
        }
    }
}


function Update-Properties {
    <#
    .SYNOPSIS
    Updates an ADUser object's proerties from the properties of a compatible object
 
    .DESCRIPTION
    Takes Source and Destination objects, and updates matching properties on the destination object with values from the source object
 
    .PARAMETER SourceObject
    The source CSV file that contains a field for usernames and a field for groups
 
    .PARAMETER DestinationObject
    The header of the field that contains usernames (Defaults to "Student Code")
 
    .EXAMPLE
    Update a single user
 
    $updatedUser = Import-Csv "\\eduhub\ST_0000.csv" |where-object {$_.STKEY -eq "ABC0001"} | ConvertFrom-STEduHubST
    $targetUser = Get-ADUser "ABC0001"
    Update-Properties -sourceObject $updatedUser -destinationObject $targetUser
 
 
    #>

    [CmdletBinding()]
    param (
        # Source object
        [Parameter(Mandatory=$true)]
        [System.Object]
        $sourceObject,
        # Destination object
        [Parameter(Mandatory=$true)]
        [System.Object]
        $destinationObject
    )

    process {
        $updated = $false
        $properties = $sourceObject |Get-Member |Where-Object {$_.membertype -eq "NoteProperty"}
        $properties |ForEach-Object {
            if ($_.Name -ne "DOB" -and $_.Name -ne "Name") {  # This is very bad... VERY bad. This ties our data to our code giving us tight coupling and making this script less robust and less portable.
                if ($sourceObject.$($_.Name) -ne $destinationObject.$($_.Name)) {
                    write-host "$($_.Name): $($sourceObject.$($_.Name)) and $($destinationObject.$($_.Name)) are different"
                    $destinationObject.Item("$($_.Name)").Value = $sourceObject.$($_.Name)
                    $updated = $true
                }
            }
        }
        if ($updated -eq $true) {
            Write-Output $destinationObject
        }
    }
}


function Get-STRandomCharacter($range, $count) {
    return $([string](-join ($range |Get-Random -count $count | ForEach-Object {[char]$_})))
}


function Get-STNewCompliantPassword () {
    $randomChars = $($(Get-STRandomCharacter -range (48..57) -count 3) +
        $(Get-STRandomCharacter -range (33..47) + (58..64) -count 2) +
        $(Get-STRandomCharacter -range (65..90) -count 3) +
        $(Get-STRandomCharacter -range(97..122) -count 4)) -split "" |Sort-Object {Get-Random}
    return -join $randomChars
}


function Set-STUser ($users, $homeDirBase="", $allUsers) {
    <#
    .SYNOPSIS
    Wraps Set-ADUser adding home directory creation and permission setting
 
    .DESCRIPTION
    Set-ADUser on users that occur in -allUsers using updated properties from -users
    Then creates home directories (if applicable) and sets full control for the user on their own home directory
 
    .PARAMETER users
    List of ADUser compatible objects with new properties
 
    .PARAMETER DestinationObject
    List of ADUser objects to be updated from AD
 
    .PARAMETER homeDirBase
    Path to directory in which to create user home directories
 
    .EXAMPLE
    Update active users with new/changed properties in EduHUB CSV file
    # Be more specific if you have a large directory structure
    $allUsers = Get-ADUser -filter * -Properties *
 
    $activeStaffList = Import-Csv $staffcsv | Where-Object { ($_.STAFF_STATUS -eq "ACTV") } |
    ConvertFrom-STEduHubSF -HomeDirBase \\fs1\users\ -Domain mydomain.com -HomeDrive U:
 
    $staffHomeDirBase = "\\fs1\users\"
 
    Set-STUser -users $activeStaffList -HomeDirBase $staffHomeDirBase -allUsers $allUsers
 
 
    #>

    $users |ForEach-Object {
        foreach ($account in $allUsers) {
            if ($_.samaccountname -eq $account.samaccountname) {
                Write-Host "Checking: $($account.samaccountname)"
                Update-Properties -sourceObject $_ -destinationObject $account
            }
        }
    } |ForEach-Object {set-aduser -Instance $_}
    $users |ForEach-Object {
        # ensure that we have a valid base directory to put files in (doesn't cover all cases, eg. if a registry container is passed in)
        # ensure the folder doesn't already exist
        # ensure the user exists before creating/setting permissions on the user's folder
        if ($(Test-Path -PathType Container -Path $homeDirBase) -and `
            -not $(Test-Path -PathType Container $(Join-Path -Path $homeDirBase -ChildPath $_.samaccountname)) `
            -and $($_.samaccountname -in $allUsers.samaccountname)) {
                New-Item -ItemType Directory -Path $(Join-Path -Path $homeDirBase -ChildPath $_.samaccountname)
                Add-NTFSAccess -Path $(Join-Path -Path $homeDirBase -ChildPath $_.samaccountname) -Account $_.samaccountname -AccessRights FullControl
        }
    }
}


function New-STUser {
    <#
    .SYNOPSIS
    Wraps New-ADUser adding home directory creation and permission setting
 
    .DESCRIPTION
    New-ADUser on users that occur in -sourceUsers
    Then creates home directories (if applicable) and sets full control for the user on their own home directory
 
    .PARAMETER sourceUsers
    List of ADUser compatible objects
 
    .PARAMETER currentUsers
    List of all ADUser objects (so this cmdlet knows which users already exist)
 
    .PARAMETER ouLookup
    Hash of OU => list of users (users are created in the OU specified in this hash)
 
    .PARAMETER homeDirBase
    String referring to the directory in which this script should create user home directories
     
    .PARAMETER smtp_server
    SMTP server for sending user creation reports
 
    .PARAMETER support_address
    From address for user creation reports
 
    .PARAMETER to_addresses
    One or more email addresses to send new user creation reports to
 
    .EXAMPLE
    Create new accounts in the default OU and email support@mydomain.com with new user account details
 
    $activeStaffList = Import-Csv $staffcsv | Where-Object { ($_.STAFF_STATUS -eq "ACTV") } |
    ConvertFrom-STEduHubSF -HomeDirBase \\fs1\users\ -Domain mydomain.com -HomeDrive U:
 
    $allUsers = Get-ADUser -filter *
 
    New-STUser -sourceUsers $activeStaffList -currentUsers $allUsers -HomeDirBase "\\fs1\users\" -smtp_server "exch01" -support_address "support@mydomain.com" -to_addresses "support@mydomain.com", "boss@mydomain.com"
 
 
    #>

    [CmdletBinding()]
    param(
        # List of source user objects
        [Parameter(Mandatory=$true)]
        [System.Object[]]
        $sourceUsers,
        # List of current AD users
        [Parameter(Mandatory=$true)]
        [System.Object[]]
        $currentUsers,
        # Hash of OU => list of users
        [Parameter(Mandatory=$false)]
        [hashtable]
        $ouLookup,
        # Home Directory base (folder in which to put home drive)
        [Parameter(Mandatory=$true)]
        [string]
        $homeDirBase,
        # SMTP server to use when sending creation report
        [Parameter(Mandatory=$false)]
        [string]
        $smtp_server,
        # Email address from which to send report email
        [Parameter(Mandatory=$false)]
        [string]
        $support_address,
        # Where to send new user reports
        [Parameter(Mandatory=$false)]
        [string[]]
        $to_addresses
    )
    process{
        $sourceUsers |ForEach-Object {
            if ($_.samaccountname -notin $currentUsers.samaccountname) {
                Write-Host "$($_.samaccountname) doesn't have an account yet... creating..."
                $_.PSObject.Properties.Remove('DOB')
                $plainpassword = Get-STNewCompliantPassword
                $_ |add-member -MemberType NoteProperty -name "AccountPassword" -value $(convertto-securestring $plainpassword -AsPlainText -Force) -Force
                $_ |add-member -MemberType NoteProperty -name "PlainPassword" -value $plainpassword
                Write-Output $_
            } else {
                if ($PSBoundParameters['Verbose']) {
                    Write-Host "Account already exists for $($_.samaccountname)... skipping..."
                }
            }
        } | foreach-object {
            $_ |New-ADUser -ErrorAction Stop
            if ($smtp_server -and $support_address -and $to_addresses) {
            Send-MailMessage -SmtpServer $smtp_server -To $to_addresses -From $support_address -Subject "New User report" -BodyAsHtml "<h2>New user:</h2><p>$($_.GivenName) $($_.Surname)</p><code>Username: $($_.samaccountname)</code>`n<br />`n<code>Password: $($_.PlainPassword)</code>"
            }
        }
        $sourceUsers |ForEach-Object {
            if (-not $(Test-Path -PathType Container $(Join-Path -Path $homeDirBase -ChildPath $_.samaccountname)) -and $($homeDirBase -ne "")) {
                New-Item -ItemType Directory -Path $(Join-Path -Path $homeDirBase -ChildPath $_.samaccountname) -ErrorAction Continue
                # Sleeping for a bit to allow propagation of new account before trying to apply folder permissions
                Start-Sleep -Seconds 20
                Add-NTFSAccess -Path $(Join-Path -Path $homeDirBase -ChildPath $_.samaccountname) -Account $_.samaccountname -AccessRights FullControl
            }
        }
    }
}

function NormaliseBirthDate ($date) {
    if ($date -eq "") {
        $date = "0/00/0000"
    }
    if ($date.Length -lt 22) {
        $date = "0" + $date
    }
    $date -match "(?<day>[0-9]+)/(?<month>[0-9]+)/(?<year>[0-9]+)" |Out-Null
    $date = $Matches["day"] + $Matches["month"] + $Matches["year"].Substring(2, 2)
    return $date
}

function Set-STOrganizationalUnit ($userOUs) {
    foreach ($OU in $userOUs.Keys) {
        $targetOU = ""
        try {
            $targetOU = Get-ADOrganizationalUnit $OU -ErrorAction Stop
        }
        catch {
            Write-host "$OU OU doesn't exist"
        }
        if ($targetOU -ne "") {
            $currentOUUsers = get-aduser -SearchBase $targetOU -SearchScope 1 -Filter *
            foreach ($user in $userOUs.$OU) {
                if ($user -notin $currentOUUsers.samaccountname) {
                    $usertomove = Get-ADUser $user
                    write-host "Moving $user to $targetOU"
                    Move-ADObject -Identity $usertomove.ObjectGUID -TargetPath $targetOU
                }
            }
        }
    }
}

function New-STManagedGroups {
    <#
    .SYNOPSIS
    Creates Universal security groups based on a list of group names and a target OU
 
    .DESCRIPTION
    Takes a list of group names and a target OU, and tries to create universal groups in that OU that don't already exist.
    This command is idempotent, so if the group name from the list already exists and is a Universal group, no changes will be
    made for that group. If the group does not exist yet, it will be created and made Universal.
 
    .PARAMETER newGroups
    A list/array of group names
 
    .PARAMETER GroupOU
    A string containing the OU that contains the current managed groups, and that will contain any new groups
 
    .EXAMPLE
    Get-STGroupsFromCSV -csvfile \\path\to\csv\file.csv -prefix "Year" |ForEach-Object {
        Remove-ADGroupMember -Identity $_.Identity -Members $_.NonMembers -Confirm:$false
        Add-ADGroupMember -Identity $_.Identity -Members $_.Members -Confirm:$false
    }
 
    Get year groups and associated users from file.csv, for each "YearX" group, remove the users who don't appear in the CSV file, and add users who do
 
    .EXAMPLE
    Get-STGroupsFromCSV -csvfile \\path\to\csv\file.csv -postfix "Teachers" -username_header "Teacher Code" |ForEach-Object {
        Remove-ADGroupMember -Identity $_.Identity -Members $_.NonMembers -Confirm:$false
        Add-ADGroupMember -Identity $_.Identity -Members $_.Members -Confirm:$false
    }
 
 
    Get year groups and associated users from file.csv, for each "XTeachers" group, remove the users who aren't in the CSV file, add users who are in the CSV file
 
    .EXAMPLE
    Get-STGroupsFromCSV -csvfile \\CASES\share\ST_XXXX.csv
 
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [String[]]
        $newGroups,
        [Parameter(Mandatory=$true)]
        [String]
        $GroupOU
    )
    
    process {
        $currentGroups = Get-ADGroup -Filter * -SearchBase $GroupOU
        foreach ($group in $newGroups) {
            if ($group -notin $currentGroups.name) {
                try {
                    New-ADGroup -Name $group -GroupScope Universal -Path $GroupOU -ErrorAction Stop
                    write-host "Created $group"
                }
                catch {
                    Write-Warning "Couldn't create group $group. Error was:`n$($_.Exception.Message)"
                }
            } else {
                write-host "$group already exists"
                $adgroup = Get-ADGroup $group
                if($adgroup.GroupScope -eq "Universal") {
                    write-host "$group is already a universal group"
                } else {
                    Write-Host "Changing $group to universal"
                    $adgroup | Set-ADGroup -GroupScope Universal
                }
            }
        }
    }
}