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 (including colon)
        [Parameter(Mandatory=$true)]
        [string]
        $HomeDrive
    )

    process {
        foreach ($user in $UserList) {
            $converteduser = $user |
            Select-Object -Property `
                @{Name="DisplayName"; Expression={"$($_.FIRST_NAME) $($_.SURNAME)"}},
                @{Name="EmailAddress"; Expression={"$($_.SFKEY)@$Domain"}},
                @{Name="Enabled"; Expression={$($_.STAFF_STATUS -eq "ACTV")}},
                @{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 (including colon)
        [Parameter(Mandatory=$true)]
        [string]
        $HomeDrive
    )

    process {
        foreach ($user in $UserList) {
            $converteduser = $user |
            Select-Object -Property `
                @{Name="DisplayName"; Expression={"$($_.FIRST_NAME) $($_.SURNAME)"}},
                @{Name="EmailAddress"; Expression={"$($_.STKEY)@$Domain"}},
                @{Name="Enabled"; Expression={$($_.STATUS -eq "ACTV" -or $_.STATUS -eq "LVNG")}},
                @{Name="GivenName"; Expression={"$($_.FIRST_NAME)"}},
                @{Name="HomeDirectory"; Expression={"$(Join-Path $HomeDirBase $_.STKEY)"}},
                @{Name="HomeDrive"; Expression={"$HomeDrive"}},
                @{Name="HomeGroup"; Expression={"$($_.HOME_GROUP)"}},
                @{Name="Name"; Expression={"$($_.STKEY)"}},
                @{Name="SamAccountName"; Expression={$_.STKEY}},
                @{Name="Surname"; Expression={$_.SURNAME}},
                @{Name="UserPrincipalName"; Expression={"$($_.STKEY)@$Domain"}},
                @{Name="YearLevel"; Expression={"$($_.SCHOOL_YEAR)"}}
            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 "Name" -and $_.Name -ne "HomeGroup" -and $_.Name -ne "YearLevel") {
                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 |New-STHomeDirectory
}


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 OUPath
    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(SupportsShouldProcess=$True)]
    param(
        # List of source user objects
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [System.Object[]]
        $sourceUsers,
        # List of current AD users
        [Parameter(Mandatory=$false)]
        [System.Object[]]
        $currentUsers,
        # Function for generating new password for user
        [Parameter(Mandatory=$false)]
        [scriptblock]
        $passwordFunction
    )

    begin {
        if (-not $currentUsers) {
            $currentUsers = get-aduser -Filter * -Properties *
        } 
    }

    process{
        $sourceUsers |ForEach-Object {
            if ($passwordFunction) {
                $plainpassword = Invoke-Command $passwordFunction
            } else {
                $plainpassword = Get-STNewCompliantPassword
            }
            $_ |add-member -MemberType NoteProperty -name "AccountPassword" -value $(convertto-securestring $plainpassword -AsPlainText -Force) -Force
            $_ |add-member -MemberType NoteProperty -name "PlainPassword" -value $plainpassword -Force
            Write-Output $_
        } | foreach-object {
            if ($PSCmdlet.ShouldProcess("New User Created: $($_.samaccountname)")) {
                $_ |New-ADUser
                if ($_.homeDirectory -and $_.HomeDrive) {
                    $_ |New-STHomeDirectory
                }
            }
            Write-Output $_
        }
    }
}


function New-STHomeDirectory {
    param (
        # User Object(s)
        [Parameter(Mandatory=$true,ValueFromPipeline)]
        [object[]]
        $User
    )
    process {
        $_ |ForEach-Object {
            if (
                $_.Enabled `
                -and $_.HomeDirectory `
                -and $_.HomeDrive `
                -and $(Test-Path -PathType Container $(split-path -parent $_.HomeDirectory)) `
                ) {
                    if (-not $(Test-Path -PathType Container $_.HomeDirectory)) {
                        New-Item -ItemType Directory -Path $_.homeDirectory -ErrorAction Continue |Out-Null
                    }
                    $sam = $_.samaccountname
                    if (-not $(Get-NTFSAccess -Path $_.homeDirectory |Where-Object {$_.Account -contains $sam})) {
                        Add-NTFSAccess -Path $_.homeDirectory -Account $_.samaccountname -AccessRights FullControl |Out-Null
                    }
            }
        }
    }
}

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 Update-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,ValueFromPipeline=$true)]
        [String[]]
        $newGroups,
        [Parameter(Mandatory=$true)]
        [String]
        $GroupOU,
        # Parameter help description
        [Parameter(Mandatory=$false)]
        [object[]]
        $currentGroups
    )
    
    begin {
        if (-not $currentGroups) {
            $currentGroups = Get-ADGroup -Filter *
        }
    }

    process {
        $_ |ForEach-Object {
            if ($_ -notin $currentGroups.name) {
                try {
                    $adgroup = New-ADGroup -Name $_ -GroupScope Universal -Path $GroupOU -ErrorAction Stop -PassThru
                    write-host "Created group: $_ in OU $GroupOU"
                }
                catch {
                    Write-Warning "Couldn't create group $_. Error was:`n$($_.Exception.Message)"
                }
            } else {
                $adgroup = $false
                $adgroup = $($currentGroups | Where-Object Name -eq $_)
                if (-not $adgroup) {
                    try {
                        Get-ADGroup $_ -ErrorAction Stop
                    }
                    catch {
                        Write-Warning "Couldn't get group $_. Error was:`n$($_.Exception.Message)"
                    }
                }
            }
            if($adgroup -and $adgroup.GroupScope -ne "Universal") {
                try {
                    $adgroup | Set-ADGroup -GroupScope Universal
                    Write-Host "Changed GroupScope for $_ to Universal"
                }
                catch {
                    Write-Warning "Couldn't change GroupScope for $_ to Universal"
                }
            }
            if ($adgroup -and (-not $adgroup.distinguishedname -match "CN=$($adgroup.CN),$GroupOU")) {
                try {
                    Move-ADObject -Identity $adgroup.ObjectGUID -TargetPath $GroupOU
                    Write-Host "Moved $($adgroup.CN) to $GroupOU"
                }
                catch {
                    Write-Warning "Couldn't move $($adgroup.CN) to $GroupOU"
                }
            }
        }
    }
}

function Get-STManualActiveUsernames {
    try {
        $(Import-Csv "ManualActiveUserNames.csv").KEY
    }
    catch {
        Write-Warning "No manual active users file found"
        New-item "ManualActiveUserNames.csv"
    }
}

function Get-STFutureUsernames {
    try {
        $(Import-Csv "FutureUserNames.csv").KEY
    }
    catch {
        Write-Warning "No future users file found"
        New-item "FutureUserNames.csv"
    }
}

function Select-STFutureStudents {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [object[]]
        $casesUsers,
        [Parameter(Mandatory=$false)]
        [object[]]
        $ADUsers,
        [Parameter(Mandatory=$true)]
        [string]
        $FutureStudentFile
    )

    begin {
        $ExistingSTFutureStudents = Get-STFutureStudents -FutureStudentFile $FutureStudentFile
        if (-not $ADUsers) {
            Write-Warning "No AD Users passed in, grabbing manually..."
            Write-Warning "If you are running this command from a script,"
            Write-Warning "it's better to do:"
            Write-Warning '$ADUsers = Get-ADUser -Filter *'
            Write-Warning 'and call this function with -ADUsers $ADUsers'
            $ADUsers = Get-ADUser -Filter *
        }
    }

    process {
        $_ |Where-Object {
            $_.HomeGroup -eq "ZZZ" `
            -and $_.Enabled -eq $false `
            -and $_.SamAccountName -notin $ADUsers.SamAccountName `
            -and $_.SamAccountName -notin $($ExistingSTFutureStudents).SamAccountName `
        }
    }
}

function Update-STFutureStudents {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true,ValueFromPipeline)]
        [object[]]
        $casesUsers,
        [Parameter(Mandatory=$true)]
        [string]
        $FutureStudentFile
    )
    process {
        $_ | Export-Csv -NoTypeInformation -Append -Path $FutureStudentFile
    }
}

function Get-STFutureStudents {
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $FutureStudentFile
    )

    try {
        Import-Csv $FutureStudentFile
    }
    catch {
        Write-Warning "No future students file found"
        New-Item $FutureStudentFile
    }
}

function Select-STAccountsToDisable {
    param(
        # source users
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [object[]]
        $sourceUsers,
        # current users
        [Parameter(Mandatory=$false)]
        [object[]]
        $ADUsers,
        # Users who should never occur in output
        [Parameter(Mandatory=$false)]
        [string[]]
        $ignoreList,
        # Users who should occur in output if they have active accounts
        [Parameter(Mandatory=$false)]
        [string[]]
        $forceInactiveList
    )
    begin {
        if (-not $ADUsers) {
            Write-Warning 'If you call this function multiple times, you should do something like:'
            Write-Warning '$allusers = get-aduser -filter * -properties *'
            Write-Warning 'Select-STAccountsToDisable -sourceUsers $sourceUsers -ADUsers $allusers'
            $ADUsers = Get-ADUser -Filter * -Properties *
        }
        $activeADUsers = $ADUsers |Where-Object {$_.Enabled -eq $true}
        $inactiveSourceUsernames = $($sourceUsers |Where-Object {$_.Enabled -eq $false}).SamAccountName
        $futureUsernames = Get-STFutureUsernames
        $manualActiveUsernames = Get-STManualActiveUsernames
    }

    process {
        $activeADUsers |Where-Object {
            ($_.SamAccountName -in $inactiveSourceUsernames `
            -and $_.SamAccountName -notin $manualActiveUsernames `
            -and $_.SamAccountName -notin $futureUsernames `
            -and $_.SamAccountName -notin $ignoreList) `
            -or `
            $_.SamAccountName -in $forceInactiveList
        }
    }
}


function Select-STAccountsToCreate {
    [CmdletBinding()]
    Param (
        # source users
        [Parameter(Mandatory=$true,ValueFromPipeline)]
        [object[]]
        $sourceUsers,
        # current users
        [Parameter(Mandatory=$false)]
        [object[]]
        $ADUsers,
        # Users who should never occur in output
        [Parameter(Mandatory=$false)]
        [string[]]
        $IgnoreList,
        # Users who should occur in output if they don't have active accounts
        [Parameter(Mandatory=$false)]
        [string[]]
        $forceActiveList
    )

    begin {
        if (-not $ADUsers) {
            Write-Warning 'If you call this function multiple times, you should do something like:'
            Write-Warning '$allusers = get-aduser -filter * -properties *'
            Write-Warning 'Select-STAccountsToCreate -sourceUsers $sourceUsers -ADUsers $allusers'
            $ADUsers = Get-ADUser -Filter *
        }
        $activeADUsernames = $ADUsers.SamAccountName
        $futureUsernames = Get-STFutureUsernames
        $manualActiveUsernames = Get-STManualActiveUsernames
    }

    process {
        $_ |Where-Object {
            ($_.Enabled -eq $true -or $_.SamAccountName -in $ManualActiveUserNames) `
            -and $_.SamAccountName -notin $activeADUsernames `
            -and $_.SamAccountName -notin $IgnoreList `
            -and $_.SamAccountName -notin $futureUsernames `
        }
    }
}

Function New-STWelcomeLetter {
    <#
.SYNOPSIS
  Generates an ICT Welcome Letter for McKinnon SC based on a template
 
.PARAMETER User
  [object[]] List of user objects to create letters for
 
.OUTPUTS
  Formatted HTML welcome letter string
 
.NOTES
  Version: 9001
  Author: Eric van de Paverd... actually... he stole it from Sam Neal
  Creation Date: 30/10/2018
   
.EXAMPLE
  New-ADUser <parameters> | Generate-WelcomeLetter | Out-File -path "Welcome.html"
#>
    
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true,ValueFromPipeline)]
        [object[]]$User,
        [Parameter(Mandatory=$true)]
        [string]
        $TemplatePath
    )

    begin {
        $HTML = Get-Content -Path $TemplatePath -Raw
    }
    process {
        $_ |ForEach-Object {
            $HTML -f $_.GivenName, $_.Surname, $_.SamAccountName, $_.PlainPassword, "$($_.SamAccountName)@brunswick.vic.edu.au"
        }
    }
}