elm-toolbox.psm1

function Clear-Folder {
        <#
    .SYNOPSIS
        This function uses Robocopy to mirror an empty folder over the top of the target directory, it is a VERY quick way of deleting a folder with a lot of subfolders and files.
        It also bypasses a lot of permission issues.
      
      
    .NOTES
        Name: Clear-Folder
        Author: Elliott Marter
      
      
    .EXAMPLE
        Clear-Folder -TargetDirectory C:\path\to\folder
      
      
    .LINK
        https://www.powershellgallery.com/profiles/elliottmarter
    #>


    [cmdletbinding(SupportsShouldProcess=$True)]
    
    param (
        [Parameter(Position=0,mandatory=$true)]
        [string] $TargetDirectory
        )


    # Create empty temp folder
    $EmptyDir = "$env:TEMP\empty_temp"
    New-Item -Path $EmptyDir -ItemType Directory -Force

    Write-Warning "You are about to erase everything in $TargetDirectory are you sure you want to continue" -WarningAction Inquire
    Write-Warning "Are you ABSOLUTELY certain?" -WarningAction Inquire

    takeown /a /r /d Y /f $TargetDirectory | Out-Null

    # use robocopy to mirror the empty directory over the top of the target directory
    robocopy $EmptyDir $TargetDirectory /MIR | Out-Null

    # remove empty temp folder and target folder
    Remove-Item $EmptyDir,$TargetDirectory -Force -Recurse | Out-Null

    Write-Output "$TargetDirectory has been removed"

}
Function Clear-RecycleBins {
    <#
    .SYNOPSIS
        This script will empty all $RECYCLE.BIN files on the selected volume
      
      
    .NOTES
        Name: Empty-RecycleBins
        Author: Elliott Marter
      
      
    .EXAMPLE
        Empty-RecycleBins -Volume D:
      
      
    .LINK
        https://www.powershellgallery.com/profiles/elliottmarter
    #>

     
        [CmdletBinding(SupportsShouldProcess)]
        param(
            [Parameter(
                Mandatory = $true,
                ValueFromPipeline = $true,
                ValueFromPipelineByPropertyName = $true,
                Position = 0
                )]
            [string[]]  $Volume
        )
     
        BEGIN {

            $Bins = (Get-ChildItem -Path $Volume`: -Recurse -Force -Filter '$RECYCLE.BIN').FullName

        }
     
        PROCESS {

            foreach ($b in $Bins ) {

                Get-ChildItem -Path $b -Force -Recurse | Remove-Item -Force -Recurse -Verbose
            
                }
        }
     
        END {}
    }
Function Disable-User {
    <#
    .SYNOPSIS
        This script will carry out some basic actions against an AD User to disable their access and process them as a leaver
        Actions include:
 
        DEFAULT
        - Disable AD Account
        - Change password
        - Set Mail Nickname (so that hide from GAL works)
        - Hide from GAL
        - Clear Title, Manager, Department and Company Fields
        - Set description to "Disabled: (date & time)"
        - Remove from all groups
        - Move to leaver OU
        - Set Mailbox to shared
        - Forward emails
        - Delegate emails
 
        OPTIONAL
        - Forward emails
        - Delegate mailbox
 
    .NOTES
        Name: Disable-User
        Author: Elliott Marter
 
        TO ADD
 
        Remove Licences
      
      
    .EXAMPLE
        Disable-User -Username john.smith -Forward mr.boss@contoso.com -Delegate mr.boss@contoso.com
      
      
    .LINK
        https://www.powershellgallery.com/profiles/elliottmarter
    #>

     
        [CmdletBinding()]
        param(
            [Parameter(Position=0,mandatory=$true)]
            [string]  $Username,
            [string]  $Forward,
            [string]  $Delegate
        )

        # Quick try catch to validate the username and confirm run
    try {
        $User = Get-ADUser -Identity $Username -Properties *
        }
    catch {
        throw "ERROR: Could not find $($Username), please try again"
        }

    $logfile = "$env:SystemDrive\elm_tools_logs\leaver_logs\$Username.txt"
    New-Item $logfile -Force -Confirm:$false | Out-Null
    Write-Output "logfile is here $logfile"
    
    # Call function to ask for Leaver OU
    if ($LeaverOU -eq $null) {
    $LeaverOU = (Select-ADOrganizationalUnit -Message "Select Leaver OU").DistinguishedName
    }

    $Confirm = Read-Host "$($User.Name) ($($User.UserPrincipalName)) will be disabled, Do you wish to contiune? Y/N"

    If ($Confirm -eq "Y") {

        # Disable Account
        Disable-ADAccount $User -Confirm:$false
        Write-Output "$($user.SamAccountName) account has been disabled" | Tee-Object $logfile -Append

        # Set New Password
        $Pass = (iwr https://www.dinopass.com/password/strong -UseBasicParsing).Content
        Set-ADAccountPassword -Identity $User -Reset -NewPassword (ConvertTo-SecureString -AsPlainText "$Pass" -Force)
        Write-Output "Password has been reset to $Pass" | Tee-Object $logfile -Append

        # Clear Title, Manager, Department and Company
        $Manager = (Get-Aduser -Identity ((Get-ADUser $User -Properties *).Manager)).Name
        Set-ADUser $User -Title $null -Manager $null -Department $null -Company $null
        Write-Output "Title = $($User.Title)" | Tee-Object $logfile -Append
        Write-Output "Manager = $Manager" | Tee-Object $logfile -Append
        Write-Output "Department = $($User.Department)" | Tee-Object $logfile -Append
        Write-Output "Company = $($User.Company)" | Tee-Object $logfile -Append
        Write-Output "Title, Manager, Department and Company fields have now been CLEARED" | Tee-Object $logfile -Append

        # Set description to date disabled
        $Description = "Disabled: $((get-date).ToString())"
        Set-ADUser $User -Description $Description
        Write-Output "Descripton set to $Description" | Tee-Object $logfile -Append

        # Remove From all groups
        $Groups = Get-AdPrincipalGroupMembership -Identity $User.SamAccountName | Where-Object -Property Name -Ne -Value 'Domain Users'
        Write-Output "$($user.SamAccountName) is being removed from the following groups" | Tee-Object $logfile -Append
        Write-Output $($Groups.Name) | Tee-Object $logfile -Append
        $Groups | Remove-AdGroupMember -Members $User -Confirm:$false

    try {
        # Set MailNickName & msExchHideFromAddressLists
        Set-ADObject $User -Replace @{MailNickName=$user.SamAccountName}
        Write-Output "Mailnickname set to $($user.SamAccountName)" | Tee-Object $logfile -Append
            
        Set-ADObject $User -Replace @{msExchHideFromAddressLists=$true}
        Write-Output "$($user.SamAccountName) hidden from GAL" | Tee-Object $logfile -Append
        }
    catch {
        Write-Output "Unable to set MailNickName & msExchHideFromAddressLists attribute (AD Schema may not have Exchange attributes??)"
        }

        # Move to Leaver OU
        Move-ADObject $User -TargetPath $LeaverOU
        Write-Output "$($user.SamAccountName) has been moved to $LeaverOU" | Tee-Object $logfile -Append

        # Set Mailbox to shared
        If ($Forward -or $Delegate) {
        Connect-ExchangeOnline
        Set-Mailbox -Identity $User.UserPrincipalName -Type Shared
        }

        # Set Mail forwarding
        If ($Forward) {
            Set-Mailbox -Identity $User.UserPrincipalName -DeliverToMailboxAndForward $true -ForwardingSMTPAddress "$Forward"
            Write-Output "Mail has been forwarded to $Forward" | Tee-Object $logfile -Append
        }

        # Set Mail delegation
        If ($Delegate) {
            Add-MailboxPermission -Identity $User.UserPrincipalName -User $Delegate -AccessRights FullAccess -InheritanceType All
            Write-Output "Mail has been delegated to $Delegate" | Tee-Object $logfile -Append
        }

    }

}
Function Export-Users {

    <#
    .SYNOPSIS
        Exports all enabled AD users, or members of a specific group, to a CSV file.
 
    .NOTES
        Name: Export-Users
        Author: Elliott Marter
 
    .EXAMPLE
        Export-Users -All
        Export-Users -Group "Finance"
        Export-Users -Group "Finance" -OutputPath "C:\Exports"
    #>


    [CmdletBinding(DefaultParameterSetName = 'all')]
    Param(
        [Parameter(ParameterSetName = 'all', Position = 0)]
        [Switch]$All,

        [Parameter(ParameterSetName = 'group', Position = 0, Mandatory)]
        [String]$Group,

        [Parameter()]
        [String]$OutputPath = "$Home\Desktop"
    )

    $Date = Get-Date -UFormat %d-%m-%y

    try {
        if ($All) {
            $Users = Get-ADUser -Filter { Enabled -eq $true } -Properties Description

            $Users |
                Sort-Object Description, Name |
                Select-Object Name, @{Label = "Username"; Expression = { $_.SamAccountName } }, Description |
                Export-Csv -NoTypeInformation -Path (Join-Path $OutputPath "Full User Export $Date.csv")
        }

        if ($Group) {
            if (-not (Get-ADGroup -Identity $Group -ErrorAction SilentlyContinue)) {
                Write-Error "Group '$Group' not found."
                return
            }

            $Users = Get-ADGroupMember -Identity $Group -Recursive |
                Where-Object { $_.objectClass -eq 'user' } |
                ForEach-Object { Get-ADUser -Identity $_.SamAccountName -Properties Description }

            $Users |
                Where-Object { $_.Enabled -eq $true } |
                Sort-Object Description, Name |
                Select-Object Name, @{Label = "Username"; Expression = { $_.SamAccountName } }, @{Label = "Role"; Expression = { $_.Description } } |
                Export-Csv -NoTypeInformation -Path (Join-Path $OutputPath "$Group Users Export $Date.csv")
        }
    }
    catch {
        Write-Error "Export-Users failed: $_"
    }
}
Function Find-GPO {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$SearchString
    )

    <#
    .SYNOPSIS
    Searches all GPOs in the domain for a specific string in their XML report.
 
    .DESCRIPTION
    This function retrieves all Group Policy Objects (GPOs) in the current domain and searches their XML reports for a specified string. It outputs the names of GPOs that contain the search string.
 
    .PARAMETER SearchString
    The string to search for within the GPO XML reports.
 
    .EXAMPLE
    Find-GPO -SearchString "Removable"
    This command searches all GPOs for the string "Removable" and outputs the names of matching GPOs.
    #>


    $matchList = @()
    $domainName = $env:USERDNSDOMAIN

    if (-not $domainName) {
        try {
            $domainName = (Get-ADDomain).DNSRoot
        }
        catch {
            throw "Unable to determine the current domain. Specify a domain or ensure AD tools are available."
        }
    }

    Import-Module GroupPolicy -ErrorAction SilentlyContinue
    $gpos = Get-GPO -All -Domain $domainName

    foreach ($gpo in $gpos) {
        $gpoReport = Get-GPOReport -Guid $gpo.Id -ReportType Xml
        if ($gpoReport -match [regex]::Escape($SearchString)) {
            $matchList += [string]$gpo.DisplayName
            Write-Host "Match: $($gpo.DisplayName)" -ForegroundColor Green
        }
        else {
            Write-Host "No match: $($gpo.DisplayName)"
        }
    }

    Write-Host "String `"$SearchString`" Found in:" -ForegroundColor Green

    if ($matchList.Count -gt 0) {
        $matchList | ForEach-Object { Write-Host $_ -ForegroundColor Green }
    }
    else {
        Write-Host "No matches found." -ForegroundColor Yellow
    }

    return $matchList
}
Function Grant-UserFolderPermissions {

    <#
    .SYNOPSIS
        This function will grant a user full access to their home and profile folders (from their AD Attribute)
      
      
    .NOTES
        Name: Grant-UserFolderPermissions
        Author: Elliott Marter
      
      
    .EXAMPLE
        Grant-UserFolderPermissions -Username john.wick
      
      
    .LINK
        https://www.powershellgallery.com/profiles/elliottmarter
    #>


    [cmdletbinding(SupportsShouldProcess=$True)]
    
    param(
        
        [Parameter(
            Mandatory = $true
            )]
        [string]$UserName

        )

    $Domain = (Get-ADDomain).Name

    if ($Username) {

        try {

            $User = Get-ADUser -Filter {SamAccountName -eq $UserName} -Properties *

            $HomeFolder = $User.HomeDirectory
            $ProfileFolder = $User.ProfilePath

            $NTFS_Params = @{
                Account = "$Domain\$UserName"
                AccessRights = "FullControl"
                AppliesTo = "ThisFolderSubfoldersAndFiles"
                }
            
            
            Write-Output "Fixing permissions for $HomeFolder"
            Add-NTFSAccess -Path $HomeFolder @NTFS_Params

            Write-Output "Fixing permissions for $ProfileFolder"
            Add-NTFSAccess -Path $ProfileFolder @NTFS_Params
        
        }
        Catch {
        
            Write-Error "$_.Exception.Message"
        
        }
    }


}
function Format-DisplayNames {

        <#
    .SYNOPSIS
        This function will format display names for all users in the selected OU
        It will ensure display names are "Firstname Lastname" with correct capitalization
        It bases this off of the first and last name attributes of the users
      
      
    .NOTES
        Name: Format-DisplayNames
        Author: Elliott Marter
      
      
    .EXAMPLE
        Format-DisplayNames
      
      
    .LINK
        https://www.powershellgallery.com/profiles/elliottmarter
    #>


    [cmdletbinding()]
    param ()

    # get users in a certain OU
    $OU = Select-ADOrganizationalUnit -HideNewOUFeature | Select-Object DistinguishedName -ExpandProperty DistinguishedName
    $Users = Get-ADUser -Filter * -SearchBase $OU

    # loop through all users found
    foreach($U in $Users){

        # assign variables
        $firstname = $U.givenname
        $surname = $U.surname
        $olddisplayname =$U.name
        
        # reassign variables with correct capitalization
        $firstname = $firstname.substring(0,1).ToUpper()

        $surname = $surname.substring(0,1).ToUpper()+$surname.substring(1).ToLower()

        # create the correct displayname
        $newdisplayname = $firstname + " $surname"

        # perform the rename action
        Set-ADUser -Identity $U -DisplayName $newdisplayname
        Rename-ADObject -Identity $U -NewName $newdisplayname

        Write-Host "Renamed $olddisplayname to $newdisplayname" -ForegroundColor Green

    }
}
function Get-InactiveComputers {

    <#
.SYNOPSIS
    This function gets computers in the domain that have been inactivate (not logged on) for $DaysInactive
    Optionally specify the $Disable to disable the account and move them to the _DISABLED OU
 
.NOTES
    Name: Get-InactivateComputers
    Author: Elliott Marter
  
.EXAMPLE
    Get-InactivateComputers -DaysInactive 90 -DisableAccount
  
.LINK
    https://www.powershellgallery.com/profiles/elliottmarter
#>


[cmdletbinding(SupportsShouldProcess=$True)]

Param(
    [Parameter(Mandatory)]
    [int] $DaysInactive,
    [switch]
    $DisableAccount,
    [switch]
    $IncludeServers
    )

$Date = (Get-Date -UFormat %Y-%m-%d)

$domain = (Get-ADDomain).DistinguishedName
$oucheckname = "_DISABLED"
$oucheck = [adsi]::Exists("LDAP://OU=$oucheckname,$domain")

if ($oucheck -eq $false) {
    New-ADOrganizationalUnit -Name _DISABLED
}

$DisabledOU = (Get-ADOrganizationalUnit -Filter 'Name -eq "_DISABLED"').DistinguishedName


if ($IncludeServers){

    $Computers = Get-ADComputer -Filter * -Properties * |
    Where Enabled -EQ $true |
    Where LastLogonDate -NE $null |
    Where LastLogonDate -LT (Get-Date).AddDays(-$DaysInactive)

    }
else {

    $Computers = Get-ADComputer -Filter * -Properties * |
    Where Enabled -EQ $true |
    Where LastLogonDate -NE $null |
    Where LastLogonDate -LT (Get-Date).AddDays(-$DaysInactive) |
    Where OperatingSystem -NotLike "*server*"
    
    }

$Computers |
Sort-Object LastLogonDate |
Select-Object Name,LastLogonDate,OperatingSystem


if ($DisableAccount) {

    foreach ($U in $computers) {

        $Description = (get-adcomputer -Identity $U.SamAccountName -Properties description).description
        $Note = " (Disabled: $(get-date -UFormat %d/%m/%y))"
        $NewDescription = $Description  + $Note
        Set-ADcomputer -Identity $U.DistinguishedName -Description $NewDescription -Enabled $false
        Move-ADObject -Identity $U.DistinguishedName -TargetPath $DisabledOU
        Write-Verbose "Successfully disabled $($Item.Name)"

    }

}

}
function Get-InactiveUsers {

    <#
.SYNOPSIS
    This function gets users in the domain that have been inactivate (not logged on) for $DaysInactive
    Optionally specify the $Disable to disable the account and move them to the _DISABLED OU
 
.NOTES
    Name: Get-InactivateUsers
    Author: Elliott Marter
  
.EXAMPLE
    Get-InactivateUsers -DaysInactive 90 -DisableAccount
  
.LINK
    https://www.powershellgallery.com/profiles/elliottmarter
#>


[cmdletbinding(SupportsShouldProcess=$True)]

Param(
    [Parameter(Mandatory)]
    [int] $DaysInactive,
    [switch]
    $DisableAccount
    )

$Date = (Get-Date -UFormat %Y-%m-%d)

$domain = (Get-ADDomain).DistinguishedName
$oucheckname = "_DISABLED"
$oucheck = [adsi]::Exists("LDAP://OU=$oucheckname,$domain")

if ($oucheck -eq $false) {
    New-ADOrganizationalUnit -Name _DISABLED
}

$DisabledOU = (Get-ADOrganizationalUnit -Filter 'Name -eq "_DISABLED"').DistinguishedName
    
$Users = Search-ADAccount -UsersOnly -AccountInactive -TimeSpan "$DaysInactive.00:00:00" |

Where-Object { ($_.Enabled -eq $true) -and ($_.lastlogondate -ne $null) -and ($_.name -notlike "*admin*")}

$Users | Sort-Object LastLogonDate | Select-Object Name,LastLogonDate

if ($DisableAccount) {

    foreach ($U in $Users) {

        $Description = (get-aduser -Identity $U.SamAccountName -Properties description).description
        $Note = " (Disabled: $(get-date -UFormat %d/%m/%y))"
        $NewDescription = $Description  + $Note
        Set-ADUser -Identity $U.DistinguishedName -Description $NewDescription -Enabled $false
        Move-ADObject -Identity $U.DistinguishedName -TargetPath $DisabledOU
        Write-Verbose "Successfully disabled $($Item.Name)"

    }

}

}
Function Import-Users {

    <#
 
    .Synopsis
    Use this function to import users from a CSV file in to Active Directory
 
    .Description
    Users are imported in to an imported users OU, from there you can move them as you wish
 
    .Parameter CSV
    The path to a CSV file containing user data, CSV MUST have 3 columns, firstname, lastname & description
 
    .Parameter UsernameFormat
    Select the format you want for the usernames generated, currently johnd, john.d, jdoe, j.doe, john.doe are supported.
 
    .Parameter Usertype
    Select the type of user account, the function uses this to find the home directory (eg Home$\Staff) and also adds them to the corresponding security group
 
    .Parameter Homeshare
    Path to the Home$ share (eg \\DC01\Home$)
 
    .Parameter Profileshare
    Path to the Profile$ share (eg \\DC01\Profile$)
 
    .Parameter Password
    What password you want for the account, MUST comply with domain policy OR fine grained password policy.
 
    .Parameter RandomPassword
    Tells the function to use www.dinopass.com to generate random passwords for each account
 
    .Parameter Logpath
    What path to generate the log files, by default is current users desktop
 
    .EXAMPLE
    Import-Users -csv C:\users.csv -UsernameFormat -john.d -UserType Office
 
 
    #>

    
    [cmdletbinding(SupportsShouldProcess=$True)]
    #[CmdletBinding(DefaultParameterSetName='password')]
    Param(
        
        [Parameter(
            Mandatory=$true
            )]
        $csv,
        
        [Parameter(
            Mandatory=$true
            )]
        [ValidateSet('johnd','john.d','jdoe','j.doe','john.doe')]
        [string]$UsernameFormat,
        
        [Parameter(
            Mandatory=$true
            )]
        [ValidateSet('Staff','Office','Students')]
        [string]$UserType,

        [Parameter(
            ParameterSetName='password'
            )]
        [string]$Password,
        
        [Parameter(
            ParameterSetName='random'
            )]
        [switch]$RandomPassword,
        
        [string]$HomeShare = "\\$Env:COMPUTERNAME\Home$",
        
        [string]$ProfileShare = "\\$Env:COMPUTERNAME\Profile$",
        
        [string]$LogPath = "$env:USERPROFILE\Desktop\User Import Logs"
    )

    $ErrorActionPreference = "Stop"

    # Setup logging
    $Log = "$LogPath\$(Get-Date -UFormat %Y-%m-%d) Import Log.csv"
    $FailLog = "$LogPath\$(Get-Date -UFormat %Y-%m-%d) FAILED Import Log.csv"

    if (!(Test-Path $Log)) {

        New-Item $Log, $FailLog -Force | Out-Null
        Set-Content -Path $Log -Value '"Full Name","UserName","Description","Password"'
        Set-Content -Path $FailLog -Value '"Full Name","UserName"'
        
        }
    
    # Store the data from the CSV in the $Users variable
    $Users = Import-csv $csv

    if ((Get-Content $csv)[0] -ne "firstname,lastname,description") {

        throw "Check your CSV Headers! Should be 'firstname,lastname,description'"
    
    }
    
    # Show current settings and require confirmation to continue
    Write-Host "Home Folder = $HomeShare\$UserType" -ForegroundColor Green
    Write-Host "Profile Folder = $ProfileShare\$UserType" -ForegroundColor Green
    Write-Host "Username Format = $UsernameFormat" -ForegroundColor Green
    Write-Warning "Please confirm these settings are correct" -WarningAction Inquire
    
    Write-Host "Log file: $LogPath" -ForegroundColor Cyan



    # Set some Variables
    $Domain = (Get-ADDomain).name
    $FullDomain = (Get-ADDomain).dnsroot
    $DomainRoot = (Get-ADDomain).DistinguishedName
    $HomePath = "$HomeShare\$UserType"
    $ProfilePath = "$ProfileShare\$UserType"

    # Tests for an "Imported Users" OU at root of domain and if it does not exist then it creates it
    $ImportOU = "OU=Imported Users,$DomainRoot"

    try {
        Get-ADOrganizationalUnit -Identity $ImportOU | Out-Null
        } catch {
        New-ADOrganizationalUnit -Name "Imported Users" -Path $DomainRoot
        }


    # Check Home & Profile paths exist
    if (!(Test-Path $HomePath)) {
        Throw "Could not find $HomePath!"
    }
        
    if (!(Test-Path $ProfilePath)) {
        Throw "Could not find $ProfilePath!"
    }

    # Loop through each row containing user details in the CSV file

    foreach ($User in $Users) {

        # Read user data from each field in each row and assign the data to a variable as below
            
        $Firstname      = $User.firstname 
        $Lastname      = $User.lastname
        $FullName    = "$Firstname $Lastname"
        $Description = $User.description

        # Select username format

        if ($UsernameFormat -eq "johnd") {
            $Username = $Firstname + $Lastname.substring(0,1)
        }

        if ($UsernameFormat -eq "john.d") {
            $Username = $Firstname + "." + $Lastname.substring(0,1)
        }

        if ($UsernameFormat -eq "jdoe") {
            $Username = $Firstname.substring(0,1) + $Lastname
        }

        if ($UsernameFormat -eq "j.doe") {
            $Username = $Firstname.substring(0,1) + "." + $Lastname
        }

        if ($UsernameFormat -eq "john.doe") {
            $Username = $Firstname + "." + $Lastname
        }
        
        # change to lower case and remove - and ' and spaces
        $Username = $Username.ToLower() -replace "[^a-zA-Z.]"

        # Generate a random password and make the first letter a capital
        if ($RandomPassword) {
            
            $Password = (Get-Culture).TextInfo.ToTitleCase((Invoke-WebRequest "http://www.dinopass.com/password/simple" -Verbose:$False | Select-Object Content -ExpandProperty Content))

            }

        $PasswordSecure = $Password | ConvertTo-SecureString -AsPlainText -Force


        # Create splat of user params
            
        $UserParams = @{
            SamAccountName = $Username
            UserPrincipalName  = "$Username@$FullDomain"
            Name = $FullName
            GivenName = $Firstname
            Surname = $Lastname
            Path = $ImportOU
            ProfilePath = "$ProfilePath\$Username"
            HomeDrive = "H:"
            HomeDirectory = "$HomePath\$Username"
            Description = $Description
            ChangePasswordAtLogon = $true
            }

        # Create an object to make reporting later easier
        
        $UserObject = [PSCustomObject]@{

            Name            = $Fullname
            UserName        = $Username
            Description     = $Description
            Password        = $Password

            }

        try {
            
            # Create the account
            New-ADUser @UserParams
            
            # Create user home folder
            New-Item -Name $Username -Path $HomePath -ItemType Directory | Out-Null
            
            # Set access rights on home folder
            Add-NTFSAccess -Path "$HomePath\$Username" -Account $Username -AccessRights FullControl
            
            # Add to groups
            Add-ADGroupMember -Identity $UserType -Members $Username
            
            # Set account password
            Set-ADAccountPassword -Identity $Username -Reset -NewPassword $PasswordSecure
            Set-ADUser -Identity $Username -Enabled $true -CannotChangePassword $false -ChangePasswordAtLogon $true

            # If student then change password requirements
            if ($UserType -eq "Students") {

                Set-ADUser -Identity $Username -Enabled $true -CannotChangePassword $true -ChangePasswordAtLogon $false -PasswordNeverExpires $true

                }

            # Write success message
            Write-Host "SUCCESS: Created $Username" -ForegroundColor Green

            # Log to file
            $UserObject | ConvertTo-Csv -NoTypeInformation | Select-Object -Skip 1 | Out-File -FilePath $Log -Append -encoding ASCII

            }
    
        catch {

            # Write failure message
            Write-Host "FAILURE: Unable to create $Username" -ForegroundColor Red

            # Log to file
            $UserObject | Select-Object Username | ConvertTo-Csv -NoTypeInformation | Select-Object -Skip 1 | Out-File -FilePath $FailLog -Append -encoding ASCII
            $_.Exception.Message | Out-File -FilePath $FailLog -Append -encoding ASCII
            
            continue
            
            }

        }

}
function Move-DisabledADObjectsToOU {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [string]$OUName = '_DISABLED',

        [ValidateSet('Users','Computers','Both')]
        [string]$ObjectType = 'Both'
    )

    $OU = Get-ADOrganizationalUnit -Filter "Name -eq '$OUName'"
    if (-not $OU) {
        Write-Error "OU '$OUName' not found."
        return
    }

    if ($ObjectType -in 'Users','Both') {
        Get-ADUser -Filter 'Enabled -eq $false' |
            Move-ADObject -TargetPath $OU.DistinguishedName -WhatIf:$WhatIfPreference
    }

    if ($ObjectType -in 'Computers','Both') {
        Get-ADComputer -Filter 'Enabled -eq $false' |
            Move-ADObject -TargetPath $OU.DistinguishedName -WhatIf:$WhatIfPreference
    }
}
function New-Password {

    [cmdletbinding(SupportsShouldProcess=$True)]

    param (
        [Parameter()]
        [int]$Quantity = 1
    )

    $PasswordFile = "$env:USERPROFILE\Passwords.txt"

    if (Test-Path $PasswordFile) {
        Remove-Item $PasswordFile
    }

    $Colours = @('Red','Blue','Green','Yellow','Purple','Orange','Pink','Black','White','Silver')
    $Animals = @('Tiger','Panda','Eagle','Otter','Shark','Wolf','Falcon','Rabbit','Turtle','Lion')
    $Symbols = @('!','@','#','$','%')

    $counter = 0

    $Passwords = 1..$Quantity | ForEach-Object {

        $counter++
        Write-Progress -Activity 'Generating Passwords' -CurrentOperation "Password $_" -PercentComplete (($counter / $Quantity) * 100)

        $Colour = Get-Random -InputObject $Colours
        $Animal = Get-Random -InputObject $Animals
        $Numbers = -join ((0..9 | Get-Random -Count 2))
        $Symbol = Get-Random -InputObject $Symbols

        "$Colour$Animal$Numbers$Symbol"
    }

    $Passwords

}
Function Ping-Test {


    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)] $Target,
        [Parameter(Mandatory = $true)] $LogFileName
    )

    
    # Base directory for log files
    $LogFolder = "$env:PUBLIC\Documents\ping_logs"
    New-Item $LogFolder -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null

    $LogFile = Join-Path -Path $LogFolder -ChildPath "PingLog_$LogFileName.txt"

    Write-Host "Logfile is here: $LogFile"

    $bar = "*" * 30

    Write-Host $bar
    Write-Host "DO NOT CLOSE THIS WINDOW - PING TEST RUNNING"
    Write-Host $bar

    # Infinite loop to keep pinging
    while ($true) {
        # Get the current date and time
        $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

        # Perform the ping and capture the result
        $PingResult = Test-Connection -ComputerName $Target -Count 1 -ErrorAction SilentlyContinue

        if ($PingResult) {
            # Log success message
            $LogMessage = "$Timestamp - Success: Reply from $($PingResult.Address) in $($PingResult.ResponseTime)ms"
        } else {
            # Log failure message
            $LogMessage = "$Timestamp - Failure: No response from $Target"
        }

        # Append the log message to the file
        Add-Content -Path $LogFile -Value $LogMessage

        # Wait for a specified interval (in seconds) before the next ping
        Start-Sleep -Seconds 10
    }

}


function Remove-DisabledUsersFromGroups {
    [CmdletBinding()]
    param (
        [string]$OU = "OU=_DISABLED"
    )

    $Users = Get-ADUser -SearchBase $OU -Filter { Enabled -eq $false }

    foreach ($User in $Users) {
        Write-Host "Processing user: $($User.SamAccountName)" -ForegroundColor Cyan

        $Groups = Get-ADUser $User -Property MemberOf | Select-Object -ExpandProperty MemberOf

        foreach ($GroupDN in $Groups) {
            try {
                $Group = Get-ADGroup $GroupDN
                Remove-ADGroupMember -Identity $Group -Members $User -Confirm:$false -ErrorAction Stop
                Write-Host "Removed $($User.SamAccountName) from $($Group.Name)" -ForegroundColor Green
            }
            catch {
                Write-Warning "Failed to remove $($User.SamAccountName) from $GroupDN. Error: $_"
            }
        }
    }
}
Function Remove-OldDownloads {
    
    <#
    .SYNOPSIS
        This script will remove files older than x days within Redirected Downloads Folders
        Set PATH to the top level Redirected folder (e.g. D:\RedirecedFolders)
     
    .EXAMPLE
        Remove-OldDownloads -Path D:\RedirectedFolders -Days 14
 
    #>

     
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path,
        [Parameter(Mandatory = $true)]
        [int]$Days
    )

    
    Get-childitem $Path -Depth 1 -Directory -Filter "Downloads" | 
    Get-ChildItem -Recurse -Force -Exclude *.ini |
    Where-Object LastWriteTime -LT (Get-Date).AddDays(-$Days) |
    Remove-Item -Force -Recurse -Verbose

    }
Function Remove-UnlinkedGPOs {

    Import-Module grouppolicy
    
    Write-Warning "This script will remove all unlinked GPOs, Do you want to Continue (Y/N)?" -WarningAction Inquire


    Function IsNotLinked($xmldata){ 
        If ($xmldata.GPO.LinksTo -eq $null) { 
            Return $true 
        } 
        Return $false
    } 
 
    $unlinkedGPOs = @()
 
    Get-GPO -All | ForEach { $gpo = ($_.ID) ; $_ | Get-GPOReport -ReportType xml | ForEach { If(IsNotLinked([xml]$_)){$unlinkedGPOs += $gpo} }} 
 
    If ($unlinkedGPOs.Count -eq 0) { 
        "No Unlinked GPO's Found. The script has not removed any GPOs" 
    } Else { 
        foreach ($GP in $unlinkedGPOs) {
            $GpToDelete = Get-GPO -guid $GP
            Write-Host "Removing $($GpToDelete.DisplayName)" -ForegroundColor Green
            Remove-GPO -Guid $GP -Verbose
            }
    }
    
    Write-Host "Finished running the script, $($unlinkedGPOs.Count) unused group policies were removed." 

}s
function Schedule-Reboot {

    [CmdletBinding()]
    param(
        [Parameter(mandatory=$true)]
        [decimal]$Hours,
        [switch]$Shutdown
    )

    if ($Hours -eq $null){
        [decimal]$Hours = Read-Host "Perferm action in how many hours?"
    }

    [int]$seconds = $hours * 3600

    if ($Shutdown) {
        shutdown.exe /s /f /t $seconds
    }
    else {
        shutdown.exe /r /f /t $seconds
    }

}
function Set-UPN {
    
    [cmdletbinding(SupportsShouldProcess=$True)]

    param (
        [string]
        [Parameter(Mandatory=$true)]
        $OldSuffix,
        
        [string]
        [Parameter(Mandatory=$true)]
        $NewSuffix
    )

    Import-Module ActiveDirectory

    $OU = Select-ADOrganizationalUnit -HideNewOUFeature | Select-Object DistinguishedName -ExpandProperty DistinguishedName

    $Users = Get-ADUser -Filter * -SearchBase $OU

    foreach ($U in $Users) {

        $NewUPN = $U.UserPrincipalName.Replace($OldSuffix,$NewSuffix)
        Set-ADUser -Identity $U.SamAccountName -UserPrincipalName $NewUPN
       
       }

}
Function Update-BootImage {
    <#
    .SYNOPSIS
        This function updates the boot image on a deployment server and automatically imports it in to WDS
      
      
    .NOTES
        Name: Update-BootImage
        Author: Elliott Marter
      
      
    .EXAMPLE
        Update-BootImage -DeploymentShare D:\DeploymentShare
      
      
    .LINK
        https://www.powershellgallery.com/profiles/elliottmarter -
    #>

     
        [CmdletBinding()]
        param(
            [Parameter(
                Mandatory = $true,
                Position = 0
                )]
            [string[]]  $DeploymentShare
        )
     
        BEGIN {

            if (!(test-path $DeploymentShare)) {

            throw "Could not find $DeploymentShare please check again..."

            }

            # Import MDT Toolkit
            Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1"

            # Get all exisiting boot images and remove them
            Get-WdsBootImage | Remove-WdsBootImage

        }
     
        PROCESS {

            $DSItem = Get-Item $DeploymentShare
            $DSItem
            
            $DSName = $DSItem.name
            $DSName
            
            $DSPath = $DSItem.fullname
            $DSPath

            New-PSDrive -Name $DSName -PSProvider MDTProvider -Root $DSPath -Verbose
            
            Update-MDTDeploymentShare -Path "$($DSName):" -Verbose
            
            Import-WdsBootImage -NewImageName $DSName -NewDescription $DSName -Path "$DSPath\Boot\LiteTouchPE_x64.wim" -Verbose 
            
            Get-PSDrive -Name $DSName | Remove-PSDrive -Verbose
            
        }
     
        END {}
    }