ADDSAuditTasks.psm1

#Region '.\Classes\1.class1.ps1' 0
class ADAuditAccount {
    [string]$UserName
    [string]$FirstName
    [string]$LastName
    [string]$Name
    [string]$UPN
    [string]$LastSignIn
    [string]$Enabled
    [string]$LastSeen
    [string]$OrgUnit
    [string]$Title
    [string]$Manager
    [string]$Department
    [bool]$AccessRequired
    [bool]$NeedMailbox
    # Constructor 1
    ADAuditAccount([string]$UserName) {
        $this.UserName = $UserName
        $this.AccessRequired = $false
        $this.NeedMailBox = $false
    }
    ADAuditAccount(
        [string]$UserName,
        [string]$FirstName,
        [string]$LastName,
        [string]$Name,
        [string]$UPN,
        [string]$LastSignIn,
        [string]$Enabled,
        [string]$LastSeen,
        [string]$OrgUnit,
        [string]$Title,
        [string]$Manager,
        [string]$Department,
        [bool]$AccessRequired,
        [bool]$NeedMailbox
    ) {
        $this.UserName = $UserName
        $this.FirstName = $FirstName
        $this.LastName = $LastName
        $this.Name = $Name
        $this.UPN = $UPN
        $this.LastSignIn = ([DateTime]::FromFileTime($LastSignIn))
        $this.Enabled = $Enabled
        $this.LastSeen = $(
            switch (([DateTime]::FromFileTime($LastSeen))) {
                # Over 90 Days
                { ($_ -lt (Get-Date).Adddays( - (90))) } { '3+ months'; break }
                # Over 60 Days
                { ($_ -lt (Get-Date).Adddays( - (60))) } { '2+ months'; break }
                # Over 90 Days
                { ($_ -lt (Get-Date).Adddays( - (30))) } { '1+ month'; break }
                default { 'Recently' }
            } # End Switch
        ) # End LastSeen
        $this.OrgUnit = $OrgUnit -replace '^.*?,(?=[A-Z]{2}=)'
        $this.Title = $Title
        $this.Manager = $(
            switch ($Manager) {
                # Over 90 Days
                { if ($_) { return $true } } { "$((Get-ADUser -Identity $Manager).Name)"; break }
                # Over 60 Days
                default { 'NotFound' }
            }
        ) # End Manager
        $this.AccessRequired = $AccessRequired
        $this.NeedMailbox = $NeedMailbox
        $this.Department = $Department
    }
}
#EndRegion '.\Classes\1.class1.ps1' 71
#Region '.\Classes\2.class2.ps1' 0
class ADComputerAccount {
    [string]$ComputerName
    [string]$DNSHostName
    [bool]$Enabled
    [string]$IPv4Address
    [string]$IPv6Address
    [string]$OperatingSystem
    [string]$LastLogon
    [string]$Created
    [string]$Modified
    [string]$Description
    [string]$OrgUnit
    [string]$KerberosEncryptionType
    [string]$SPNs
    [string]$GroupMemberships #Computername for Group Membership Search
    [string]$LastSeen
    # Constructor 1
    ADComputerAccount(
        [string]$ComputerName,
        [string]$DNSHostName,
        [bool]$Enabled,
        [string]$IPv4Address,
        [string]$IPv6Address,
        [string]$OperatingSystem,
        [long]$LastLogon,
        [datetime]$Created,
        [string]$Modified,
        [string]$Description,
        [string]$OrgUnit,
        [string]$KerberosEncryptionType,
        [string]$SPNs,
        [string]$GroupMemberships,
        [long]$LastSeen
    ) {
        #Begin Contructor 1
        $this.ComputerName = $ComputerName
        $this.DNSHostName = $DNSHostName
        $this.Enabled   = $Enabled
        $this.IPv4Address = $IPv4Address
        $this.IPv6Address = $IPv6Address
        $this.OperatingSystem = $OperatingSystem
        $this.LastLogon = ([DateTime]::FromFileTime($LastLogon))
        $this.Created = $Created
        $this.Modified = $Modified
        $this.Description = $Description
        $this.OrgUnit = $(($OrgUnit -replace '^.*?,(?=[A-Z]{2}=)') -replace ",", ">")
        $this.KerberosEncryptionType = $(($KerberosEncryptionType | Select-Object -ExpandProperty $_) -replace ", ", " | ")
        $this.SPNs = $SPNs
        $this.GroupMemberships = $(Get-ADComputerGroupMemberof -SamAccountName $GroupMemberships)
        $this.LastSeen = $(
            switch (([DateTime]::FromFileTime($LastSeen))) {
                # Over 90 Days
                { ($_ -lt (Get-Date).Adddays( - (90))) } { '3+ months'; break }
                # Over 60 Days
                { ($_ -lt (Get-Date).Adddays( - (60))) } { '2+ months'; break }
                # Over 90 Days
                { ($_ -lt (Get-Date).Adddays( - (30))) } { '1+ month'; break }
                default { 'Recently' }
            } # End Switch
        ) # End LastSeen
    }# End Constuctor 1
}
# $($SPNs -join " | " )
#
#
#EndRegion '.\Classes\2.class2.ps1' 66
#Region '.\Private\Export-AuditCSVtoZip.ps1' 0
function Export-AuditCSVtoZip {
    [CmdletBinding()]
    param (
        [PSCustomObject[]]$Exported,
        [ADAuditAccount[]]$ExportObject,
        [string]$CSVName,
        [string]$ZipName
    )
    process {
        if ($Exported) {
            [PSCustomObject[]]$ExportObject = $Exported
            $membertype = "NoteProperty"
        }
        else {
            $membertype = "Property"
        }
        Write-TSLog "The $($script:MyInvocation.MyCommand.Name -replace '\..*') Export was successful. There are $($ExportObject.Count) objects listed with the following properties: "
            ($ExportObject | Get-Member -MemberType $membertype ).Name | Write-TSLog
        Write-TSLog "Exporting CSV to path: $CSVName"
        try {
            $ExportObject | Export-Csv -Path $CSVName -NoTypeInformation -ErrorVariable ExportErr -ErrorAction Stop
        }
        catch {
            Write-TSLog "The CSV export failed with error: "
            write-tslog "Error" + $ExportErr.Exception.ErrorRecord
        }
        Write-TSLog "Compressing file: $CSVName"
        Write-TSLog "to destination zip file: $ZipName"
        try {
            Compress-Archive -Path $CSVName -DestinationPath $ZipName -ErrorVariable ZipErr -ErrorAction Stop
        }
        catch {
            Write-TSLog "Failed compressing file: "
            Write-TSLog $CSVName
            Write-TSLog "to destination zip file: "
            Write-TSLog $ZipName
            Write-TSLog "with error: "
            Write-TSLog -End
            write-tslog $ZipErr.Exception.ErrorRecord
        }
        Write-TSLog "Removing CSV file: "
        Write-TSLog $CSVName
        Write-TSLog "from the file system."
        try {
            Remove-Item $CSVName -Force -ErrorVariable CSVDeleteErr -ErrorAction Stop
        }
        catch {
            Write-TSLog "Failed to remove CSV file: $CSVName"
            Write-TSLog -LogError -LogErrorVar $CSVDeleteErr.Exception.ErrorRecord
        }
        Write-TSLog "Removed CSV file: "
        Write-TSLog $CSVName
        Write-TSLog "from the file system."

    }
}
#EndRegion '.\Private\Export-AuditCSVtoZip.ps1' 57
#Region '.\Private\Get-ADComputerGroupMemberof.ps1' 0
function Get-ADComputerGroupMemberof {
    [CmdletBinding()]
    param (
        [string]$SamAccountName
    )
    process {
        $GroupStringArray = ((Get-ADComputer -Identity $SamAccountName -Properties memberof).memberof | Get-ADGroup | Select-Object name | Sort-Object name).name
        $GroupString = $GroupStringArray -join " | "
        return $GroupString
    }
}
#EndRegion '.\Private\Get-ADComputerGroupMemberof.ps1' 12
#Region '.\Private\Get-ADExtendedRight.ps1' 0
Function Get-AdExtendedRight([Microsoft.ActiveDirectory.Management.ADObject] $ADObject) {
    $ExportER = @()
    Foreach ($Access in $ADObject.ntsecurityDescriptor.Access) {
        # Ignore well known and normal permissions
        if ($Access.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Deny) { continue }
        if ($Access.IdentityReference -eq "NT AUTHORITY\SYSTEM") { continue }
        if ($Access.IdentityReference -eq "NT AUTHORITY\SELF") { continue }
        if ($Access.IsInherited) { continue }
        # Check extended right
        if ($Access.ActiveDirectoryRights -band [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight) {
            $Right = "";
            # This is the list of dangerous extended attributs
            # see : https://technet.microsoft.com/en-us/library/ff405676.aspx
            switch ($Access.ObjectType) {
                "00299570-246d-11d0-a768-00aa006e0529" { $Right = "User-Force-Change-Password" }
                "45ec5156-db7e-47bb-b53f-dbeb2d03c40" { $Right = "Reanimate-Tombstones" }
                "bf9679c0-0de6-11d0-a285-00aa003049e2" { $Right = "Self-Membership" }
                "ba33815a-4f93-4c76-87f3-57574bff8109" { $Right = "Manage-SID-History" }
                "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2" { $Right = "DS-Replication-Get-Changes-All" }
            } # End switch
            if ($Right -ne "") {
                $Rights = [ordered]@{
                    Actor                   = $($Access.IdentityReference)
                    CanActOnThePermissionof = "$($ADObject.name)" + " " + "($($ADObject.DistinguishedName))"
                    WithExtendedRight       = $Right
                }
                $ExportER += New-Object -TypeName PSObject -Property $Rights
                #"$($Access.IdentityReference) can act on the permission of $($ADObject.name) ($($ADObject.DistinguishedName)) with extended right: $Right"
            } # Endif
        } # Endif
    } # End Foreach
    return $ExportER
} # End Function
#EndRegion '.\Private\Get-ADExtendedRight.ps1' 34
#Region '.\Private\Get-ADGroupMemberof.ps1' 0
function Get-ADGroupMemberof {
    [CmdletBinding()]
    param (
        [string]$SamAccountName
    )
    process {
        $GroupStringArray = ((Get-ADUser -Identity $SamAccountName -Properties memberof).memberof | Get-ADGroup | Select-Object name | Sort-Object name).name
        $GroupString = $GroupStringArray -join " | "
        return $GroupString
    }
}
#EndRegion '.\Private\Get-ADGroupMemberof.ps1' 12
#Region '.\Private\Get-TimeStamp.ps1' 0
function Get-TimeStamp {
    $Stamp = "[{0:yyyy/MM/dd} {0:HH:mm:ss}]" -f (Get-Date)
    return $Stamp
}
#EndRegion '.\Private\Get-TimeStamp.ps1' 5
#Region '.\Private\Initialize-AuditBeginBlock.ps1' 0
function Initialize-AuditBeginBlock {
    <#
    .SYNOPSIS
    This is a sample Private function only visible within the module.
 
    .DESCRIPTION
    This sample function is not exported to the module and only return the data passed as parameter.
 
    .EXAMPLE
    $null = Initialize-AuditBeginBlock -PrivateData 'NOTHING TO SEE HERE'
 
    .PARAMETER PrivateData
    The PrivateData parameter is what will be returned without transformation.
 
    #>

    [cmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(ValueFromPipeline = $true)]
        [string]$AttachmentFolderPathBegin = "C:\temp\ADDSAuditTasks",
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [string]$ScriptFunctionName,
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [bool]$SendEmailMessageBegin,
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [bool]$CleanBegin,
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [bool]$WinSCPBegin
    )
    process {
        # Create Directory Path
        $AttachmentFolderPathCheck = Test-Path -Path $AttachmentFolderPathBegin
        If (!($AttachmentFolderPathCheck)) {
            Try {
                # If not present then create the dir
                New-Item -ItemType Directory $AttachmentFolderPathBegin -Force -ErrorAction Stop
            }
            Catch {
                Write-TSLog -Begin
                Write-TSLog "Directory: $AttachmentFolderPathBegin was not created."
                Write-TSLog -LogErrorEnd
                throw
            }
        }
        # Begin Logging to $script:Logs
        Write-TSLog -Begin
        $script:LogOutputPath = "$AttachmentFolderPathBegin\$((Get-Date).ToString('yyyy-MM-dd_hh.mm.ss'))_$($ScriptFunctionName)_$($env:USERDNSDOMAIN)_ADDSAuditlog.log"
        Write-TSLog "Log output path: $LogOutputPath"
        # If not Clean
        if (!($CleanBegin)) {
            # Import Active Directory Module
            $module = Get-Module -Name ActiveDirectory -ListAvailable
            if (-not $module) {
                Add-WindowsFeature RSAT-AD-PowerShell -IncludeAllSubFeature -Verbose -ErrorAction Stop
            }
            try {
                Import-Module "activedirectory" -Global -ErrorAction Stop
            }
            catch {
                Write-TSLog "The Module Was not installed. Use `"Add-WindowsFeature RSAT-AD-PowerShell`" or install using server manager under `"Role Administration Tools>AD DS and AD LDS Tools>Active Directory module for Windows Powershell`"."
                Write-TSLog -LogErrorEnd
                throw
            }
            if ($SendEmailMessageBegin) {
                # Install / Import required modules.
                $module = Get-Module -Name Send-MailKitMessage -ListAvailable
                if (-not $module) {
                    Install-Module -Name Send-MailKitMessage -AllowPrerelease -Scope AllUsers -Force
                }
                try {
                    Import-Module "Send-MailKitMessage" -Global -ErrorAction Stop
                }
                catch {
                    # End run and log to file.
                    Write-TSLog "The Module Was not installed. Use `"Save-Module -Name Send-MailKitMessage -AllowPrerelease -Path C:\temp`" on another Windows Machine."
                    Write-TSLog -End
                    throw
                }
            }
            elseif ($WinSCPBegin) {
                $module = Get-Module -Name WinSCP -ListAvailable
                if (-not $module) {
                    Install-Module WinSCP -Scope CurrentUser
                }
                try {
                    Import-Module WinSCP -Global -ErrorAction Stop
                }
                catch {
                    Write-TSLog "The Module Was not installed. Export WinSCP using: `"Save-Module WinSCP -Path <Path>`" and import to this machine."
                    Write-TSLog -LogErrorEnd
                    throw
                }
            }
        }
        # If SendMailMessage
        return $LogOutputPath
    }
}

#EndRegion '.\Private\Initialize-AuditBeginBlock.ps1' 100
#Region '.\Private\Initialize-AuditEndBlock.ps1' 0
function Initialize-AuditEndBlock {
    <#
    .SYNOPSIS
    This is a sample Private function only visible within the module.
    .DESCRIPTION
    This sample function is not exported to the module and only return the data passed as parameter.
    .EXAMPLE
    $null = Initialize-AuditEndBlock -PrivateData 'NOTHING TO SEE HERE'
    .PARAMETER PrivateData
    The PrivateData parameter is what will be returned without transformation.
    #>

    [cmdletBinding()]
    param
    (
        [string]$SmtpServerEnd,
        [int]$PortEnd,
        [string]$UserNameEnd,
        #[switch]$ssl,
        [string]$FromEnd,
        [string]$ToEnd,
        #[string]$subject = "$($script:MyInvocation.MyCommand.Name -replace '\..*') report ran for $($env:USERDNSDOMAIN).",
        [string]$AttachmentFolderPathEnd,
        #[string]$body,
        [securestring]$Password,
        [string]$FunctionEnd,
        [string]$FunctionAppEnd,
        [string]$ApiTokenEnd,
        [string[]]$ZipEnd,
        [bool]$CleanEnd,
        [bool]$LocalDiskEnd,
        [bool]$SendEmailMessageEnd,
        [bool]$WinSCPEnd,
        [string]$FTPHostEnd,
        [string]$SshHostKeyFingerprintEnd,
        [string]$RemotePathEnd
    )
    process {
        Write-TSLog "The Value of Clean is $CleanEnd."
        if ($CleanEnd) {
            Write-TSLog "Removing Send-MailKitMessage Module"
            try {
                # Remove Modules
                Remove-Module -Name "Send-MailKitMessage" -Force -Confirm:$false -ErrorAction Stop
            }
            catch {
                Write-TSLog "Error removing Send-MailKitMessage Module"
                Write-TSLog -LogError
            }
            Write-TSLog "Uninstalling Send-MailKitMessage Module"
            try {
                # Uninstall Modules
                Uninstall-Module -Name "Send-MailKitMessage" -AllowPrerelease -Force -Confirm:$false
            }
            catch {
                Write-TSLog -LogError
                if (Get-Module -Name Send-MailKitMessage -ListAvailable) {
                    Write-TSLog "Error uninstalling Send-MailKitMessage Module"
                }
            }
            Write-TSLog "Removing directories and files in: "
            Write-TSLog "$AttachmentFolderPathEnd"
            try {
                Remove-Item -Path $AttachmentFolderPathEnd -Recurse -Force -ErrorAction Stop
            }
            catch {
                Write-TSLog "Directory Cleanup error!"
                Write-TSLog -LogError
                throw
            }
            Write-TSLog -End
            Write-TSLog -LogOutputPath C:\temp\ADDSAuditTaskCleanupLogs.log
        }
        else {
            if ($SendEmailMessageEnd) {
                if ($Password) {
                    <#
                    Send Attachment using O365 email account and password.
                    Must exclude from conditional access legacy authentication policies.
                    #>

                    Write-TSLog "Account: $UserNameEnd,"
                    Write-TSLog "SENDING email to: $ToEnd,"
                    Write-TSLog "From USER: $FromEnd,"
                    Write-TSLog "Using PORT: $PortEnd,"
                    Write-TSLog "Using RELAY $SMTPServerEnd, with SSL"
                    Write-TSLog "With: PASSWORD"
                    Write-TSLog "Logs included in body"
                    Write-TSLog -End
                    Send-AuditEmail -smtpServer $SMTPServerEnd -port $PortEnd -username $UserNameEnd `
                        -body $script:Logs -pass $Password -from $FromEnd -to $ToEnd -attachmentfiles ($ZipEnd).Split(" ") -ssl
                    $Password.Dispose()
                    Remove-Item -Path $AttachmentFolderPath -Recurse -Force -ErrorAction Stop
                } # End if
                else {
                    Write-TSLog "Account: $UserNameEnd,"
                    Write-TSLog "SENDING email to: $ToEnd,"
                    Write-TSLog "From USER: $FromEnd,"
                    Write-TSLog "Using PORT: $PortEnd,"
                    Write-TSLog "Using RELAY: $SMTPServerEnd, with SSL"
                    Write-TSLog "Without: PASSWORD"
                    Write-TSLog "Logs included in body"
                    Write-TSLog -End
                    Send-AuditEmail -smtpServer $SMTPServerEnd -port $PortEnd -username $UsernameEnd `
                        -body $script:Logs -from $FromEnd -to $ToEnd -attachmentfiles ($ZipEnd).Split(" ") -ssl
                    Remove-Item -Path $AttachmentFolderPathEnd -Recurse -Force -ErrorAction Stop
                }
            }
            elseif ($FunctionAppEnd) {
                <#
                Send Attachment using O365 email account and Keyvault retrived password.
                Must exclude email account from conditional access legacy authentication policies.
                #>

                Write-TSLog "Account: $UserNameEnd,"
                Write-TSLog "SENDING email to: $ToEnd,"
                Write-TSLog "From USER: $FromEnd,"
                Write-TSLog "Using PORT: $PortEnd,"
                Write-TSLog "Using RELAY: $SMTPServerEnd, with SSL"
                Write-TSLog "Using FUNCTION APP: $FunctionAppEnd,"
                Write-TSLog "With FUNCTION: $FunctionEnd"
                Write-TSLog "Logs included in body"
                Write-TSLog -End
                Send-AuditEmail -smtpServer $SMTPServerEnd -port $PortEnd -username $UserNameEnd `
                    -body $script:Logs -Function $FunctionEnd -FunctionApp $FunctionAppEnd -token $ApiTokenEnd -from $FromEnd -to $ToEnd -attachmentfiles ($ZipEnd).Split(" ") -ssl
                Remove-Item -Path $AttachmentFolderPathEnd -Recurse -Force -ErrorAction Stop
            }
            elseif ($WinSCPEnd) {
                Write-TSLog "Account: $UserNameEnd,"
                Write-TSLog "SENDING SFTP to: $FTPHostEnd,"
                Write-TSLog "For SshHostKeyFingerprint: "
                Write-TSLog "Files: $ZipEnd"
                Write-TSLog $SshHostKeyFingerprintEnd
                Submit-FTPUpload -FTPUserName $UserNameEnd -Password $Password `
                    -FTPHostName $FTPHostEnd -LocalFilePath ($ZipEnd).Split(" ") -SshHostKeyFingerprint $SshHostKeyFingerprintEnd -RemoteFTPPath $RemotePathEnd -ErrorVariable SubmitFTPErr
                if ($?) {
                    Write-TSLog  "The ADDSAuditTask archive has been uploaded to ftp."
                    Write-TSLog -End
                    Write-TSLog -LogOutputPath $LogOutputPath
                }
                else {
                    Write-TSLog -LogError $SubmitFTPErr
                }
            }
            elseif ($LocalDiskEnd) {
                #Confirm output path to console.
                Write-TSLog  "The ADDSAuditTask archive has been saved to: "
                Write-TSLog  "$ZipEnd"
                Write-TSLog -End
                Write-TSLog -LogOutputPath $LogOutputPath
            }
        }
    }
}

#EndRegion '.\Private\Initialize-AuditEndBlock.ps1' 153
#Region '.\Private\Send-AuditEmail.ps1' 0
function Send-AuditEmail {
    <#
    .SYNOPSIS
    This is a sample Private function only visible within the module. It uses Send-MailkitMessage
    To send email messages.
    .DESCRIPTION
    This sample function is not exported to the module and only return the data passed as parameter.
    .EXAMPLE
    Send-AuditEmail -smtpServer $SMTPServer -port $Port -username $Username -Function $Function -FunctionApp $FunctionApp -token $ApiToken -from $from -to $to -attachmentfilePath "$FilePath" -ssl
    .PARAMETER PrivateData
    The PrivateData parameter is what will be returned without transformation.
    #>

    param (
        [string]$smtpServer,
        [int]$port,
        [string]$username,
        [switch]$ssl,
        [string]$from,
        [string]$to,
        [string]$subject = "$($script:MyInvocation.MyCommand.Name -replace '\..*') report ran for $($env:USERDNSDOMAIN).",
        [string[]]$attachmentfiles,
        [string]$body,
        [securestring]$pass,
        [string]$Function,
        [string]$FunctionApp,
        [string]$token
    )
    Import-Module Send-MailKitMessage
    # Recipient
    $RecipientList = [MimeKit.InternetAddressList]::new()
    $RecipientList.Add([MimeKit.InternetAddress]$to)
    # Attachment
    $AttachmentList = [System.Collections.Generic.List[string]]::new()
    foreach ($currentItem in $attachmentfiles) {
        $AttachmentList.Add("$currentItem")
    }
    # From
    $from = [MimeKit.MailboxAddress]$from
    # Mail Account variable
    $User = $username
    if ($pass) {
        # Set Credential to $Password parameter input.
        $Credential = `
            [System.Management.Automation.PSCredential]::new($User, $pass)
    }
    elseif ($FunctionApp) {
        $url = "https://$($FunctionApp).azurewebsites.net/api/$($Function)"
        # Retrieve credentials from function app url into a SecureString.
        $a, $b = (Invoke-RestMethod $url -Headers @{ 'x-functions-key' = "$token" }).split(',')
        $Credential = `
            [System.Management.Automation.PSCredential]::new($User, (ConvertTo-SecureString -String $a -Key $b.split(' ')) )
    }
    # Create Parameter hashtable
    $Parameters = @{
        "UseSecureConnectionIfAvailable" = $ssl
        "Credential"                     = $Credential
        "SMTPServer"                     = $SMTPServer
        "Port"                           = $Port
        "From"                           = $From
        "RecipientList"                  = $RecipientList
        "Subject"                        = $subject
        "TextBody"                       = $body
        "AttachmentList"                 = $AttachmentList
    }
    Send-MailKitMessage @Parameters
    Clear-Variable -Name "a", "b", "Credential", "token" -Scope Local -ErrorAction SilentlyContinue
}
#EndRegion '.\Private\Send-AuditEmail.ps1' 68
#Region '.\Private\Submit-FTPUpload.ps1' 0
function Submit-FTPUpload {
    [CmdletBinding()]
    param (
        [string]$FTPUserName,
        [securestring]$Password,
        [string]$FTPHostName,
        [ValidateSet("Sftp", "SCP", "FTP", "Webdav", "s3")]
        [string]$Protocol = "Sftp",
        [ValidateSet("None", "Implicit ", "Explicit")]
        [string]$FTPSecure = "None",
        #[int]$FTPPort = 0,
        # Mandatory with SFTP/SCP
        [string[]]$SshHostKeyFingerprint,
        #[string]$SshPrivateKeyPath,
        [string[]]$LocalFilePath,
        # Send-WinSCPItem
        # './remoteDirectory'
        [string]$RemoteFTPPath
    )
    process {
        # This script will run in the context of the user. Please be sure it's a local admin with cached credentials.
        # Required Modules
        Import-Module WinSCP
        # Capture credentials.
        $Credential = [System.Management.Automation.PSCredential]::new($FTPUserName, $Password)
        # Open the session using the SessionOptions object.
        $sessionOption = New-WinSCPSessionOption -Credential $Credential -HostName $FTPHostName -SshHostKeyFingerprint $SshHostKeyFingerprint -Protocol $Protocol -FtpSecure $FTPSecure
        # New-WinSCPSession sets the PSDefaultParameterValue of the WinSCPSession parameter for all other cmdlets to this WinSCP.Session object.
        # You can set it to a variable if you would like, but it is only necessary if you will have more then one session open at a time.
        $WinSCPSession = New-WinSCPSession -SessionOption $sessionOption
        if (!(Test-WinSCPPath -Path $RemoteFTPPath -WinSCPSession $WinSCPSession)) {
            New-WinSCPItem -Path $RemoteFTPPath -ItemType Directory -WinSCPSession $WinSCPSession
        }
        # Upload a file to the directory.
        $errorindex = 0
        foreach ($File in $LocalFilePath) {
            $sendvar = Send-WinSCPItem -Path $File -Destination $RemoteFTPPath -WinSCPSession $WinSCPSession -ErrorAction Stop -ErrorVariable SendWinSCPErr
            if ($sendvar.IsSuccess -eq $false) {
                write-tslog -LogErrorEnd -LogErrorVar $SendWinSCPErr
                $errorindex += 1
            }
        }
        if ($ErrorIndex -ne 0) {
            Write-Output "Error"
            throw 1
        }
        # Close and remove the session object.
        Remove-WinSCPSession -WinSCPSession $WinSCPSession
    }
}
#EndRegion '.\Private\Submit-FTPUpload.ps1' 51
#Region '.\Private\Write-TSLog.ps1' 0
function Write-TSLog {
    <#
    .SYNOPSIS
    This is a sample Private function only visible within the module.
    .DESCRIPTION
    This sample function is not exported to the module and only return the data passed as parameter.
    .EXAMPLE
    $null = Write-Logs -PrivateData 'NOTHING TO SEE HERE'
    .PARAMETER PrivateData
    The PrivateData parameter is what will be returned without transformation.
#>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [OutputType([string])]
    param(
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true,
            ParameterSetName = 'Default',
            Position = 0
        )]
        [String[]]$LogString,
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true,
            ParameterSetName = 'Begin',
            Position = 0
        )]
        [switch]$Begin,
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true,
            ParameterSetName = 'End',
            Position = 0
        )]
        [switch]$End,
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true,
            ParameterSetName = 'LogError',
            Position = 0
        )]
        [switch]$LogError,
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true,
            ParameterSetName = 'LogErrorEnd',
            Position = 0
        )]
        [switch]$LogErrorEnd,
        [Parameter(ParameterSetName = 'LogError', Position = 1)]
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'LogErrorEnd',
            Position = 1
        )]
        [System.Management.Automation.ErrorRecord[]]$LogErrorVar,
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true,
            ParameterSetName = 'LogToFile',
            Position = 0
        )]
        [string]$LogOutputPath
    )
    process {
        # Change the ErrorActionPreference to 'Stop'
        $ErrorActionPreference = "SilentlyContinue"
        $ModuleName = $script:MyInvocation.MyCommand.Name.ToString() -replace '\..*'
        $ModuleVer = $MyInvocation.MyCommand.Version.ToString()
        $ErrorActionPreference = "Continue"
        if ($Begin) {
            Clear-Variable Logs -Scope Script -ErrorAction SilentlyContinue
            $TSLogString = "$(Get-TimeStamp) Begin Log for Module version $ModuleVer of Module: $ModuleName `n"
            $script:Logs += $TSLogString
        }
        elseif ($End) {
            $TSLogString = "$(Get-TimeStamp) End Log for Module version $ModuleVer of Module: $ModuleName `n"
            $script:Logs += $TSLogString
            Write-Output $script:Logs
        }
        elseif ($LogError) {
            if ($LogErrorVar) {
                #$TSLogString += "$(($LogErrorVar.Exception).ToString()) `n"
                $TSLogString += "$($global:Error[0].Exception.ErrorRecord) `n"
            }
            else {
                $TSLogString = "$($global:Error[0].Exception.ErrorRecord) `n"
            }
            $script:Logs += $TSLogString
        }
        elseif ($LogErrorEnd) {
            if ($LogErrorVar) {
                $TSLogString = "$(Get-TimeStamp) An Error Occured. The Error Variable was: `n"
                $script:Logs += $TSLogString
                $TSLogString += "$($LogErrorVar.Exception.ErrorRecord)" + "Error`n"
                $script:Logs += $TSLogString
                $TSLogString = "$(Get-TimeStamp) End Log for Module version $ModuleVer of Module: $ModuleName `n"
                $script:Logs += $TSLogString
                $TSLogString = "$(Get-TimeStamp) ErrorLog output to 'C:\temp\ADDSAuditTasksErrors.log' `n"
                $script:Logs += $TSLogString
            }
            else {
                $TSLogString = "$(Get-TimeStamp) An Error Occured. The exception was: `n"
                $script:Logs += $TSLogString
                $TSLogString = "$($global:Error[0].Exception.ErrorRecord) `n"
                $script:Logs += $TSLogString
                $TSLogString = "$(Get-TimeStamp) ErrorLog output to 'C:\temp\ADDSAuditTasksErrors.log' `n"
                $script:Logs += $TSLogString
            }
            Write-Output $script:Logs
            $script:Logs | Out-File "C:\temp\ADDSAuditTasksErrors.log" -Encoding utf8 -Append -Force
        }
        elseif ($LogOutputPath) {
            $script:Logs | Out-File $LogOutputPath -Encoding utf8 -Append -Force
            Write-Output "Logs saved to $LogOutputPath"
        }
        else {
            $TSLogString = "$(Get-TimeStamp) $logstring `n"
            $script:Logs += $TSLogString
        }
    }
}
#EndRegion '.\Private\Write-TSLog.ps1' 124
#Region '.\Public\Get-ADDSActiveAccountAudit.ps1' 0
function Get-ADDSActiveAccountAudit {
    <#
    .SYNOPSIS
        Active Directory Audit with Keyvault retrieval option.
    .DESCRIPTION
        Audit's Active Directory taking "days" as the input for how far back to check for a last sign in.
        Output can be kept locally, or sent remotely via email or sftp.
        Function App is the same as SendEmail except that it uses a password retrieved using the related Function App.
        The related function app would need to be created.
        Expects SecureString and Key as inputs to function app parameter set.
    .EXAMPLE
        PS C:\> Get-ADDSActiveAccountAudit -LocalDisk -Verbose
    .EXAMPLE
        PS C:\> Get-ADDSActiveAccountAudit -SendMailMessage -SMTPServer $SMTPServer -UserName "helpdesk@domain.com" -Password (Read-Host -AsSecureString) -To "support@domain.com" -Verbose
    .EXAMPLE
        PS C:\> Get-ADDSActiveAccountAudit -FunctionApp $FunctionApp -Function $Function -SMTPServer $SMTPServer -UserName "helpdesk@domain.com" -To "support@domain.com" -Verbose
    .EXAMPLE
        PS C:\> Get-ADDSActiveAccountAudit -WinSCP -UserName "ftphostname.UserName" -Password (Read-Host -AsSecureString) -FTPHost "ftphost.domain.com" -SshHostKeyFingerprint "<SShHostKeyFingerprint>" -Verbose
    .EXAMPLE
        PS C:\> Get-ADDSActiveAccountAudit -Clean -Verbose
    .PARAMETER LocalDisk
        Only output data to local disk.
    .PARAMETER SendMailMessage
        Adds parameters for sending Audit Report as an Email.
    .PARAMETER WinSCP
        Adds parameters for sending Audit Report via SFTP.
    .PARAMETER AttachmentFolderPath
        Default path is C:\temp\ADDSActiveAccountAuditLogs.
        This is the folder where attachments are going to be saved.
    .PARAMETER FunctionApp
        Azure Function App Name.
    .PARAMETER Function
        Azure Function App's Function Name. Ex. "HttpTrigger1"
    .PARAMETER ApiToken
        Private Function Key.
    .PARAMETER SMTPServer
        Defaults to Office 365 SMTP relay. Enter optional relay here.
    .PARAMETER Port
        SMTP Port to Relay. Ports can be: "993", "995", "587", or "25"
    .PARAMETER UserName
        Specify the account with an active mailbox and MFA disabled.
        Ensure the account has delegated access for Send On Behalf for any
        UPN set in the "$From" Parameter
    .PARAMETER Password
        Use: (Read-Host -AsSecureString) as in Examples.
        May be omitted.
    .PARAMETER To
        Recipient of the attachment outputs.
    .PARAMETER From
        Defaults to the same account as $UserName unless the parameter is set.
        Ensure the Account has delegated access to send on behalf for the $From account.
    .PARAMETER FTPHost
        SFTP Hostname.
    .PARAMETER RemotePath
        Remove FTP path. Will be created in the user path under functionname folder if not specified.
    .PARAMETER SshHostKeyFingerprint
        Adds parameters for sending Audit Report via SFTP.
    .PARAMETER DaysInactive
        Defaults to 90 days in the past.
        Specifies how far back to look for accounts last logon.
        If logon is within 90 days, it won't be included.
    .PARAMETER ADDSAccountIsNotEnabled
        Defaults to not being set.
        Choose to search for disabled Active Directory Users.
    .PARAMETER Clean
        Remove installed modules during run. Remove local files if not a LocalDisk run.
    .NOTES
        Can take password as input into secure string using (Read-Host -AsSecureString).
    #>

    [CmdletBinding(DefaultParameterSetName = 'LocalDisk', HelpURI = "https://criticalsolutionsnetwork.github.io/ADDSAuditTasks/#Get-ADDSActiveAccountAudit")]
    param (
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'LocalDisk',
            HelpMessage = 'Output to disk only',
            Position = 0
        )]
        [switch]$LocalDisk,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Send Mail to a relay',
            Position = 0
        )]
        [switch]$SendMailMessage,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'WinSCP',
            HelpMessage = 'Send using SFTP via WinSCP Module',
            Position = 0,
            ValueFromPipelineByPropertyName = $true
        )]
        [switch]$WinSCP,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'FunctionApp',
            HelpMessage = 'Enter the FunctionApp name',
            Position = 0
        )]
        [string]$FunctionApp,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'FunctionApp',
            HelpMessage = 'Enter the FunctionApp Function name',
            ValueFromPipelineByPropertyName = $true,
            Position = 1
        )]
        [string]$Function,
        [Parameter(ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Enter the SMTP hostname' ,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$SMTPServer = "smtp.office365.com",
        [Parameter(ParameterSetName = 'FunctionApp')]
        [Parameter(ParameterSetName = 'SendMailMessage')]
        [Parameter(ParameterSetName = 'WinSCP')]
        [Parameter(
            ParameterSetName = 'LocalDisk',
            HelpMessage = 'Enter output folder path',
            ValueFromPipeline = $true
        )]
        [string]$AttachmentFolderPath = "C:\temp\ADDSActiveAccountAuditLogs",
        [Parameter(ParameterSetName = 'WinSCP')]
        [Parameter(ParameterSetName = 'FunctionApp')]
        [Parameter(ParameterSetName = 'SendMailMessage')]
        [Parameter(
            ParameterSetName = 'LocalDisk',
            HelpMessage = 'Active Directory User Enabled or not',
            ValueFromPipelineByPropertyName = $true
        )]
        [switch]$ADDSAccountIsNotEnabled,
        [Parameter(ParameterSetName = 'WinSCP')]
        [Parameter(ParameterSetName = 'FunctionApp')]
        [Parameter(ParameterSetName = 'SendMailMessage')]
        [Parameter(
            ParameterSetName = 'LocalDisk',
            HelpMessage = 'Days back to check for recent sign in',
            ValueFromPipelineByPropertyName = $true
        )]
        [int]$DaysInactive = '90',
        [Parameter(Mandatory = $true, ParameterSetName = 'WinSCP')]
        [Parameter(Mandatory = $true, ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            Mandatory = $true,
            HelpMessage = 'Enter the Sending Account UPN Ex:"user@contoso.com"',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$UserName,
        [Parameter(ParameterSetName = 'WinSCP', Mandatory = $true)]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Copy Paste the following: $Password = (Read-Host -AsSecureString)',
            ValueFromPipelineByPropertyName = $true
        )]
        [securestring]$Password,
        [Parameter(ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Enter the port n
                umber for the mail relay'
,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("993", "995", "587", "25")]
        [int]$Port = 587,
        [Parameter(Mandatory = $true, ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            Mandatory = $true,
            HelpMessage = 'Enter the recipient email address',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidatePattern("[a-z0-9!#\$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")]
        [string]$To,
        [Parameter(ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Enter the name of the sender',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidatePattern("[a-z0-9!#\$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")]
        [string]$From = $UserName,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'FunctionApp',
            HelpMessage = 'Enter output folder path',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$ApiToken,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'WinSCP',
            HelpMessage = 'Enter FTP HostName',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$FTPHost,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'WinSCP',
            HelpMessage = 'Enter SshHostKeyFingerprint like: "ecdsa-sha2-nistp256 256 <Key>" ',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$SshHostKeyFingerprint,
        [Parameter(
            ParameterSetName = 'WinSCP',
            HelpMessage = 'Enter ftp remote path "/path/" ',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$RemotePath = ("./" + $($MyInvocation.MyCommand.Name -replace '\..*')) ,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'Clean',
            HelpMessage = 'Clean Modules and output path',
            Position = 0
        )]
        [switch]$Clean
    )
    Begin {
        $ScriptFunctionName = $MyInvocation.MyCommand.Name -replace '\..*'
        try {
            Initialize-AuditBeginBlock -AttachmentFolderPathBegin $AttachmentFolderPath -ScriptFunctionName $ScriptFunctionName -SendEmailMessageBegin $SendMailMessage -WinSCPBegin $WinSCP -CleanBegin $Clean -ErrorVariable InitBeginErr -ErrorAction Stop
        }
        catch {
            Write-TSLog "End Block last error for log: "
            Write-TSLog -LogError -LogErrorVar InitBeginErr
        }
        if ($ADDSAccountIsNotEnabled) {
            $Enabled = $false
        }
        else {
            $Enabled = $true
        }
    }
    Process {
        if (!($Clean)) {
            # Establish timeframe to review.
            $time = (Get-Date).Adddays( - ($DaysInactive))
            # Add Datetime to filename
            $csvFileName = "$AttachmentFolderPath\$((Get-Date).ToString('yyyy-MM-dd_hh.mm.ss'))_$($ScriptFunctionName)_$($env:USERDNSDOMAIN)"
            # Create FileNames
            $csv = "$csvFileName.csv"
            $zip = "$csvFileName.zip"
            Write-TSLog "Searching for users who have not signed in within the last $DaysInactive days, where parameter Enabled = $Enabled"
            # Audit Script with export to csv and zip. Paramters for Manager, lastLogonTimestamp and DistinguishedName normalized.
            # GetActiveUsers
            Get-ADUser -Filter { LastLogonTimeStamp -lt $time -and Enabled -eq $Enabled } -Properties `
                samaccountname, GivenName, Surname, Name, UserPrincipalName, lastlogontimestamp, DistinguishedName, `
                Title, Enabled, Description, Manager, Department -OutVariable ADExport | Out-Null
            $Export = @()
            foreach ($item in $ADExport) {
                $Export += [ADAuditAccount]::new(
                    $($item.SamAccountName),
                    $($item.GivenName),
                    $($item.Surname),
                    $($item.Name),
                    $($item.UserPrincipalName),
                    $($item.LastLogonTimeStamp),
                    $($item.Enabled),
                    $($item.LastLogonTimeStamp),
                    $($item.DistinguishedName),
                    $($item.Title),
                    $($item.Manager),
                    $($item.Department),
                    $false,
                    $false
                )
            }
            try {
                Export-AuditCSVtoZip -Exportobject $Export -csv $csv -zip $zip -ErrorAction Stop -ErrorVariable ExportAuditCSVZipErr
            }
            catch {
                Write-TSLog -LogErrorEnd -LogErrorVar $ExportAuditCSVZipErr.Exception.ErrorRecord
            }
        } # End If Clean Region
    } ## End Process Region
    End {
        try {
            Initialize-AuditEndBlock -SendEmailMessageEnd $SendMailMessage -WinSCPEnd $WinSCP -FTPHostend $FTPHost -SshHostKeyFingerprintEnd $SshHostKeyFingerprint -SmtpServerEnd $SMTPServer -PortEnd $Port -UserNameEnd $UserName -FromEnd $From -ToEnd $To `
                -AttachmentFolderPathEnd $AttachmentFolderPath -Password $Password -FunctionEnd $function -FunctionAppEnd $FunctionApp `
                -ApiTokenEnd $ApiToken -ZipEnd $zip -RemotePathEnd $RemotePath -LocalDiskEnd $LocalDisk -CleanEnd $Clean -ErrorVariable InitEndErr
        }
        catch {
            Write-TSLog "End Block last error for log: "
            Write-TSLog -LogError
        }
        # Clear Variables
        Clear-Variable -Name "Function", "FunctionApp", "ApiToken"
    }
}
#EndRegion '.\Public\Get-ADDSActiveAccountAudit.ps1' 292
#Region '.\Public\Get-ADDSAssetInventoryAudit.ps1' 0
function Get-ADDSAssetInventoryAudit {
    <#
    .SYNOPSIS
        Active Directory Server and Workstation Audit with Report export option (Can also be piped to CSV if Report isn't specified).
    .DESCRIPTION
        Audit's Active Directory taking "days" as the input for how far back to check for a device's last sign in.
        Output can be piped to a csv manually, or automatically to C:\temp or a specified path in "DirPath" using
        the -Report Switch.
        Use the Tab key for the -HostType Parameter.
    .EXAMPLE
        PS C:\> Get-ADDSInventoryAudit -HostType WindowsServers
    .EXAMPLE
        PS C:\> Get-ADDSInventoryAudit -HostType WindowsWorkstations -DirPath "C:\Temp\" -Report
    .EXAMPLE
        PS C:\> Get-ADDSInventoryAudit -HostType WindowsServers -DirPath "C:\Temp\" -Report
    .EXAMPLE
        PS C:\> Get-ADDSInventoryAudit -OSType "2008" -DirPath "C:\Temp\" -Report
    .PARAMETER HostType
        Select from Windows Server or Windows 10 plus.
    .PARAMETER OSType
        Search an OS String. Wildcards can be omitted as the function will automatically add the
        wildcard characters before searching.
    .PARAMETER DirPath
        The path to the -Report output directory.
    .PARAMETER Report
        Add report output as csv to DirPath directory.
    .PARAMETER AttachmentFolderPath
        Default path is C:\temp\ADDSDepartedUsersAuditLogs.
        This is the folder where attachments are going to be saved.
    .NOTES
        Outputs to C:\temp by default. For help type: help Get-ADDSAssetInventoryAudit -ShowWindow
    #>

    [CmdletBinding(DefaultParameterSetName = 'HostType' , HelpURI = "https://criticalsolutionsnetwork.github.io/ADDSAuditTasks/#Get-ADDSInventoryAudit")]
    param (
        [ValidateSet("WindowsServers","WindowsWorkstations","Non-Windows")]
        [Parameter(
            ParameterSetName = 'HostType',
            Mandatory = $true,
            Position = 0,
            HelpMessage = 'Name filter attached to users.',
            ValueFromPipeline = $true
        )]
        [string]$HostType,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'OSType',
            Position = 0,
            HelpMessage = 'Enter a Specific OS Name or first few letters of the OS to Search for in ActiveDirectory',
            ValueFromPipeline = $true
        )]
        [string]$OSType,
        [Parameter(
            Position = 1,
            HelpMessage = 'How many days back to consider an AD Computer last sign in as active',
            ValueFromPipelineByPropertyName = $true
        )]
        [int]$DaystoConsiderAHostInactive = 90,
        [Parameter(
            Position = 2,
            HelpMessage = 'Switch to output to directory specified in DirPath parameter',
            ValueFromPipelineByPropertyName = $true
        )]
        [switch]$Report,
        [Parameter(
            Position = 3,
            HelpMessage = 'Enter the working directory you wish the report to save to. Default creates C:\temp'
        )]
        [string]$DirPath = 'C:\temp\ADDSAssetInventoryAudit',
        [Parameter(
            HelpMessage = 'Search for Enabled or Disabled hosts',
            ValueFromPipelineByPropertyName = $true
        )]
        [bool]$Enabled = $true
    )
    begin {
        $ScriptFunctionName = $MyInvocation.MyCommand.Name -replace '\..*'
        $time = (Get-Date).Adddays( - ($DaystoConsiderAHostInactive))
        $module = Get-Module -Name ActiveDirectory -ListAvailable
        if (-not $module) {
            [ValidateSet("Y", "N")]$choice = Read-Host "Install Active Directory Module? Y or N ?"
            if (($Choice -eq "Y")) {
                Add-WindowsFeature RSAT-AD-PowerShell -IncludeAllSubFeature -Verbose -ErrorAction Stop
            }
            else {
                throw "You must install the Active Directory module to continue"
            }
        }
        try {
            Import-Module "activedirectory" -Global -ErrorAction Stop | Out-Null
        }
        catch {
            throw $Error[0].Error.Exception
        }
        $AttachmentFolderPathCheck = Test-Path -Path $DirPath
        If (!($AttachmentFolderPathCheck)) {
            Try {
                # If not present then create the dir
                New-Item -ItemType Directory $DirPath -Force -ErrorAction Stop | Out-Null
            }
            Catch {
                throw "Unable to create output directory $($DirPath)"
            }
        }
        switch ($PsCmdlet.ParameterSetName) {
            'HostType' {
                if ($HostType -eq "WindowsWorkstations") {
                    $FileSuffix = "Workstations"
                    Write-Verbose "###############################################"
                    Write-Verbose "Searching Windows Workstations......"
                    Start-Sleep 2
                }
                elseif ($HostType -eq "Non-Windows") {
                    $POSIX = $true
                    $FileSuffix = "Non-Windows"
                    Write-Verbose "###############################################"
                    Write-Verbose "Searching Non-Windows Computer Objects......"
                    Start-Sleep 2
                }
                elseif ($HostType -eq "WindowsServers") {
                    $OSPicked = "*Server*"
                    $FileSuffix = "Servers"
                    Write-Verbose "###############################################"
                    Write-Verbose "Searching Windows Servers......"
                    Start-Sleep 2
                }
            }
            'OSType' {
                $OSPicked = '*' + $OSType + '*'
                $FileSuffix = $OSType
                Write-Verbose "###############################################"
                Write-Verbose "Searching OSType $OsType......"
                Start-Sleep 2
            }
        }
        $propsArray = `
            "Created", `
            "Description", `
            "DNSHostName", `
            "Enabled", `
            "IPv4Address", `
            "IPv6Address", `
            "KerberosEncryptionType", `
            "lastLogonTimestamp", `
            "Name", `
            "OperatingSystem", `
            "DistinguishedName", `
            "servicePrincipalName", `
            "whenChanged"
    } # End Begin
    process {
        Write-Verbose "Searching computers that have logged in within the last $DaystoConsiderAHostInactive days."
        Write-Verbose "Where property Enabled = $Enabled"

        Start-Sleep 2
        if ($OSPicked) {
            Write-Verbose "And Operating System is like: $OSPicked."
            $ActiveComputers = (Get-ADComputer -Filter { (LastLogonTimeStamp -gt $time) -and (Enabled -eq $Enabled) -and (OperatingSystem -like $OSPicked) }).Name
        }
        elseif ($POSIX) {
            Write-Verbose "And Operating System is: Non-Windows(POSIX)."
            $ActiveComputers = (Get-ADComputer -Filter {OperatingSystem -notlike "*windows*" -and OperatingSystem -notlike "*server*" -and Enabled -eq $Enabled -and lastlogontimestamp -gt $time} ).Name
        }
        else{
            Write-Verbose "And Operating System is -like `"*windows*`" -and Operating System -notlike `"*server*`" (Workstations)."
            $ActiveComputers = (Get-ADComputer -Filter {OperatingSystem -like "*windows*" -and OperatingSystem -notlike "*server*" -and Enabled -eq $Enabled -and lastlogontimestamp -gt $time} ).Name
        }

        $ADComps = @()
        foreach ($comp in $ActiveComputers) {
            Get-ADComputer -Identity $comp -Properties $propsArray | Select-Object $propsArray -OutVariable ADComp | Out-Null
            $ADComps += $ADComp
        } # End Foreach
        $ADCompExport = @()
        foreach ($item in $ADComps) {
            $ADCompExport += [ADComputerAccount]::new(
                $item.Name,
                $item.DNSHostName,
                $item.Enabled,
                $item.IPv4Address,
                $item.IPv6Address,
                $item.OperatingSystem,
                $item.lastLogonTimestamp,
                $item.Created,
                $item.whenChanged,
                $item.Description,
                $item.DistinguishedName,
                $(($item.KerberosEncryptionType).Value.tostring()),
                ($item.servicePrincipalName -join " | "),
                $item.Name,
                $item.lastLogonTimestamp
            ) # End New [ADComputerAccount] object
        }# End foreach Item in ADComps
        $Export = @()
        foreach ($Comp in $ADCompExport) {
            $hash = [ordered]@{
                DNSHostName            = $Comp.DNSHostName
                ComputerName           = $Comp.ComputerName
                Enabled                = $Comp.Enabled
                IPv4Address            = $Comp.IPv4Address
                IPv6Address            = $Comp.IPv6Address
                OperatingSystem        = $Comp.OperatingSystem
                LastLogon              = $Comp.LastLogon
                LastSeen               = $Comp.LastSeen
                Created                = $Comp.Created
                Modified               = $Comp.Modified
                Description            = $Comp.Description
                GroupMemberships       = $Comp.GroupMemberships
                OrgUnit                = $Comp.OrgUnit
                KerberosEncryptionType = $Comp.KerberosEncryptionType
                SPNs                   = $Comp.SPNs
            }
            New-Object -TypeName PSCustomObject -Property $hash -OutVariable PSObject | Out-Null
            $Export += $PSObject
        } # End foreach Comp in ADCompExport
    } # End Process
    end {
        if ($Report) {
            # Add Datetime to filename
            $csvFileName = "$DirPath\$((Get-Date).ToString('yyyy-MM-dd_hh.mm.ss'))_$($env:USERDNSDOMAIN)_$($ScriptFunctionName)_$($FileSuffix)"
            # Create FileNames
            $csv = "$csvFileName.csv"
            $zip = "$csvFileName.zip"
            $Export | Export-Csv $csv -NoTypeInformation
            Compress-Archive -Path $csv -DestinationPath $zip
            Remove-Item $csv -Force
            Write-Verbose "Archive saved to: "
            Write-Verbose "Directory: $DirPath"
            Write-Verbose "FilePath: $zip"
        }
        else {
            Write-Verbose "Returning output object."
            Start-Sleep 2
            return $Export
        }
    } # End End
}
#EndRegion '.\Public\Get-ADDSAssetInventoryAudit.ps1' 237
#Region '.\Public\Get-ADDSDepartedUsersAccountAudit.ps1' 0
function Get-ADDSDepartedUsersAccountAudit {
    <#
    .SYNOPSIS
        Active Directory Audit with Keyvault retrieval option.
    .DESCRIPTION
        Audit's Active Directory taking a Prefix used as a Wildcard as input for checking user accounts.
        Output can be kept locally, or sent remotely via email or sftp.
        Function App is the same as SendEmail except that it uses a password retrieved using the related Function App.
        The related function app would need to be created.
        Expects SecureString and Key as inputs to function app parameter set.
    .EXAMPLE
        PS C:\> Get-ADDSDepartedUsersAccountAudit -LocalDisk -WildCardIdentifier "<StringToSearchFor>" -Verbose
    .EXAMPLE
        PS C:\> Get-ADDSDepartedUsersAccountAudit -SendMailMessage -SMTPServer $SMTPServer -UserName "helpdesk@domain.com" -Password (Read-Host -AsSecureString) -To "support@domain.com" -WildCardIdentifier "<StringToSearchFor>" -Verbose
    .EXAMPLE
        PS C:\> Get-ADDSDepartedUsersAccountAudit -FunctionApp $FunctionApp -Function $Function -SMTPServer $SMTPServer -UserName "helpdesk@domain.com" -To "support@domain.com" -WildCardIdentifier "<StringToSearchFor>" -Verbose
    .EXAMPLE
        PS C:\> Get-ADDSDepartedUsersAccountAudit -WinSCP -UserName "ftphostname.UserName" -Password (Read-Host -AsSecureString) -FTPHost "ftphost.domain.com" -SshHostKeyFingerprint "<SShHostKeyFingerprint>" -WildCardIdentifier "<StringToSearchFor>" -Verbose
    .EXAMPLE
        PS C:\> Get-ADDSDepartedUsersAccountAudit -Clean -Verbose
    .PARAMETER LocalDisk
        Only output data to local disk.
    .PARAMETER SendMailMessage
        Adds parameters for sending Audit Report as an Email.
    .PARAMETER WinSCP
        Adds parameters for sending Audit Report via SFTP.
    .PARAMETER AttachmentFolderPath
        Default path is C:\temp\ADDSDepartedUsersAuditLogs.
        This is the folder where attachments are going to be saved.
    .PARAMETER FunctionApp
        Azure Function App Name.
    .PARAMETER Function
        Azure Function App's Function Name. Ex. "HttpTrigger1"
    .PARAMETER ApiToken
        Private Function Key.
    .PARAMETER SMTPServer
        Defaults to Office 365 SMTP relay. Enter optional relay here.
    .PARAMETER Port
        SMTP Port to Relay. Ports can be: "993", "995", "587", or "25"
    .PARAMETER UserName
        Specify the account with an active mailbox and MFA disabled.
        Ensure the account has delegated access for Send On Behalf for any
        UPN set in the "$From" Parameter
    .PARAMETER Password
        Use: (Read-Host -AsSecureString) as in Examples.
        May be omitted.
    .PARAMETER To
        Recipient of the attachment outputs.
    .PARAMETER From
        Defaults to the same account as $UserName unless the parameter is set.
        Ensure the Account has delegated access to send on behalf for the $From account.
    .PARAMETER WildCardIdentifier
        Name wildcard appended to user account.
    .PARAMETER Clean
        Remove installed modules during run. Remove local files if not a LocalDisk run.
    .NOTES
        Can take password as input into secure string using (Read-Host -AsSecureString).
    #>

    [CmdletBinding(DefaultParameterSetName = 'LocalDisk' , HelpURI = "https://criticalsolutionsnetwork.github.io/ADDSAuditTasks/#Get-ADDSDepartedUsersAccountAudit")]
    param (
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'LocalDisk',
            HelpMessage = 'Output to disk only',
            Position = 0
        )]
        [switch]$LocalDisk,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Send Mail to a relay',
            Position = 0
        )]
        [switch]$SendMailMessage,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'WinSCP',
            HelpMessage = 'Send using SFTP via WinSCP Module',
            Position = 0,
            ValueFromPipelineByPropertyName = $true
        )]
        [switch]$WinSCP,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'FunctionApp',
            HelpMessage = 'Enter the FunctionApp name',
            Position = 0
        )]
        [string]$FunctionApp,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'FunctionApp',
            HelpMessage = 'Enter the FunctionApp Function name',
            ValueFromPipelineByPropertyName = $true,
            Position = 1
        )]
        [string]$Function,
        [Parameter(ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Enter the SMTP hostname' ,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$SMTPServer = "smtp.office365.com",
        [Parameter(ParameterSetName = 'FunctionApp')]
        [Parameter(ParameterSetName = 'SendMailMessage')]
        [Parameter(ParameterSetName = 'WinSCP')]
        [Parameter(
            ParameterSetName = 'LocalDisk',
            HelpMessage = 'Enter output folder path',
            ValueFromPipeline = $true
        )]
        [string]$AttachmentFolderPath = "C:\temp\ADDSDepartedUsersAuditLogs",
        [Parameter(Mandatory = $true, ParameterSetName = 'WinSCP')]
        [Parameter(Mandatory = $true, ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            Mandatory = $true,
            HelpMessage = 'Enter the Sending Account UPN Ex:"user@contoso.com"',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$UserName,
        [Parameter(ParameterSetName = 'WinSCP', Mandatory = $true)]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Copy Paste the following: $Password = (Read-Host -AsSecureString)',
            ValueFromPipelineByPropertyName = $true
        )]
        [securestring]$Password,
        [Parameter(ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Enter the port n
                umber for the mail relay'
,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("993", "995", "587", "25")]
        [int]$Port = 587,
        [Parameter(Mandatory = $true, ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            Mandatory = $true,
            HelpMessage = 'Enter the recipient email address',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidatePattern("[a-z0-9!#\$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")]
        [string]$To,
        [Parameter(ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Enter the name of the sender',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidatePattern("[a-z0-9!#\$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")]
        [string]$From = $UserName,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'FunctionApp',
            HelpMessage = 'Enter output folder path',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$ApiToken,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'WinSCP',
            HelpMessage = 'Enter FTP HostName',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$FTPHost,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'WinSCP',
            HelpMessage = 'Enter SshHostKeyFingerprint like: "ecdsa-sha2-nistp256 256 <Key>" ',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$SshHostKeyFingerprint,
        [Parameter(
            ParameterSetName = 'WinSCP',
            HelpMessage = 'Enter ftp remote path "/path/" ',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$RemotePath = ("./" + $($MyInvocation.MyCommand.Name -replace '\..*')) ,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'Clean',
            HelpMessage = 'Clean Modules and output path',
            Position = 0
        )]
        [switch]$Clean,
        [Parameter(ParameterSetName = 'WinSCP', Mandatory = $true)]
        [Parameter(ParameterSetName = 'FunctionApp', Mandatory = $true)]
        [Parameter(ParameterSetName = 'SendMailMessage', Mandatory = $true)]
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'LocalDisk',
            HelpMessage = 'Name filter attached to users.',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$WildCardIdentifier
    )
    begin {
        $ScriptFunctionName = $MyInvocation.MyCommand.Name -replace '\..*'
        try {
            Initialize-AuditBeginBlock -AttachmentFolderPathBegin $AttachmentFolderPath -ScriptFunctionName $ScriptFunctionName -SendEmailMessageBegin $SendMailMessage -CleanBegin $Clean -ErrorVariable InitBeginErr -ErrorAction Stop
        }
        catch {
            Write-TSLog "End Block last error for log: "
            Write-TSLog -LogError -LogErrorVar InitBeginErr
        }
    }
    process {
        if (!($Clean)) {
            # Add Datetime to filename
            $csvFileName = "$AttachmentFolderPath\$((Get-Date).ToString('yyyy-MM-dd_hh.mm.ss'))_$($ScriptFunctionName)_$($env:USERDNSDOMAIN)"
            # Create FileNames
            $csv = "$csvFileName.csv"
            $zip = "$csvFileName.zip"
            Write-TSLog "Searching for users appended with:`"$WildCardIdentifier`" in Active Directory."
            # Audit Script with export to csv and zip.
            # Get ad user with Name String Filter
            $WildCardIdentifierstring = '*' + $WildCardIdentifier + '*'
            Get-ADUser -Filter { Name -like $WildCardIdentifierstring } -Properties `
                samaccountname, GivenName, Surname, Name, UserPrincipalName, lastlogontimestamp, DistinguishedName, `
                Title, Enabled, Description, Manager, Department `
                -OutVariable ADExport | Out-Null
            $Export = @()
            foreach ($item in $ADExport) {
                $Export += [ADAuditAccount]::new(
                    $($item.SamAccountName),
                    $($item.GivenName),
                    $($item.Surname),
                    $($item.Name),
                    $($item.UserPrincipalName),
                    $($item.LastLogonTimeStamp),
                    $($item.Enabled),
                    $($item.LastLogonTimeStamp),
                    $($item.DistinguishedName),
                    $($item.Title),
                    $($item.Manager),
                    $($item.Department),
                    $false,
                    $false
                )
            }
            try {
                Export-AuditCSVtoZip -Exportobject $Export -csv $csv -zip $zip -ErrorAction Stop -ErrorVariable ExportAuditCSVZipErr
            }
            catch {
                Write-TSLog -LogErrorEnd -LogErrorVar $ExportAuditCSVZipErr
                throw $ExportAuditCSVZipErr
            }
        } # End if (!($Clean)) {...}
    } # End process region
    End {
        try {
            Initialize-AuditEndBlock -SendEmailMessageEnd $SendMailMessage -WinSCPEnd $WinSCP -FTPHostend $FTPHost -SshHostKeyFingerprintEnd $SshHostKeyFingerprint -SmtpServerEnd $SMTPServer -PortEnd $Port -UserNameEnd $UserName -FromEnd $From -ToEnd $To `
                -AttachmentFolderPathEnd $AttachmentFolderPath -Password $Password -FunctionEnd $function -FunctionAppEnd $FunctionApp `
                -ApiTokenEnd $ApiToken -ZipEnd $zip -RemotePathEnd $RemotePath -LocalDiskEnd $LocalDisk -CleanEnd $Clean -ErrorVariable InitEndErr
        }
        catch {
            Write-TSLog "End Block last error for log: "
            Write-TSLog -LogError
        }
        # Clear Variables
        Clear-Variable -Name "Function", "FunctionApp", "ApiToken"
    }
}
#EndRegion '.\Public\Get-ADDSDepartedUsersAccountAudit.ps1' 268
#Region '.\Public\Get-ADDSPrivilegedAccountAudit.ps1' 0
function Get-ADDSPrivilegedAccountAudit {
    <#
    .SYNOPSIS
        Active Directory Audit with Keyvault retrieval option.
    .DESCRIPTION
        Audit's Active Directory for priviledged users and groups, and extended rights.
        Output can be kept locally, or sent remotely via email or sftp.
        Function App is the same as SendEmail except that it uses a password retrieved using the related Function App.
        The related function app would need to be created.
        Expects SecureString and Key as inputs to function app parameter set.
    .EXAMPLE
        PS C:\> Get-ADDSPrivilegedAccountAudit -LocalDisk -Verbose
    .EXAMPLE
        PS C:\> Get-ADDSPrivilegedAccountAudit -SendMailMessage -SMTPServer $SMTPServer -UserName "helpdesk@domain.com" -Password (Read-Host -AsSecureString) -To "support@domain.com" -Verbose
    .EXAMPLE
        PS C:\> Get-ADDSPrivilegedAccountAudit -FunctionApp $FunctionApp -Function $Function -SMTPServer $SMTPServer -UserName "helpdesk@domain.com" -To "support@domain.com" -Verbose
    .EXAMPLE
        PS C:\> Get-ADDSPrivilegedAccountAudit -WinSCP -UserName "ftphostname.UserName" -Password (Read-Host -AsSecureString) -FTPHost "ftphost.domain.com" -SshHostKeyFingerprint "<SShHostKeyFingerprint>" -Verbose
    .EXAMPLE
        PS C:\> Get-ADDSPrivilegedAccountAudit -Clean -Verbose
    .PARAMETER LocalDisk
        Only output data to local disk.
    .PARAMETER SendMailMessage
        Adds parameters for sending Audit Report as an Email.
    .PARAMETER WinSCP
        Adds parameters for sending Audit Report via SFTP.
    .PARAMETER AttachmentFolderPath
        Default path is C:\temp\ADDSPrivilegedAccountAuditLogs.
        This is the folder where attachments are going to be saved.
    .PARAMETER FunctionApp
        Azure Function App Name.
    .PARAMETER Function
        Azure Function App's Function Name. Ex. "HttpTrigger1"
    .PARAMETER ApiToken
        Private Function Key.
    .PARAMETER SMTPServer
        Defaults to Office 365 SMTP relay. Enter optional relay here.
    .PARAMETER Port
        SMTP Port to Relay. Ports can be: "993", "995", "587", or "25"
    .PARAMETER UserName
        Specify the account with an active mailbox and MFA disabled.
        Ensure the account has delegated access for Send On Behalf for any
        UPN set in the "$From" Parameter
    .PARAMETER Password
        Use: (Read-Host -AsSecureString) as in Examples.
        May be omitted.
    .PARAMETER To
        Recipient of the attachment outputs.
    .PARAMETER From
        Defaults to the same account as $UserName unless the parameter is set.
        Ensure the Account has delegated access to send on behalf for the $From account.
    .PARAMETER FTPHost
        SFTP Hostname.
    .PARAMETER RemotePath
        Remove FTP path. Will be created in the user path under functionname folder if not specified.
    .PARAMETER SshHostKeyFingerprint
        Adds parameters for sending Audit Report via SFTP.
    .PARAMETER Clean
        Remove installed modules during run. Remove local files if not a LocalDisk run.
    .NOTES
        Can take password as input into secure string using (Read-Host -AsSecureString).
    #>

    [CmdletBinding(DefaultParameterSetName = 'LocalDisk' , HelpURI = "https://criticalsolutionsnetwork.github.io/ADDSAuditTasks/#Get-ADDSPrivilegedAccountAudit")]
    param (
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'LocalDisk',
            HelpMessage = 'Output to disk only',
            Position = 0
        )]
        [switch]$LocalDisk,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Send Mail to a relay',
            Position = 0
        )]
        [switch]$SendMailMessage,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'WinSCP',
            HelpMessage = 'Send using SFTP via WinSCP Module',
            Position = 0,
            ValueFromPipelineByPropertyName = $true
        )]
        [switch]$WinSCP,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'FunctionApp',
            HelpMessage = 'Enter the FunctionApp name',
            Position = 0
        )]
        [string]$FunctionApp,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'FunctionApp',
            HelpMessage = 'Enter the FunctionApp Function name',
            ValueFromPipelineByPropertyName = $true,
            Position = 1
        )]
        [string]$Function,
        [Parameter(ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Enter the SMTP hostname' ,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$SMTPServer = "smtp.office365.com",
        [Parameter(ParameterSetName = 'FunctionApp')]
        [Parameter(ParameterSetName = 'SendMailMessage')]
        [Parameter(ParameterSetName = 'WinSCP')]
        [Parameter(
            ParameterSetName = 'LocalDisk',
            HelpMessage = 'Enter output folder path',
            ValueFromPipeline = $true
        )]
        [string]$AttachmentFolderPath = "C:\temp\ADDSPrivilegedAccountAuditLogs",
        [Parameter(Mandatory = $true, ParameterSetName = 'WinSCP')]
        [Parameter(Mandatory = $true, ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            Mandatory = $true,
            HelpMessage = 'Enter the Sending Account UPN or FTP Username if using WinSCP. Ex:"user@contoso.com" or "ftphost.helpdesk"',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$UserName,
        [Parameter(ParameterSetName = 'WinSCP', Mandatory = $true)]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Copy Paste the following: $Password = (Read-Host -AsSecureString)',
            ValueFromPipelineByPropertyName = $true
        )]
        [securestring]$Password,
        [Parameter(ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Enter the port number for the mail relay',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("993", "995", "587", "25")]
        [int]$Port = 587,
        [Parameter(Mandatory = $true, ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            Mandatory = $true,
            HelpMessage = 'Enter the recipient email address',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidatePattern("[a-z0-9!#\$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")]
        [string]$To,
        [Parameter(ParameterSetName = 'FunctionApp')]
        [Parameter(
            ParameterSetName = 'SendMailMessage',
            HelpMessage = 'Enter the name of the sender',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidatePattern("[a-z0-9!#\$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")]
        [string]$From = $UserName,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'FunctionApp',
            HelpMessage = 'Enter output folder path',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$ApiToken,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'WinSCP',
            HelpMessage = 'Enter FTP HostName',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$FTPHost,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'WinSCP',
            HelpMessage = 'Enter SshHostKeyFingerprint like: "ecdsa-sha2-nistp256 256 <Key>" ',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$SshHostKeyFingerprint,
        [Parameter(
            ParameterSetName = 'WinSCP',
            HelpMessage = 'Enter ftp remote path "/path/" ',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$RemotePath = ("./" + $($MyInvocation.MyCommand.Name -replace '\..*')) ,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'Clean',
            HelpMessage = 'Clean Modules and output path',
            Position = 0
        )]
        [switch]$Clean
    )
    begin {
        $ScriptFunctionName = $MyInvocation.MyCommand.Name -replace '\..*'
        try {
            Initialize-AuditBeginBlock -AttachmentFolderPathBegin $AttachmentFolderPath -ScriptFunctionName $ScriptFunctionName -SendEmailMessageBegin $SendMailMessage -CleanBegin $Clean -ErrorVariable InitBeginErr -ErrorAction Stop
        }
        catch {
            Write-TSLog "End Block last error for log: "
            Write-TSLog -LogError -LogErrorVar InitBeginErr
        }
    } # End begin
    process {
        if (!($Clean)) {
            $csvFileName = "$AttachmentFolderPath\$((Get-Date).ToString('yyyy-MM-dd_hh.mm.ss'))_$($ScriptFunctionName)_$($env:USERDNSDOMAIN)"
            # Create FileNames
            $csv = "$csvFileName.csv"
            $zip = "$csvFileName.zip"
            # AD Privileged Groups Array
            $AD_PrivilegedGroups = @(
                'Enterprise Admins',
                'Schema Admins',
                'Domain Admins',
                'Administrators',
                'Cert Publishers',
                'Account Operators',
                'Server Operators',
                'Backup Operators',
                'Print Operators',
                'DnsAdmins',
                'DnsUpdateProxy',
                'DHCP Administrators'
            )
            # Time Variables
            $time90 = (Get-Date).Adddays( - (90))
            $time60 = (Get-Date).Adddays( - (60))
            $time30 = (Get-Date).Adddays( - (30))
            # Create Arrays
            $members = @()
            $ADUsers = @()
            foreach ($group in $AD_PrivilegedGroups) {
                Clear-Variable GroupMember -ErrorAction SilentlyContinue
                Get-ADGroupMember -Identity $group -Recursive -OutVariable GroupMember | Out-Null
                $GroupMember | Select-Object SamAccountName, Name, ObjectClass, `
                @{N = 'PriviledgedGroup'; E = { $group } }, `
                @{N = 'Enabled'; E = { (Get-ADUser -Identity $_.samaccountname).Enabled } }, `
                @{N = 'PasswordNeverExpires'; E = { (Get-ADUser -Identity $_.samaccountname -Properties PasswordNeverExpires).PasswordNeverExpires } }, `
                @{N = 'LastLogin'; E = { [DateTime]::FromFileTime((Get-ADUser -Identity $_.samaccountname -Properties lastLogonTimestamp).lastLogonTimestamp) } }, `
                @{N = 'LastSeen'; E = {
                        switch ([DateTime]::FromFileTime((Get-ADUser -Identity $_.samaccountname -Properties lastLogonTimestamp).lastLogonTimestamp)) {
                            # Over 90 Days
                            { ($_ -lt $time90) } { '3+ months'; break }
                            # Over 60 Days
                            { ($_ -lt $time60) } { '2+ months'; break }
                            # Over 90 Days
                            { ($_ -lt $time30) } { '1+ month'; break }
                            default { 'Recently' }
                        }
                    }
                }, `
                @{N = 'OrgUnit'; E = { $_.DistinguishedName -replace '^.*?,(?=[A-Z]{2}=)' } }, `
                @{N = 'GroupMemberships'; E = { Get-ADGroupMemberof -SamAccountName $_.samaccountname } }, `
                    Title, `
                @{N = 'Manager'; E = { (Get-ADUser -Identity $_.manager).Name } }, `
                @{N = 'SuspectedSvcAccount'; E = {
                        # Null gave unexpected behavior on the left side. Works on the right side.
                        if (((Get-ADUser -Identity $_.samaccountname -Properties PasswordNeverExpires).PasswordNeverExpires) -or (((Get-ADUser -Identity $_.samaccountname -Properties servicePrincipalName).servicePrincipalName) -ne $null) ) {
                            return $true
                        } # end if
                        else {
                            return $false
                        } # end else
                    } # End Expression
                }, # End Named Expression SuspectedSvcAccount
                Department, AccessRequired, NeedMailbox -OutVariable members | Out-Null
                $ADUsers += $members
            }
            $Export = @()
            # Create $Export Object
            foreach ($User in $ADUsers) {
                $hash = [ordered]@{
                    PriviledgedGroup     = $User.PriviledgedGroup
                    SamAccountName       = $User.SamAccountName
                    Name                 = $User.Name
                    ObjectClass          = $User.ObjectClass
                    LastLogin            = $User.LastLogin
                    LastSeen             = $User.LastSeen
                    GroupMemberships     = $User.GroupMemberships
                    Title                = $User.Title
                    Manager              = $User.Manager
                    Department           = $User.Department
                    OrgUnit              = $User.OrgUnit
                    Enabled              = $User.Enabled
                    PasswordNeverExpires = $User.PasswordNeverExpires
                    SuspectedSvcAccount  = $User.SuspectedSvcAccount
                    AccessRequired       = $false
                    NeedMailbox          = $true
                }
                New-Object -TypeName PSCustomObject -Property $hash -OutVariable PSObject | Out-Null
                $Export += $PSObject
            }
            # Create filenames
            $csv2 = $csv -replace ".csv", ".ExtendedPermissions.csv"
            $zip2 = $zip -replace ".zip", ".ExtendedPermissions.zip"
            $csv3 = $csv -replace ".csv", ".PossibleServiceAccounts.csv"
            $zip3 = $zip -replace ".zip", ".PossibleServiceAccounts.zip"
            # Get PDC
            $dc = (Get-ADDomainController -Discover -DomainName $env:USERDNSDOMAIN -Service PrimaryDC).Name
            # Get DN of AD Root.
            $rootou = (Get-ADRootDSE).defaultNamingContext
            # Get ad objects from the PDC for the root ou. #TODO Check
            $Allobjects = Get-ADObject -Server $dc -SearchBase $rootou -SearchScope subtree -LDAPFilter `
                "(&(objectclass=user)(objectcategory=person))" -Properties ntSecurityDescriptor -ResultSetSize $null
            # "(|(objectClass=domain)(objectClass=organizationalUnit)(objectClass=group)(sAMAccountType=805306368)(objectCategory=Computer)(&(objectclass=user)(objectcategory=person)))"
            # Create $Export2 Object
            $Export2 = Foreach ($ADObject in $Allobjects) {
                Get-AdExtendedRight $ADObject
            }
            # Export Delegated access, allowed protocols and Destination Serivces.
            $Export3 = Get-ADObject -Filter { (msDS-AllowedToDelegateTo -like '*') -or (UserAccountControl -band 0x0080000) -or (UserAccountControl -band 0x1000000) } `
                -prop samAccountName, msDS-AllowedToDelegateTo, servicePrincipalName, userAccountControl | `
                Select-Object DistinguishedName, ObjectClass, samAccountName, `
            @{N = 'servicePrincipalName'; E = { $_.servicePrincipalName -join " | " } }, `
            @{N = 'DelegationStatus'; E = { if ($_.UserAccountControl -band 0x80000) { 'AllServices' }else { 'SpecificServices' } } }, `
            @{N = 'AllowedProtocols'; E = { if ($_.UserAccountControl -band 0x1000000) { 'Any' }else { 'Kerberos' } } }, `
            @{N = 'DestinationServices'; E = { $_.'msDS-AllowedToDelegateTo' } }
            # Try first export.
            Export-AuditCSVtoZip -Exported $Export -CSVName $csv -ZipName $zip -ErrorVariable ExportAuditCSVZipErr
            # Try second export.
            Export-AuditCSVtoZip -Exported $Export2 -CSVName $csv2 -ZipName $zip2 -ErrorVariable ExportAuditCSVZipErr2
            # try third export
            Export-AuditCSVtoZip -Exported $Export3 -CSVName $csv3 -ZipName $zip3 -ErrorVariable ExportAuditCSVZipErr3
        } # End If Not Clean
    } # End process
    End {
        try {
            Initialize-AuditEndBlock -SendEmailMessageEnd $SendMailMessage -WinSCPEnd $WinSCP -FTPHostend $FTPHost -SshHostKeyFingerprintEnd $SshHostKeyFingerprint -SmtpServerEnd $SMTPServer -PortEnd $Port -UserNameEnd $UserName -FromEnd $From -ToEnd $To `
                -AttachmentFolderPathEnd $AttachmentFolderPath -Password $Password -FunctionEnd $function -FunctionAppEnd $FunctionApp `
                -ApiTokenEnd $ApiToken -ZipEnd $zip, $zip2, $zip3 -RemotePathEnd $RemotePath -LocalDiskEnd $LocalDisk -CleanEnd $Clean -ErrorVariable InitEndErr
        }
        catch {
            Write-TSLog "End Block last error for log: "
            Write-TSLog -LogError
        }
        # Clear Variables
        Clear-Variable -Name "Function", "FunctionApp", "ApiToken"
    } # End end
}
#EndRegion '.\Public\Get-ADDSPrivilegedAccountAudit.ps1' 340
#Region '.\Public\Get-ADUsersLastLogon.ps1' 0
function Get-ADUsersLastLogon {
    <#
    .SYNOPSIS
    Takes SamAccountName as input to retrieve most recent LastLogon from all DC's.
    .DESCRIPTION
    Takes SamAccountName as input to retrieve most recent LastLogon from all DC's and output as DateTime.
    .EXAMPLE
    Get-ADUsersLastLogon -SamAccountName "UserName"
    .PARAMETER SamAccountName
    The SamAccountName of the user being checked for LastLogon.
    #>

    [CmdletBinding(HelpURI = "https://criticalsolutionsnetwork.github.io/ADDSAuditTasks/#Get-ADUsersLastLogon")]
    [OutputType([datetime])]
    param (
        [Alias("Identity", "UserName", "Account")]
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Enter the SamAccountName',
            ValueFromPipeline = $true
        )]
        $SamAccountName
    )
    process {
        $dcs = Get-ADDomainController -Filter { Name -like "*" }
        $user = Get-ADUser -Identity $SamAccountName
        $time = 0
        $dt = @()
        foreach ($dc in $dcs) {
            $hostname = $dc.HostName
            $usertime = $user | Get-ADObject -Server $hostname -Properties lastLogon
            if ($usertime.LastLogon -gt $time) {
                $time = $usertime.LastLogon
            }
            $dt += [DateTime]::FromFileTime($time)
        }
        return ($dt | Sort-Object -Descending)[0]
    }
}
#EndRegion '.\Public\Get-ADUsersLastLogon.ps1' 39
#Region '.\Public\Get-NetworkScan.ps1' 0
function Get-NetworkScan {
    <#
    .SYNOPSIS
        Discovers local network and runs port scans on all hosts found for specific or default sets of ports.
    .DESCRIPTION
        Scans the network for open ports specified by the user or default ports if no ports are specified.
        Creates reports if report switch is active.
    .NOTES
        Installs PSnmap if not found and can output a report, or just the results.
    .LINK
        Specify a URI to a help page, this will show when Get-Help -Online is used.
    .EXAMPLE
        Get-NetworkScan -report
    .PARAMETER Ports
        Default ports are:
        "21", "22", "23", "25", "53", "67", "68", "80", "443", `
        "88", "464", "123", "135", "137", "138", "139", `
        "445", "389", "636", "514", "587", "1701", `
        "3268", "3269", "3389", "5985", "5986"
 
        If you want to supply a port, do so as an integer or an array of integers.
        "22","80","443", etc.
    .PARAMETER Report
        Specify this switch if you would like a report generated in C:\temp.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default' , HelpURI = "https://criticalsolutionsnetwork.github.io/ADDSAuditTasks/#Get-NetworkScan")]
    param (
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Position = 0
        )]
        [ValidateRange(1, 65535)]
        [int[]]$Ports,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'Default',
            HelpMessage = 'Automatically find and scan local attached subnets',
            ValueFromPipelineByPropertyName = $true,
            Position = 1
        )]
        [switch]$LocalSubnets,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'Computers',
            HelpMessage = 'Scan host or array of hosts using Subet ID in CIDR Notation, IP, NETBIOS, or FQDN in "quotes"',
            ValueFromPipelineByPropertyName = $true,
            Position = 1
        )]
        [string[]]$Computers,
        [switch]$Report
    )
    begin {
        If (Get-Module -ListAvailable -Name "PSnmap") { Import-Module "PSnmap" } Else { Install-Module "PSnmap" -Force; Import-Module "PSnmap" }
        if (!($ports)) {
            [int[]]$ports = "21", "22", "23", "25", "53", "67", "68", "80", "443", `
                "88", "464", "123", "135", "137", "138", "139", `
                "445", "389", "636", "514", "587", "1701", `
                "3268", "3269", "3389", "5985", "5986"
        }
        $ouiobject = Invoke-RestMethod https://standards-oui.ieee.org/oui/oui.csv | ConvertFrom-Csv
    } # Begin Close
    process {
        if ($LocalSubnets) {
            $ConnectedNetworks = Get-NetIPConfiguration -Detailed | Where-Object { $_.Netadapter.status -eq "up" }
            $results = @()
            foreach ($network in $ConnectedNetworks) {
                # Get Network DHCP Server
                $DHCPServer = (Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration | Where-Object { $_.IPAddress -eq $network.IPv4Address }).DHCPServer
                # Get Subnet as CIDR
                $Subnet = "$($network.IPv4DefaultGateway.nexthop)/$($network.IPv4Address.PrefixLength)"
                # Regex for IPV4 and IPV6 validation
                if (($subnet -match '^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$') -or ($subnet -match '^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$')) {
                    # Create Network Scan Object
                    $NetWorkScan = Invoke-PSnmap -ComputerName $subnet -Port $ports -Dns -NoSummary -AddService
                    # Filter devices that don't ping as no results will be found.
                    $scan = $NetworkScan | Where-Object { $_.Ping -eq $true }
                    Write-Verbose "##########################################"
                    Write-Verbose "Network scan for Subnet $Subnet completed."
                    Write-Verbose "DHCP Server: $($DHCPServer)"
                    Write-Verbose "Gateway: $($network.IPv4DefaultGateway.nexthop)"
                    Write-Verbose "##########################################"
                    $scan | ForEach-Object {
                        $org = ""
                        $macid = ((arp -a $_.ComputerName | Select-String '([0-9a-f]{2}-){5}[0-9a-f]{2}').Matches.Value).Replace("-", ":")
                        $macpop = $macid.replace(":", "")
                        $macsubstr = $macpop.Substring(0, 6)
                        $org = ($ouiobject | Where-Object { $_.assignment -eq $macsubstr })."Organization Name"
                        Add-Member -InputObject $_ -MemberType NoteProperty -Name MacID -Value $macid
                        if ($org) {
                            Add-Member -InputObject $_ -MemberType NoteProperty -Name ManufacturerName -Value $org
                        }
                        else {
                            Add-Member -InputObject $_ -MemberType NoteProperty -Name ManufacturerName -Value "Not Found"
                        }
                    }
                    # Normalize Subnet text for filename.
                    $subnetText = $(($subnet.Replace("/", ".CIDR.")))
                    # If report switch is true.
                    if ($report) {
                        $scan | Export-Csv "C:\temp\$((Get-Date).ToString('yyyy-MM-dd_hh.mm.ss')).$($env:USERDNSDOMAIN)_Subnet.$($subnetText)_DHCP.$($DHCPServer)_Gateway.$($network.IPv4DefaultGateway.nexthop).NetScan.csv" -NoTypeInformation
                    }
                    # Add scan to function output.
                    $results += $scan
                } # IF Subnet Match End
            } # End Foreach
        } # End If $LocalSubnets
        elseif ($Computers) {
            $Subnet = $Computers
            $results = Invoke-PSnmap -ComputerName $subnet -Port $ports -Dns -NoSummary -AddService | Where-Object { $_.Ping -eq $true }
            if ($Report) {
                $results | Export-Csv "C:\temp\$((Get-Date).ToString('yyyy-MM-dd_hh.mm.ss')).$($env:USERDNSDOMAIN)_HostScan.csv" -NoTypeInformation
            }
        }
    } # Process Close
    end {
        return $results
    }# End Close
}
#EndRegion '.\Public\Get-NetworkScan.ps1' 119
#Region '.\Public\Switch-SurnameWithGivenName.ps1' 0
function Switch-SurnameWithGivenName {
    <#
    .SYNOPSIS
        Takes CSV input as "LastName<space>FirstName" and flips it to "Firstname<space>Lastname"
    .DESCRIPTION
        Takes a CSV that was formatted as 'LastName, FirstName' with the comma and space removed, to 'FirstName Lastname'.
    .NOTES
        This function depends on the name column in the employee roster name column, to have been formatted in excel using a find and replace to replace ", " with " ".
        In other words: The file needs to have "comma space" replaces with "space" in the name column to be easily compared to ADUser output.
    .LINK
        Specify a URI to a help page, this will show when Get-Help -Online is used.
    .EXAMPLE
        Switch-SurnameWithGivenName -RosterCSV "C:\temp\RosterNameColumnFormattedLastNameSpaceFirstname.csv" -Verbose
    #>

    [CmdletBinding()]
    param (
        [Parameter(
            HelpMessage = 'Enter the full path to the csv file',
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            Position = 0
        )]
        [string]$RosterCSV,
        [Parameter(
            HelpMessage = 'Enter the folder path to the new csv file',
            ValueFromPipelineByPropertyName = $true,
            Position = 1
        )][string]$AttachmentFolder = "C:\temp\Switch-SurnameWithGivenName"
    )
    begin {
        $AttachmentFolderPathCheck = Test-Path -Path $AttachmentFolder
        If (!($AttachmentFolderPathCheck)) {
            # If not present then create the dir
            New-Item -ItemType Directory $AttachmentFolder -Force -ErrorAction Stop
        }
        $HRCSV = Import-Csv $RosterCSV
    }
    Process {
        $Export = @()
        foreach ($user in $HRCSV) {
            $a, $b = ($user.Name).split(' ')
            New-Object -TypeName PSCustomObject -Property @{
                NewName = "$b $a"
            } -OutVariable PSObject | Out-Null
            $Export += $PSObject
        }
    }
    end {
        $Export | Export-Csv "$($home)\Documents\$((Get-Date).ToString("yyyy.MM.dd hh.mm tt")).Switch-SurnameWithGivenName.csv" -NoTypeInformation
    }
}
#EndRegion '.\Public\Switch-SurnameWithGivenName.ps1' 52