ADAuditTasks.psm1

#Region '.\Classes\1.ADAuditTasksUser.ps1' 0
class ADAuditTasksUser {
    [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
    ADAuditTasksUser(
        [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.ADAuditTasksUser.ps1' 65
#Region '.\Classes\2.ADAuditTasksComputer.ps1' 0
class ADAuditTasksComputer {
    [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
    ADAuditTasksComputer(
        [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-ADGroupMemberof -SamAccountName $GroupMemberships -AccountType ADComputer)
        $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
}
#EndRegion '.\Classes\2.ADAuditTasksComputer.ps1' 63
#Region '.\Private\Build-ReportArchive.ps1' 0
function Build-ReportArchive {
    [CmdletBinding()]
    param (
        [Parameter(
            HelpMessage = 'Active Directory User Enabled or not. Default $true',
            Position = 0,
            ValueFromPipelineByPropertyName = $true
        )]$Export,
        [Parameter(
            HelpMessage = 'CSV File Name',
            Position = 1,
            ValueFromPipelineByPropertyName = $true
        )][string]$csv,
        [Parameter(
            HelpMessage = 'Zip File Name',
            Position = 2,
            ValueFromPipelineByPropertyName = $true
        )][string]$zip,
        [Parameter(
            HelpMessage = 'Log File Name',
            Position = 3,
            ValueFromPipelineByPropertyName = $true
        )][string]$log
    )
    begin {
        $ExportFile = $Export
    }
    process {
        try {
            $ExportFile | Export-Csv $csv -NoTypeInformation -Encoding utf8 -ErrorVariable ExportErr -ErrorAction Stop
        }
        catch {
            $Script:ADLogString += Write-AuditLog -Message "Failed to export CSV: $csv" -Severity Error
            throw $ExportErr
        }
        $Sha256Hash = (Get-FileHash $csv).Hash
        $Script:ADLogString += Write-AuditLog -Message "Exported CSV SHA256 hash: "
        $Script:ADLogString += Write-AuditLog -Message "$($Sha256Hash)"
        $Script:ADLogString += Write-AuditLog -Message "Directory: $AttachmentFolderPath"
        $Script:ADLogString += Write-AuditLog -Message "FilePath: $zip"
        $Script:ADLogString | Export-Csv $log -NoTypeInformation -Encoding utf8
    }
    end {
        Compress-Archive -Path $csv, $log -DestinationPath $zip -CompressionLevel Optimal
        Remove-Item $csv, $log -Force
        return [string[]]$zip
    }
}
#EndRegion '.\Private\Build-ReportArchive.ps1' 49
#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,
        [ValidateSet("ADUser", "ADComputer")]
        [string]$AccountType = "ADUser"
    )
    process {
        switch ($AccountType) {
            "ADComputer" {
                $GroupStringArray = ((Get-ADComputer -Identity $SamAccountName -Properties memberof).memberof | Get-ADGroup | Select-Object name | Sort-Object name).name
                $GroupString = $GroupStringArray -join " | "
            }
            Default {
                $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' 22
#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) {
                $ADLogString += Write-AuditLog -Message $SendWinSCPErr -Severity Error
                $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\Test-IsAdmin.ps1' 0
function Test-IsAdmin {
    (New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
#EndRegion '.\Private\Test-IsAdmin.ps1' 4
#Region '.\Private\Write-ADAuditLog.ps1' 0
function Write-AuditLog {
    [OutputType([pscustomobject])]
    [CmdletBinding()]
    param(
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Input a Message string.',
            Position = 0
        )]
        [ValidateNotNullOrEmpty()]
        [string]$Message,
        [Parameter(
            HelpMessage = 'Information, Warning or Error.',
            Position = 1
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('Information', 'Warning', 'Error')]
        [string]$Severity = 'Information'
    )
    switch ($Severity) {
        'Warning' { Write-Warning $Message -WarningAction Inquire }
        'Error' { Write-Error $Message }
        Default { Write-Verbose $Message }
    }
    $ErrorActionPreference = "SilentlyContinue"
    $ModuleName = $Script:MyInvocation.MyCommand.Name -replace '\..*'
    $FuncName = (Get-PSCallStack)[1].Command
    $ModuleVer = $MyInvocation.MyCommand.Version.ToString()
    $ErrorActionPreference = "Continue"
    return [pscustomobject]@{
        Time      = ((Get-Date).ToString('yyyy-MM-dd hh:mmTss'))
        PSVersion = ($PSVersionTable.PSVersion).ToString()
        IsAdmin   = $(Test-IsAdmin)
        User      = "$Env:USERDOMAIN\$Env:USERNAME"
        HostName  = $Env:COMPUTERNAME
        InvokedBy = $( $ModuleName + "/" + $FuncName + '.v' + $ModuleVer )
        Severity  = $Severity
        Message   = $Message
    }
}
#EndRegion '.\Private\Write-ADAuditLog.ps1' 41
#Region '.\Public\Get-ADActiveUserAudit.ps1' 0
function Get-ADActiveUserAudit {
    <#
    .SYNOPSIS
        Gets active but stale AD User accounts that haven't logged in within the last 90 days by default.
    .DESCRIPTION
        Audit's Active Directory taking "days" as the input for how far back to check for a user's last sign in.
        Output can be piped to a csv manually, or automatically to C:\temp\ADActiveUserAudit or a specified path
        in "AttachmentFolderPath" using the -Report Switch.
 
        Any user account that is enabled and not signed in over 90 days is a candidate for removal.
    .EXAMPLE
        PS C:\> Get-ADActiveUserAudit
    .EXAMPLE
        PS C:\> Get-ADActiveUserAudit -Report -Verbose
    .EXAMPLE
        PS C:\> Get-ADActiveUserAudit -Enabled $false -DaysInactive 30 -AttachmentFolderPath "C:\temp\MyNewFolderName" -Report -Verbose
    .PARAMETER Report
        Add report output as csv to DirPath directory.
    .PARAMETER AttachmentFolderPath
        Default path is C:\temp\ADActiveUserAudit.
        This is the folder where attachments are going to be saved.
    .PARAMETER Enabled
        If "$false", will also search disabled users.
    .PARAMETER DaysInactive
        How far back in days to look for sign ins. Outside of this window, users are considered "Inactive"
    .NOTES
        Outputs to C:\temp\ADActiveUserAudit by default.
        For help type: help Get-ADActiveUserAudit -ShowWindow
    #>

    [OutputType([ADAuditTasksUser])]
    [CmdletBinding()]
    param (
        [Parameter(
            HelpMessage = 'Active Directory User Enabled or not. Default $true',
            Position = 0,
            ValueFromPipelineByPropertyName = $true
        )]
        [bool]$Enabled = $true,
        [Parameter(
            HelpMessage = 'Days back to check for recent sign in. Default: 90 days',
            Position = 1,
            ValueFromPipelineByPropertyName = $true
        )]
        [int]$DaysInactive = 90,
        [Parameter(
            HelpMessage = 'Enter output folder path. Default: C:\temp\ADActiveUserAudit',
            Position = 2,
            ValueFromPipeline = $true
        )]
        [string]$AttachmentFolderPath = "C:\temp\ADActiveUserAudit",
        [Parameter(
            HelpMessage = 'Switch to export output to a csv and zipped to Directory C:\temp. Default: $false',
            Position = 3,
            ValueFromPipelineByPropertyName = $true
        )]
        [switch]$Report
    )
    begin {
        #Create logging object
        $Script:ADLogString = @()
        #Begin Logging
        $Script:ADLogString += Write-AuditLog -Message "Begin Log"
        $ScriptFunctionName = $MyInvocation.MyCommand.Name -replace '\..*'
        $module = Get-Module -Name ActiveDirectory -ListAvailable -InformationAction SilentlyContinue
        if (-not $module) {
            $Script:ADLogString += Write-AuditLog -Message "Install Active Directory Module?" -Severity Warning
            try {
                Import-Module ServerManager -ErrorAction Stop -ErrorVariable InstallADModuleErr
                Add-WindowsFeature RSAT-AD-PowerShell -IncludeAllSubFeature -ErrorAction Stop -ErrorVariable InstallADModuleErr
            }
            catch {
                $Script:ADLogString += Write-AuditLog -Message "You must install the Active Directory module to continue" -Severity Error
                throw $InstallADModuleError
            }
        } # End If not Module
        try {
            Import-Module "ActiveDirectory" -Global -ErrorAction Stop -InformationAction SilentlyContinue -ErrorVariable ImportADModuleErr
        }
        catch {
            $Script:ADLogString += Write-AuditLog -Message "You must import the Active Directory module to continue" -Severity Error
            throw $ImportADModuleErr
        } # End Try Catch
        # Create Directory Path
        $AttachmentFolderPathCheck = Test-Path -Path $AttachmentFolderPath
        If (!($AttachmentFolderPathCheck)) {
            $Script:ADLogString += Write-AuditLog -Message "Would you like to create the directory $($AttachmentFolderPath)?" -Severity Warning
            Try {
                # If not present then create the dir
                New-Item -ItemType Directory $AttachmentFolderPath -Force -ErrorAction Stop
            }
            Catch {
                $Script:ADLogString += Write-AuditLog -Message $("Directory: " + $AttachmentFolderPath + "was not created.") -Severity Error
                $Script:ADLogString += Write-AuditLog -Message "End Log"
                throw $Script:ADLogString
            }
            $outputMsg = "$("Output Folder created at: `n" + $AttachmentFolderPath)"
            $Script:ADLogString += Write-AuditLog -Message $outputMsg
            Start-Sleep 2
        }
        # ADUser Properties to search for.
        $propsArray =
        "SamAccountName",
        "GivenName",
        "Surname",
        "Name",
        "UserPrincipalName",
        "LastLogonTimeStamp",
        "Enabled",
        "LastLogonTimeStamp",
        "DistinguishedName",
        "Title",
        "Manager",
        "Department"
        $Script:ADLogString += Write-AuditLog -Message "Retriving the following ADUser properties: "
        $Script:ADLogString += Write-AuditLog -Message "$($propsArray -join " | ")"
        # Establish timeframe to review.
        $time = (Get-Date).Adddays( - ($DaysInactive))
        $Script:ADLogString += Write-AuditLog -Message "Searching for users who have not signed in within the last $DaysInactive days."
        $Script:ADLogString += Write-AuditLog -Message "Where property Enabled = $Enabled"
        Start-Sleep 2
    }
    process {
        # Get Users
        Get-ADUser -Filter { LastLogonTimeStamp -lt $time -and Enabled -eq $Enabled } `
            -Properties $propsArray -OutVariable ADExport | Out-Null
        $Script:ADLogString += Write-AuditLog -Message "Creating a custom object from ADUser output."
        $Export = @()
        foreach ($item in $ADExport) {
            $Export += [ADAuditTasksUser]::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
            )
        }
    }
    end {
        $Script:ADLogString += Write-AuditLog -Message "The $ScriptFunctionName Export was successful."
        $Script:ADLogString += Write-AuditLog -Message "There are $($Export.Count) objects listed with the following properties: "
        $Script:ADLogString += Write-AuditLog -Message "$(($Export | Get-Member -MemberType property ).Name -join " | ")"
        if ($Report) {
            # Add Datetime to filename
            $ExportFileName = "$AttachmentFolderPath\$((Get-Date).ToString('yyyy-MM-dd_hh.mm.ss'))_$($ScriptFunctionName)_$($env:USERDNSDOMAIN)"
            # Create FileNames
            $csv = "$ExportFileName.csv"
            $zip = "$ExportFileName.zip"
            $hash = "$ExportFileName.csv.SHA256.txt"
            $log = "$ExportFileName.AuditLog.csv"
            Build-ReportArchive -Export $Export -csv $csv -zip $zip -hash $hash -log $log -ErrorAction SilentlyContinue -ErrorVariable BuildErr
        }
        else {
            $Script:ADLogString += Write-AuditLog -Message "Returning output object."
            Start-Sleep 2
            return $Export
        }
    }
}
#EndRegion '.\Public\Get-ADActiveUserAudit.ps1' 168
#Region '.\Public\Get-ADHostAudit.ps1' 0
function Get-ADHostAudit {
    <#
    .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\ADHostAudit or a specified path in
        "AttachmentFolderPath" using the -Report Switch.
 
            Use the Tab key to cycle through the -HostType Parameter.
    .EXAMPLE
        PS C:\> Get-ADHostAudit -HostType WindowsServers -Report -Verbose
    .EXAMPLE
        PS C:\> Get-ADHostAudit -HostType WindowsWorkstations -Report -Verbose
    .EXAMPLE
        PS C:\> Get-ADHostAudit -HostType "Non-Windows" -Report -Verbose
    .EXAMPLE
        PS C:\> Get-ADHostAudit -OSType "2008" -DirPath "C:\Temp\" -Report -Verbose
    .PARAMETER HostType
        Select from WindowsServers, WindowsWorkstations or Non-Windows.
    .PARAMETER OSType
        Search an OS String. There is no need to add wildcards.
    .PARAMETER DaystoConsiderAHostInactive
        How far back in days to look for sign ins. Outside of this window, hosts are considered "Inactive"
    .PARAMETER Report
        Add report output as csv to DirPath directory.
    .PARAMETER AttachmentFolderPath
        Default path is C:\temp\ADHostAudit.
        This is the folder where attachments are going to be saved.
    .PARAMETER Enabled
        If "$false", will also search disabled computers.
    .NOTES
        Outputs to C:\temp\ADHostAudit by default.
        For help type: help Get-ADHostAudit -ShowWindow
    #>

    [OutputType([pscustomobject])]
    [CmdletBinding(DefaultParameterSetName = 'HostType')]
    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]$AttachmentFolderPath = 'C:\temp\ADHostAudit',
        [Parameter(
            HelpMessage = 'Search for Enabled or Disabled hosts',
            ValueFromPipelineByPropertyName = $true
        )]
        [bool]$Enabled = $true
    )
    begin {
        # Create logging object
        $Script:ADLogString = @()
        # Begin Logging
        $Script:ADLogString += Write-AuditLog -Message "Begin Log"
        # Get the name of the script function
        $ScriptFunctionName = $MyInvocation.MyCommand.Name -replace '\..*'
        # Check if the Active Directory module is installed and install it if necessary
        $module = Get-Module -Name ActiveDirectory -ListAvailable -InformationAction SilentlyContinue
        if (-not $module) {
            $Script:ADLogString += Write-AuditLog -Message "Install Active Directory Module?" -Severity Warning
            try {
                Import-Module ServerManager -ErrorAction Stop -ErrorVariable InstallADModuleErr
                Add-WindowsFeature RSAT-AD-PowerShell -IncludeAllSubFeature -ErrorAction Stop -ErrorVariable InstallADModuleErr
            }
            catch {
                $Script:ADLogString += Write-AuditLog -Message "You must install the Active Directory module to continue" -Severity Error
                throw $InstallADModuleError
            }
        }
        try {
            Import-Module "ActiveDirectory" -Global -ErrorAction Stop -InformationAction SilentlyContinue -ErrorVariable ImportADModuleErr
        }
        catch {
            $Script:ADLogString += Write-AuditLog -Message "You must import the Active Directory module to continue" -Severity Error
            throw ImportADModuleErr
        }
        # Calculate the time that is considered a host inactive
        $time = (Get-Date).Adddays( - ($DaystoConsiderAHostInactive))
        # Check if the attachment folder exists and create it if it does not
        $AttachmentFolderPathCheck = Test-Path -Path $AttachmentFolderPath
        If (!($AttachmentFolderPathCheck)) {
            $Script:ADLogString += Write-AuditLog -Message "Would you like to create the directory $($AttachmentFolderPath)?" -Severity Warning
            Try {
                # If not present then create the dir
                New-Item -ItemType Directory $AttachmentFolderPath -Force -ErrorAction Stop -ErrorVariable CreateDirErr | Out-Null
            }
            Catch {
                $Script:ADLogString += Write-AuditLog -Message "Unable to create output directory $($AttachmentFolderPath)" -Severity Error
                throw $CreateDirErr
            }
        }
        # Determine the host type and set the appropriate search criteria
        switch ($PsCmdlet.ParameterSetName) {
            'HostType' {
                if ($HostType -eq "WindowsWorkstations") {
                    $FileSuffix = "Workstations"
                    $Script:ADLogString += Write-AuditLog -Message "###############################################"
                    $Script:ADLogString += Write-AuditLog -Message "Searching Windows Workstations......"
                    Start-Sleep 2
                }
                elseif ($HostType -eq "Non-Windows") {
                    $POSIX = $true
                    $FileSuffix = "Non-Windows"
                    $Script:ADLogString += Write-AuditLog -Message "###############################################"
                    $Script:ADLogString += Write-AuditLog -Message "Searching Non-Windows Computer Objects......"
                    Start-Sleep 2
                }
                elseif ($HostType -eq "WindowsServers") {
                    $OSPicked = "*Server*"
                    $FileSuffix = "Servers"
                    $Script:ADLogString += Write-AuditLog -Message "###############################################"
                    $Script:ADLogString += Write-AuditLog -Message "Searching Windows Servers......"
                    Start-Sleep 2
                }
            }
            'OSType' {
                $OSPicked = '*' + $OSType + '*'
                $FileSuffix = $OSType
                $Script:ADLogString += Write-AuditLog -Message "###############################################"
                $Script:ADLogString += Write-AuditLog -Message "Searching OSType $OsType......"
                Start-Sleep 2
            }
        }
        # Set the properties to retrieve for the host objects
        $propsArray = `
            "Created", `
            "Description", `
            "DNSHostName", `
            "Enabled", `
            "IPv4Address", `
            "IPv6Address", `
            "KerberosEncryptionType", `
            "lastLogonTimestamp", `
            "Name", `
            "OperatingSystem", `
            "DistinguishedName", `
            "servicePrincipalName", `
            "whenChanged"
    } # End Begin
    process {
        # Log the search criteria
        $Script:ADLogString += Write-AuditLog -Message "Searching computers that have logged in within the last $DaystoConsiderAHostInactive days."
        $Script:ADLogString += Write-AuditLog -Message "Where property Enabled = $Enabled"
        Start-Sleep 2
        # Determine the Active Directory computers to include in the report
        if ($OSPicked) {
            $Script:ADLogString += Write-AuditLog -Message "And Operating System is like: $OSPicked."
            $ActiveComputers = (Get-ADComputer -Filter { (LastLogonTimeStamp -gt $time) -and (Enabled -eq $Enabled) -and (OperatingSystem -like $OSPicked) }).Name
        }
        elseif ($POSIX) {
            $Script:ADLogString += Write-AuditLog -Message "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 {
            $Script:ADLogString += Write-AuditLog -Message "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
        }
        # Retrieve the selected properties for each Active Directory computer and store the results in an array
        $ADComps = @()
        foreach ($comp in $ActiveComputers) {
            Get-ADComputer -Identity $comp -Properties $propsArray | Select-Object $propsArray -OutVariable ADComp | Out-Null
            $ADComps += $ADComp
        } # End Foreach
        # Create a new object for each Active Directory computer with the selected properties and store the results in an array
        $ADCompExport = @()
        foreach ($item in $ADComps) {
            $ADCompExport += [ADAuditTasksComputer]::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
        # Convert the objects to PSCustomObjects and store the results in an array
        $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
            } # End Ordered Hash table
            New-Object -TypeName PSCustomObject -Property $hash -OutVariable PSObject | Out-Null
            $Export += $PSObject
        } # End foreach Comp in ADCompExport
    } # End Process
    end {
        # If there the export is not empty
        if ($Export) {
            # Create a message that lists the properties that were exported
            $ExportMembers = "Export: $(($Export | Get-Member -MemberType noteproperty ).Name -join " | ")"
            # Log a successful export message and list the exported properties and the number of objects exported
            $Script:ADLogString += Write-AuditLog -Message "The $ScriptFunctionName Export was successful."
            $Script:ADLogString += Write-AuditLog -Message "There are $($Export.Count) objects listed with the following properties: "
            $Script:ADLogString += Write-AuditLog -Message "$ExportMembers"
            # If the -Report switch is used, create a report archive and log the output
            if ($Report) {
                # Add Datetime to filename
                $ExportFileName = "$AttachmentFolderPath\$((Get-Date).ToString('yyyy-MM-dd_hh.mm.ss'))_$($ScriptFunctionName)_$($env:USERDNSDOMAIN)"
                # Create FileNames
                $csv = "$ExportFileName.$FileSuffix.csv"
                $zip = "$ExportFileName.$FileSuffix.zip"
                $log = "$ExportFileName.$FileSuffix.AuditLog.csv"
                Build-ReportArchive -Export $Export -csv $csv -zip $zip -log $log -ErrorVariable BuildErr
            }
            # If the -Report switch is not used, return the output object
            else {
                $Script:ADLogString += Write-AuditLog -Message "Returning output object."
                Start-Sleep 2
                return $Export
            }
        }
        else {
            # If there is no output, log message and create an audit log file
            $ExportFileName = "$AttachmentFolderPath\$((Get-Date).ToString('yyyy-MM-dd_hh.mm.ss'))_$($ScriptFunctionName)_$($env:USERDNSDOMAIN)"
            $log = "$ExportFileName.$FileSuffix.AuditLog.csv"
            $Script:ADLogString += Write-AuditLog "There is no output for the specified host type $FileSuffix"
            $Script:ADLogString | Export-Csv $log -NoTypeInformation -Encoding utf8

            # If the -Report switch is not used, return null
            if (-not $Report) {
                return $null
            }
            else {
                return $log
            }
        }
    } # End End
}
#EndRegion '.\Public\Get-ADHostAudit.ps1' 281
#Region '.\Public\Get-ADUserLogonAudit.ps1' 0
function Get-ADUserLogonAudit {
    <#
    .SYNOPSIS
    Takes SamAccountName as input to retrieve most recent LastLogon from all DC's and output as DateTime.
    .DESCRIPTION
    Will check if DC's are available for queries. Best run on PDC. To add: Verbose output of all datetime objects.
    .EXAMPLE
    Get-ADUsersLastLogon -SamAccountName "UserName"
    .PARAMETER SamAccountName
    The SamAccountName of the user being checked for LastLogon.
    #>

    [CmdletBinding()]
    [OutputType([datetime])]
    param (
        [Alias("Identity", "UserName", "Account")]
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Enter the SamAccountName',
            ValueFromPipeline = $true
        )]
        $SamAccountName
    )
    process {
        #Create logging object
        $ADLogString = @()
        #Begin Logging
        $DomainControllers = Get-ADDomainController -Filter { Name -like "*" }
        $Comps = $DomainControllers.name
        $Params = @{}
        $Params.ComputerName = @()
        $NoRemoteAccess = @{}
        $NoRemoteAccess.NoRemoteAccess = @()
        foreach ($comp in $comps) {
            $testRemoting = Test-WSMan -ComputerName $comp -ErrorAction SilentlyContinue
            if ($null -ne $testRemoting ) {
                $params.ComputerName += $comp
            }
            else {
                $NoRemoteAccess.NoRemoteAccess += $comp
            }
        }
        if ($params.ComputerName) {
            $ADLogString += Write-AuditLog -Message "The following DC's were available for WSMan:"
            $ADLogString += Write-AuditLog -Message "$($params.ComputerName)"
        }
        if ($NoRemoteAccess.NoRemoteAccess) {
            $ADLogString += Write-AuditLog -Message "The following DC's were unavailable and weren't included:"
            $ADLogString += Write-AuditLog -Message "$($NoRemoteAccess.NoRemoteAccess)"
        }
        $user = Get-ADUser -Identity $SamAccountName
        $time = 0
        $dt = @()
        foreach ($dc in $params.ComputerName) {
            $user | Get-ADObject -Server $dc -Properties lastLogon -OutVariable usertime -ErrorAction SilentlyContinue | Out-Null
            if ($usertime.LastLogon -gt $time) {
                $time = $usertime.LastLogon
            }
            $dt += [DateTime]::FromFileTime($time)
        }
        return ($dt | Sort-Object -Descending)[0]
    }
}
#EndRegion '.\Public\Get-ADUserLogonAudit.ps1' 63
#Region '.\Public\Get-ADUserPrivilegeAudit.ps1' 0
function Get-ADUserPrivilegeAudit {
    <#
    .SYNOPSIS
        Produces 3 object outputs: PrivilegedGroups, AdExtendedRights and possible service accounts.
    .DESCRIPTION
        Reports will be created in the C:\temp directory if the -Report Switch is used.
        To instantiate variables with the objects, provide 3 objects on the left side of the assignment:
            For Example: $a,$b,$c = Get-ADUserPrivilegeAudit -verbose
        The objects will be populated with PrivilegedGroups, AdExtendedRights and possible
        service accounts respectively.
    .EXAMPLE
        Get-ADUserPrivilegeAudit -Verbose
        Get the reports as three separate objects.
            To instantiate variables with the objects, provide 3 objects on the left side of the assignment:
                For Example: $a,$b,$c = Get-ADUserPrivilegeAudit -verbose
                The objects will be populated with PrivilegedGroups, AdExtendedRights and possible
                service accounts respectively.
    .EXAMPLE
        Get-ADUserPrivilegeAudit -Report -Verbose
            Will return 3 reports to the default temp directory in a single zip file.
    .PARAMETER AttachmentFolderPath
        The path of the folder you want to save attachments to. The default is:
            C:\temp\ADUserPrivilegeAudit
    .PARAMETER Report
        Add report output as csv to DirPath directory.
    #>



    [CmdletBinding()]
    param (
        [Parameter(
            HelpMessage = ' Enter output folder path. Default: C:\temp\ADUserPrivilegeAudit ',
            Position = 0,
            ValueFromPipeline = $true
        )]
        [string]$AttachmentFolderPath = 'C:\temp\ADUserPrivilegeAudit',
        [Parameter(
            HelpMessage = 'Switch to export output to a csv and zipped to Directory C:\temp\ADUserPrivilegeAudit Default: $false',
            Position = 1,
            ValueFromPipelineByPropertyName = $true
        )]
        [switch]$Report
    )
    begin {
        #Create logging object
        $ADLogString = @()
        #Begin Logging
        $ADLogString += Write-AuditLog -Message "Begin Log"
        $ScriptFunctionName = $MyInvocation.MyCommand.Name -replace '\..*'
        $module = Get-Module -Name ActiveDirectory -ListAvailable -InformationAction SilentlyContinue
        if (-not $module) {
            $ADLogString += Write-AuditLog -Message "Install Active Directory Module?" -Severity Warning
            try {
                Import-Module ServerManager -ErrorAction Stop -ErrorVariable InstallADModuleErr
                Add-WindowsFeature RSAT-AD-PowerShell -IncludeAllSubFeature -ErrorAction Stop -ErrorVariable InstallADModuleErr
            }
            catch {
                $ADLogString += Write-AuditLog -Message "You must install the Active Directory module to continue" -Severity Error
                throw $InstallADModuleError
            }
        } # End If not Module
        try {
            Import-Module "ActiveDirectory" -Global -ErrorAction Stop -InformationAction SilentlyContinue -ErrorVariable ImportADModuleErr
        }
        catch {
            $ADLogString += Write-AuditLog -Message "You must import the Active Directory module to continue" -Severity Error
            throw $ImportADModuleErr
        } # End Try Catch
        # Create Directory Path
        $AttachmentFolderPathCheck = Test-Path -Path $AttachmentFolderPath
        If (!($AttachmentFolderPathCheck)) {
            $ADLogString += Write-AuditLog -Message "Would you like to create the directory $($AttachmentFolderPath)?" -Severity Warning
            Try {
                # If not present then create the dir
                New-Item -ItemType Directory $AttachmentFolderPath -Force -ErrorAction Stop
            }
            Catch {
                $ADLogString += Write-AuditLog -Message $("Directory: " + $AttachmentFolderPath + "was not created.") -Severity Error
                $ADLogString += Write-AuditLog -Message "End Log"
                throw $ADLogString
            }
            $ADLogString += Write-AuditLog -Message "$("Output Folder created at: `n" + $AttachmentFolderPath)"
            Start-Sleep 2
        }
        $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 = @()
        # AD Groups to search for.
        $ADLogString += Write-AuditLog -Message "Retriving info from the following priveledged groups: "
        $ADLogString += Write-AuditLog -Message "$($AD_PrivilegedGroups -join " | ")"
        Start-Sleep 2
    }
    process {
        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
        }
        $ADLogString += Write-AuditLog -Message "The $ScriptFunctionName Export was successful."
        $ADLogString += Write-AuditLog -Message "There are $($Export.Count) objects listed with the following properties: "
        $ADLogString += Write-AuditLog -Message "$(($Export | Get-Member -MemberType noteproperty ).Name -join " | ")"
        # 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
        }
        $ADLogString += Write-AuditLog -Message "The Extended Permissions Export was successful."
        $ADLogString += Write-AuditLog -Message "There are $($Export2.Count) objects listed with the following properties: "
        $ADLogString += Write-AuditLog -Message "$(($Export2 | Get-Member -MemberType noteproperty ).Name -join " | ")"
        # 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' } }
        # Log Success
        $ADLogString += Write-AuditLog -Message "The delegated permissions Export was successful."
        $ADLogString += Write-AuditLog -Message "There are $($Export3.Count) objects listed with the following properties: "
        $ADLogString += Write-AuditLog -Message "$(($Export3 | Get-Member -MemberType noteproperty ).Name -join " | ")"
    }
    end {
        if ($Report) {
            # Add Datetime to filename
            $ExportFileName = "$AttachmentFolderPath\$((Get-Date).ToString('yyyy-MM-dd_hh.mm.ss'))_$($ScriptFunctionName)_$($env:USERDNSDOMAIN)"
            # Create FileNames
            $csv1 = "$ExportFileName.csv"
            $csv2 = "$ExportFileName.ExtendedPermissions.csv"
            $csv3 = "$ExportFileName.PossibleServiceAccounts.csv"
            $zip1 = "$ExportFileName.zip"
            $log = "$ExportFileName.AuditLog.csv"

            $Export | Export-Csv $csv1
            $Export2 | Export-Csv $csv2
            $Export3 | Export-Csv $csv3
            $csv1Sha256Hash = (Get-FileHash $csv1).Hash
            $csv2Sha256Hash = (Get-FileHash $csv2).Hash
            $csv3Sha256Hash = (Get-FileHash $csv3).Hash


            $ADLogString += Write-AuditLog -Message "Exported CSV $csv1 SHA256 hash: "
            $ADLogString += Write-AuditLog -Message "$($csv1Sha256Hash)"
            $ADLogString += Write-AuditLog -Message "Exported CSV $csv2 SHA256 hash: "
            $ADLogString += Write-AuditLog -Message "$($csv2Sha256Hash)"
            $ADLogString += Write-AuditLog -Message "Exported CSV $csv3 SHA256 hash: "
            $ADLogString += Write-AuditLog -Message "$($csv3Sha256Hash)"
            $ADLogString += Write-AuditLog -Message "Directory: $AttachmentFolderPath"
            $ADLogString += Write-AuditLog -Message "Returning string filepath of: "
            $ADLogString += Write-AuditLog -Message "FilePath: $zip1"


            $ADLogString | Export-Csv $log -NoTypeInformation -Encoding utf8

            Compress-Archive $csv1, $csv2, $csv3, $log -DestinationPath $zip1 -CompressionLevel Optimal
            Remove-Item $csv1, $csv2, $csv3, $log -Force
            return $zip1
        }
        else {
            $ADLogString += Write-AuditLog -Message "Returning 3 output objects. Create object like this: `$a, `$b, `$c, = Get-ADUserPrivilegedAudit"
            Start-Sleep 2
            return $Export, $Export2, $Export3
        }
    }
}
#EndRegion '.\Public\Get-ADUserPrivilegeAudit.ps1' 247
#Region '.\Public\Get-ADUserWildCardAudit.ps1' 0
function Get-ADUserWildCardAudit {
    <#
    .SYNOPSIS
        Takes a search string to find commonly named accounts.
    .DESCRIPTION
        Takes a search string to find commonly named accounts.
        For example:
            If you commonly name service accounts with the prefix "svc",
            Use "svc" for the WildCardIdentifier to search for names that contain "svc"
    .EXAMPLE
        Get-ADUserWildCardAudit -WildCardIdentifier "svc" -Report -Verbose
        Searches for all user accounts that are named like the search string "svc".
    .PARAMETER Report
        Add report output as csv to AttachmentFolderPath directory.
    .PARAMETER AttachmentFolderPath
        Default path is C:\temp\ADUserWildCardAudit.
        This is the folder where attachments are going to be saved.
    .PARAMETER Enabled
        If "$false", will also search disabled users.
    .PARAMETER DaysInactive
        How far back in days to look for sign ins. Outside of this window, users are considered "Inactive"
    .PARAMETER WildCardIdentifier
        The search string to look for in the name of the account. Case does not matter.
        Do not add a wildcard (*) as it will do this automatically.
    #>

    [OutputType([ADAuditTasksUser])]
    [CmdletBinding()]
    param (
        [Parameter(
            HelpMessage = 'Active Directory User Enabled or not. Default $true',
            Position = 0,
            ValueFromPipelineByPropertyName = $true
        )]
        [bool]$Enabled = $true,
        [Parameter(
            HelpMessage = 'Days back to check for recent sign in. Default: 90 days',
            Position = 1,
            ValueFromPipelineByPropertyName = $true
        )]
        [int]$DaysInactive = 90,
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Name filter attached to users.',
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$WildCardIdentifier,
        [Parameter(
            HelpMessage = 'Enter output folder path. Default: C:\temp\ADUserWildCardAudit',
            Position = 3,
            ValueFromPipeline = $true
        )]
        [string]$AttachmentFolderPath = "C:\temp\ADUserWildCardAudit",
        [Parameter(
            HelpMessage = 'Switch to export output to a csv and zipped to Directory C:\temp. Default: $false',
            Position = 4,
            ValueFromPipelineByPropertyName = $true
        )]
        [switch]$Report
    )
    begin {
        #Create logging object
        $Script:ADLogString = @()
        #Begin Logging
        $Script:ADLogString += Write-AuditLog -Message "Begin Log"
        $ScriptFunctionName = $MyInvocation.MyCommand.Name -replace '\..*'
        $module = Get-Module -Name ActiveDirectory -ListAvailable -InformationAction SilentlyContinue
        if (-not $module) {
            $Script:ADLogString += Write-AuditLog -Message "Install Active Directory Module?" -Severity Warning
            try {
                Import-Module ServerManager -ErrorAction Stop -ErrorVariable InstallADModuleErr
                Add-WindowsFeature RSAT-AD-PowerShell -IncludeAllSubFeature -ErrorAction Stop -ErrorVariable InstallADModuleErr
            }
            catch {
                $Script:ADLogString += Write-AuditLog -Message "You must install the Active Directory module to continue" -Severity Error
                throw $InstallADModuleError
            }
        } # End If not Module
        try {
            Import-Module "ActiveDirectory" -Global -ErrorAction Stop -InformationAction SilentlyContinue -ErrorVariable ImportADModuleErr
        }
        catch {
            $Script:ADLogString += Write-AuditLog -Message "You must import the Active Directory module to continue" -Severity Error
            throw $ImportADModuleErr
        } # End Try Catch
        # Create Directory Path
        $AttachmentFolderPathCheck = Test-Path -Path $AttachmentFolderPath
        If (!($AttachmentFolderPathCheck)) {
            $Script:ADLogString += Write-AuditLog -Message "Would you like to create the directory $($AttachmentFolderPath)?" -Severity Warning
            Try {

                # If not present then create the dir
                New-Item -ItemType Directory $AttachmentFolderPath -Force -ErrorAction Stop
            }
            Catch {
                $Script:ADLogString += Write-AuditLog -Message $("Directory: " + $AttachmentFolderPath + "was not created.") -Severity Error
                $Script:ADLogString += Write-AuditLog -Message "End Log"
                throw $Script:ADLogString
            }
            $Script:ADLogString += Write-AuditLog -Message "$("Output Folder created at: `n" + $AttachmentFolderPath)"
            Start-Sleep 2
        }
        # ADUser Properties to search for.
        $propsArray =
        "SamAccountName",
        "GivenName",
        "Surname",
        "Name",
        "UserPrincipalName",
        "LastLogonTimeStamp",
        "Enabled",
        "LastLogonTimeStamp",
        "DistinguishedName",
        "Title",
        "Manager",
        "Department"
        $Script:ADLogString += Write-AuditLog -Message "Retriving the following ADUser properties: "
        $Script:ADLogString += Write-AuditLog -Message "$($propsArray -join " | ")"
        # Establish timeframe to review.
        $Script:ADLogString += Write-AuditLog -Message "Searching for accounts using search string `"$WildCardIdentifier`" "
        Start-Sleep 2
    }
    process {
        # Get Users
        $WildCardIdentifierstring = '*' + $WildCardIdentifier + '*'
        Get-ADUser -Filter { Name -like $WildCardIdentifierstring } `
            -Properties $propsArray -OutVariable ADExport | Out-Null
        $Script:ADLogString += Write-AuditLog -Message "Creating a custom object from ADUser output."
        $Export = @()
        foreach ($item in $ADExport) {
            $Export += [ADAuditTasksUser]::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
            )
        }
    }
    end {
        $Script:ADLogString += Write-AuditLog -Message "The $ScriptFunctionName Export was successful."
        $Script:ADLogString += Write-AuditLog -Message "There are $($Export.Count) objects listed with the following properties: "
        $Script:ADLogString += Write-AuditLog -Message "$(($Export | Get-Member -MemberType property ).Name -join " | ")"
        if ($Report) {
            # Add Datetime to filename
            $ExportFileName = "$AttachmentFolderPath\$((Get-Date).ToString('yyyy-MM-dd_hh.mm.ss'))_$($ScriptFunctionName)_$($env:USERDNSDOMAIN)"
            # Create FileNames
            $csv = "$ExportFileName.csv"
            $zip = "$ExportFileName.zip"
            $log = "$ExportFileName.AuditLog.csv"
            Build-ReportArchive -Export $Export -csv $csv -zip $zip -log $log -ErrorVariable BuildErr
        }
        else {
            $Script:ADLogString += Write-AuditLog -Message "Returning output object."
            Start-Sleep 2
            return $Export
        }
    }
}
#EndRegion '.\Public\Get-ADUserWildCardAudit.ps1' 168
#Region '.\Public\Get-HostTag.ps1' 0
function Get-HostTag {
    <#
    .SYNOPSIS
        Creates a host name or tag based on predetermined criteria for as many as 999 hosts at a time.
    .DESCRIPTION
        A longer description of the function, its purpose, common use cases, etc.
    .EXAMPLE
        Get-HostTag -PhysicalOrVirtual Physical -Prefix "CSN" -SystemOS 'Windows Server' -DeviceFunction 'Application Server' -HostCount 5
            CSN-PWSVAPP001
            CSN-PWSVAPP002
            CSN-PWSVAPP003
            CSN-PWSVAPP004
            CSN-PWSVAPP005
        This creates the name of the host under 15 characters and numbers them. Prefix can be 2-3 characters.
    .PARAMETER PhysicalOrVirtual
        Tab through selections to add 'P' or 'V' for physical or virtual to host tag.
    .PARAMETER Prefix
        Enter the 2-3 letter prefix. Good for prefixing company initials, locations, or other.
    .PARAMETER SystemOS
        Use tab to cycle through the following options:
            "Cisco ASA", "Android", "Apple IOS",
            "Dell Storage Center", "MACOSX",
            "Dell Power Edge", "Embedded", "Embedded Firmware",
            "Cisco IOS", "Linux", "Qualys", "Citrix ADC (Netscaler)",
            "Windows Thin Client", "VMWare",
            "Nutanix", "TrueNas", "FreeNas",
            "ProxMox", "Windows Workstation", "Windows Server",
            "Windows Server Core", "Generic OS", "Generic HyperVisor"
    .PARAMETER DeviceFunction
        Use tab to cycle through the following options:
            "Application Server", "Backup Server", "Directory Server",
            "Email Server", "Firewall", "FTP Server",
            "Hypervisor", "File Server", "NAS File Server",
            "Power Distribution Unit", "Redundant Power Supply", "SAN Appliance",
            "SQL Server", "Uninteruptable Power Supply", "Web Server",
            "Management", "Blade Enclosure", "Blade Enclosure Switch",
            "SAN specific switch", "General server/Network switch", "Generic Function Device"
    .PARAMETER HostCount
        Enter a number from 1 to 999 for how many hostnames you'd like to create.
    #>

    [OutputType([string[]])]
    [CmdletBinding()]
    param (
        [Parameter(
            MandaTory = $true,
            Position = 0,
            HelpMessage = 'Enter 2 character site code or prefix for your devices',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("Physical", "Virtual")]
        [string]$PhysicalOrVirtual,
        [Parameter(
            MandaTory = $true,
            Position = 1,
            HelpMessage = 'Enter 2 to 3 character site code or prefix for your devices',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateLength(2, 3)]
        [string]$Prefix,
        [Parameter(
            MandaTory = $true,
            Position = 2,
            HelpMessage = 'Tab complete to pick from a list of System OSs',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet(
            "Cisco ASA", "Android", "Apple IOS",
            "Dell Storage Center", "MACOSX",
            "Dell Power Edge", "Embedded", "Embedded Firmware",
            "Cisco IOS", "Linux", "Qualys", "Citrix ADC (Netscaler)",
            "Windows Thin Client", "VMWare",
            "Nutanix", "TrueNas", "FreeNas",
            "ProxMox", "Windows Workstation", "Windows Server",
            "Windows Server Core", "Generic OS", "Generic HyperVisor"
        )]
        [string]$SystemOS,
        [Parameter(
            MandaTory = $true,
            Position = 3,
            HelpMessage = 'Tab complete to pick from a list of Device Functions',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet(
            "Application Server", "Backup Server", "Directory Server",
            "Email Server", "Firewall", "FTP Server",
            "Hypervisor", "File Server", "NAS File Server",
            "Power Distribution Unit", "Redundant Power Supply", "SAN Appliance",
            "SQL Server", "Uninteruptable Power Supply", "Web Server",
            "Management", "Blade Enclosure", "Blade Enclosure Switch",
            "SAN specific switch", "General server/Network switch", "Generic Function Device"
        )]
        [string]$DeviceFunction,
        [Parameter(
            Position = 4,
            HelpMessage = 'Enter the number of host names you want to create between 1 and 254',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateRange(1, 999)]
        [int]$HostCount = 1
    )
    begin {
        switch ($DeviceFunction) {
            "Application Server" { $DFunction = "APP" }
            "Backup Server" { $DFunction = "BAK" }
            "Directory Server" { $DFunction = "DIR" }
            "Email Server" { $DFunction = "EML" }
            "Firewall" { $DFunction = "FRW" }
            "FTP Server" { $DFunction = "FTP" }
            "Hypervisor" { $DFunction = "HYP" }
            "File Server" { $DFunction = "FIL" }
            "NAS File Server" { $DFunction = "NAS" }
            "Power Distribution Unit" { $DFunction = "PDU" }
            "Redundant Power Supply" { $DFunction = "RPS" }
            "SAN Appliance" { $DFunction = "SAN" }
            "SQL Server" { $DFunction = "SQL" }
            "Uninteruptable Power Supply" { $DFunction = "UPS" }
            "Web Server" { $DFunction = "WEB" }
            "Management" { $DFunction = "MGT" }
            "Blade Enclosure" { $DFunction = "BLDENC" }
            "Blade Enclosure Switch" { $DFunction = "SW-BLD" }
            "SAN specific Switch" { $DFunction = "SW-SAN" }
            "General Server/Network Switch" { $DFunction = "SW-SVR" }
            Default { $DFunction = "XDV" }
        }
        switch ($SystemOS) {
            "Cisco ASA" { $OSTxt = "ASA" }
            "Android" { $OSTxt = "DRD" }
            "Apple IOS" { $OSTxt = "IOS" }
            "Dell Storage Center" { $OSTxt = "DLS" }
            "MACOSX" { $OSTxt = "MAC" }
            "Dell Power Edge" { $OSTxt = "DPE" }
            "Embedded" { $OSTxt = "EMD" }
            "Embedded Firmware" { $OSTxt = "EFW" }
            "Cisco IOS" { $OSTxt = "COS" }
            "Linux" { $OSTxt = "NIX" }
            "Qualys" { $OSTxt = "QLS" }
            "Citrix ADC (Netscaler)" { $OSTxt = "ADC" }
            "Windows Thin Client" { $OSTxt = "WTC" }
            "VMWare" { $OSTxt = "VMW" }
            "Nutanix" { $OSTxt = "NTX" }
            "TrueNas" { $OSTxt = "FNS" }
            "FreeNas" { $OSTxt = "XDV" }
            "ProxMox" { $OSTxt = "PMX" }
            "Windows Workstation" { $OSTxt = "WWS" }
            "Windows Server" { $OSTxt = "WSV" }
            "Windows Server Core" { $OSTxt = "WSC" }
            "Generic OS" { $OSTxt = "GOS" }
            Default { $DFunction = "GHV" }
        }
        switch ($PhysicalOrVirtual) {
            "Physical" { $DevType = "P" }
            Default { $DevType = "V" }
        }
    }
    process {
        $OutPut = @()
        1..$HostCount | ForEach-Object {
            $CustomName = $Prefix + "-" + $DevType + $OSTxt + $DFunction + $('{0:d3}' -f [int]$_)
            $Output += $CustomName
        }
        # Create Device Name
    }
    end {
        return $Output
    }
}
#EndRegion '.\Public\Get-HostTag.ps1' 167
#Region '.\Public\Get-NetworkAudit.ps1' 0
function Get-NetworkAudit {
    <#
    .SYNOPSIS
        Discovers local network and runs port scans on all hosts found for specific or default sets of ports and displays MAC ID vendor info.
    .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. Adds MACID vendor info if found.
    .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-NetworkAudit -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.
    .PARAMETER LocalSubnets
        Specify this switch to automatically scan subnets on the local network of the scanning device.
        Will not scan outside of the hosting device's subnet.
    .PARAMETER Computers
        Scan single host or array of hosts using Subet ID in CIDR Notation, IP, NETBIOS, or FQDN in "quotes"'
        For Example:
            "10.11.1.0/24","10.11.2.0/24"
    #>

    [OutputType([pscustomobject])]
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    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
                    $NetworkAudit = Invoke-PSnmap -ComputerName $subnet -Port $ports -Dns -NoSummary -AddService
                    # Filter devices that don't ping as no results will be found.
                    $scan = $NetworkAudit | 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-NetworkAudit.ps1' 127
#Region '.\Public\Send-AuditEmail.ps1' 0
function Send-AuditEmail {
    <#
    .SYNOPSIS
    This is a wrapper function for Send-MailKitMessage and takes string arrays as input.
    .DESCRIPTION
    Other Audit tasks can be used as the -AttachmentFiles parameter when used with the report switch.
    .EXAMPLE
    Send-AuditEmail -SMTPServer "smtp.office365.com" -Port 587 -UserName "Username@contoso.com" `
    -From "Username@contoso.com" -To "user@anothercompany.com" -Pass (Read-Host -AsSecureString) -AttachmentFiles "$(Get-ADActiveUserAudit -Report)" -SSL
 
        This will automatically send the report zip via email to the parameters specified.
        There is no cleanup of files. Please cleanup the directory of zip's if neccessary.
    .EXAMPLE
    Send-AuditEmail -SMTPServer "smtp.office365.com" -Port 587 -UserName "Username@contoso.com" `
    -From "Username@contoso.com" -To "user@anothercompany.com" -AttachmentFiles "$(Get-ADActiveUserAudit -Report)" -FunctionApp "MyVaultFunctionApp" `
    -Function "MyClientSpecificFunction" -Token "ABCDEF123456" -SSL
 
        This will automatically send the report zip via email to the parameters specified.
        There is no cleanup of files. Please cleanup the directory of zip's if neccessary.
    .PARAMETER SMTPServer
        The SMTP Server address. For example: "smtp.office365.com"
    .PARAMETER AttachmentFiles
        The full filepath to the zip you are sending:
            -AttachmentFiles "C:\temp\ADHostAudit\2023-01-04_03.45.14_Get-ADHostAudit_AD.CONTOSO.COM.Servers.zip"
 
        The Audit reports output this filename if the "-Report" switch is used allowing it to be nested in this parameter
        for ease of automation.
    .PARAMETER Port
        The following ports can be used to send email:
            "993", "995", "587", "25"
    .PARAMETER UserName
        The Account authorized to send email via SMTP. From parameter is usually the same.
    .PARAMETER SSL
        Switch to ensure SSL is used during transport.
    .PARAMETER From
        This is who the email will appear to originate from. This is either the same as the UserName,
        or, if delegated, access to an email account the Username account has delegated permissions to send for.
        Link:
            https://learn.microsoft.com/en-us/microsoft-365/admin/add-users/give-mailbox-permissions-to-another-user?view=o365-worldwide
    .PARAMETER To
        This is the mailbox who will be the recipient of the communication.
    .PARAMETER Subject
        The subject is automatically populated with the name of the function that ran the script,
        as well as the domain and hostname.
 
        If you specify subject in the parameters, it will override the default with your subject.
    .PARAMETER Body
        The body of the message, pre-populates with the same data as the subject line. Specify body text
        in the function parameters to override.
    .PARAMETER Pass
        Takes a SecureString as input. The password must be added to the command by using:
            -Pass (Read-Host -AsSecureString)
            You will be promted to enter the password for the UserName parameter.
    .PARAMETER Function
        If you are using the optional function feature and created a password retrieval function,
        this is the name of the function in Azure AD that accesses the vault.
    .PARAMETER FunctionApp
        If you are using the optional function feature, this is the name of the function app in Azure AD.
    .PARAMETER Token
        If you are using the optional function feature, this is the api token for the specific function.
        Ensure you are using the "Function Key" and NOT the "Host Key" to ensure access is only to the specific funtion.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Pass')]
    param (
        [Parameter(
            MandaTory = $true,
            HelpMessage = 'Enter the Zip file paths as comma separated array with quotes for each filepath',
            ValueFromPipelineByPropertyName = $true
        )][string[]]$AttachmentFiles,
        [string]$SMTPServer,
        [Parameter(
            HelpMessage = 'Enter the port number for the mail relay',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("993", "995", "587", "25")]
        [int]$Port,
        [string]$UserName,
        [switch]$SSL,
        [string]$From,
        [string]$To,
        [string]$Subject = "$($script:MyInvocation.MyCommand.Name -replace '\..*') report ran for $($env:USERDNSDOMAIN) on host $($env:COMPUTERNAME).",
        [string]$Body = "$($script:MyInvocation.MyCommand.Name -replace '\..*') report ran for $($env:USERDNSDOMAIN) on host $($env:COMPUTERNAME).",
        [Parameter(
            ParameterSetName = 'Pass',
            HelpMessage = 'Enter this as the parameter: (Read-Host -AsSecureString)'
        )]
        [securestring]$Pass,
        [Parameter(
            ParameterSetName = 'Func',
            HelpMessage = 'Enter the name of the Function as showing in the function app'
        )]
        [string]$Function,
        [Parameter(
            ParameterSetName = 'Func',
            HelpMessage = 'Enter the name of the function app'
        )]
        [string]$FunctionApp,
        [Parameter(
            ParameterSetName = 'Func',
            HelpMessage = 'Enter the API key associated with the function. Not the Host Key.'
        )]
        [string]$Token
    )
    begin {
        $module = Get-Module -Name Send-MailKitMessage -ListAvailable
        if (-not $module) {
            Install-Module -Name Send-MailKitMessage -AllowPrerelease -Scope CurrentUser -Force
        }
        try {
            Import-Module "Send-MailKitMessage" -Global -ErrorAction STop -ErrorVariable MailkitErr | Out-Null
        }
        catch {
            # End run and log To file.
            $ADLogString += Write-AuditLog -Message "The Module Was not installed. Use `"Save-Module -Name Send-MailKitMessage -AllowPrerelease -Path C:\temp`" on another Windows Machine."
            $ADLogString += Write-AuditLog -Message "End Log" -Severity Error
            throw MailkitErr
        }
        # 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(' ')) )
        }
    }
    Process {
        $Parameters = @{
            "UseSecureConnectionIfAvailable" = $SSL
            "Credential"                     = $Credential
            "SMTPServer"                     = $SMTPServer
            "Port"                           = $Port
            "From"                           = $From
            "RecipientList"                  = $RecipientList
            "Subject"                        = $Subject
            "TextBody"                       = $Body
            "AttachmentList"                 = $AttachmentList
        }
        Send-MailKitMessage @Parameters
    }
    End {
        Clear-Variable -Name "a", "b", "Credential", "Token" -Scope Local -ErrorAction SilentlyContinue
    }
}
#EndRegion '.\Public\Send-AuditEmail.ps1' 161