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 {
    [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 {
    [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 {
    [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) {
    $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 ($sourceUsers, $currentUsers, $ouLookup, $homeDirBase="", $smtp_server="", $support_address="", $to_addresses=@()){
    $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 $_
        }
    } | 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 "<code>New user: $($_.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-Debug "$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 -TargetPath $targetOU
                }
            }
        }
    }
}