ADDSActiveAccountAudit.psm1
#Region '.\Private\Get-TimeStamp.ps1' 0 function Get-TimeStamp{ return "[{0:yyyy/MM/dd} {0:HH:mm:ss}]" -f (Get-Date) } #EndRegion '.\Private\Get-TimeStamp.ps1' 4 #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 = "ADDSActiveAccountAudit for $($env:USERDNSDOMAIN).", [string]$attachmentfilePath, [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() $AttachmentList.Add("$attachmentfilePath") # 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' 71 #Region '.\Private\Write-Log.ps1' 0 function Write-Log { <# .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-Log -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, 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, ParameterSetName = 'LogToFile', Position = 0 )] [string]$LogOutputPath ) process { if ($Begin) { $script:Logs = "" $TSLogString = "$(Get-TimeStamp) Begin Log for Script: $($MyInvocation.MyCommand) `n" return $script:Logs += $TSLogString } elseif ($End) { $TSLogString = "$(Get-TimeStamp) End Log for Script: $($MyInvocation.MyCommand) `n" return $script:Logs += $TSLogString } elseif ($LogOutputPath) { Add-Content $LogOutputPath\ADDSAuditLog.log -Value $script:Logs } else { $TSLogString = "$(Get-TimeStamp) $logstring `n" Write-Output $TSLogString return $script:Logs += $TSLogString } } } #EndRegion '.\Private\Write-Log.ps1' 65 #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. .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 .PARAMETER LocalDisk Only output data to local disk. .PARAMETER AttachementFolderPath Default path is C:\temp\ADDSActiveAccountAuditLogs. This is the folder where attachments are going to be saved. .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 SendMailMessage Adds parameters for sending Audit Report as an Email. .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 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')] 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 = '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 = 'LocalDisk', HelpMessage = 'Enter output folder path', ValueFromPipeline = $true )] [string]$AttachementFolderPath = "C:\temp\ADDSActiveAccountAuditLogs", [Parameter(ParameterSetName = 'FunctionApp')] [Parameter(ParameterSetName = 'SendMailMessage')] [Parameter( ParameterSetName = 'LocalDisk', HelpMessage = 'Active Directory User Enabled or not', ValueFromPipelineByPropertyName = $true )] [switch]$ADDSAccountIsNotEnabled, [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 = 'FunctionApp')] [Parameter( ParameterSetName = 'SendMailMessage', Mandatory = $true, HelpMessage = 'Enter the Sending Account UPN Ex:"user@contoso.com"', 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]$UserName, [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( HelpMessage = 'Clean Modules and output path', ValueFromPipelineByPropertyName = $true )] [switch]$Clean ) Begin { # Create Directory Path $DirPath = $AttachementFolderPath $DirPathCheck = Test-Path -Path $DirPath If (!($DirPathCheck)) { Try { #If not present then create the dir New-Item -ItemType Directory $DirPath -Force } Catch { Write-Log -Begin Write-Log "Directory: $DirPath was not created." Write-Log $Error[0] Write-Log -End throw $Error[0] } } # Begin Logging to $script:Logs Write-Log -Begin $LogOutputPath = "$AttachementFolderPath\$((Get-Date).ToString("yyyy-MM-dd-hh.mm.ss"))_ADDSAuditLog.log" Write-Log "Log output path: $LogOutputPath" # 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 } catch { Write-Log "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-Log -End Write-Log $Error[0] Write-Log -LogtoFile throw $Error[0] } # If SendMailMessage Begin if ($SendMailMessage) { # 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 } catch { Write-Log "The Module Was not installed. Use `"Save-Module -Name Send-MailKitMessage -AllowPrerelease -Path C:\temp`" on another Windows Machine." Write-Log $Error[0] Write-Log -End Write-Log -LogOutputPath $LogOutputPath throw $Error[0] } } if ($ADDSAccountIsNotEnabled) { $Enabled = $false } else { $Enabled = $true } } Process { # Establish timeframe to review. $time = (Get-Date).Adddays( - ($DaysInactive)) # Add Datetime to filename $csvFileName = "$attachementfolderpath\AD_Export_$($env:USERDNSDOMAIN).$((Get-Date).ToString('yyyy-MM-dd.hh.mm.ss'))" # Create FileNames $csv = "$csvFileName.csv" $zip = "$csvFileName.zip" Write-Log "Searching for users who have 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. try { Get-aduser -Filter { LastLogonTimeStamp -lt $time -and Enabled -eq $Enabled } -Properties ` GivenName, Surname, Mail, UserPrincipalName, Title, Description, Manager, lastlogontimestamp, samaccountname, DistinguishedName | ` Select-Object ` @{N = 'FirstName'; E = { $_.GivenName } }, ` @{N = 'LastName'; E = { $_.Surname } }, ` @{N = 'UserName'; E = { $_.samaccountname } }, ` @{N = "Last Sign-in"; E = { [DateTime]::FromFileTime($_.lastLogonTimestamp).ToString('yyyy-MM-dd.hh.mm') } }, ` @{N = 'UPN'; E = { $_.UserPrincipalName } }, ` @{N = 'OrgUnit'; E = { $_.DistinguishedName -replace '^.*?,(?=[A-Z]{2}=)' } } ` @{N = 'Job Title'; E = { $_.Title } }, ` $_.Description, ` @{N = 'Manager'; E = { (Get-ADUser ($_.Manager)).name } }, ` -Verbose -OutVariable Export } catch { Write-Log $Error[0] Write-Log -End Write-Log -LogOutputPath $LogOutputPath throw $Error[0] } try { $Export | Export-CSV -Path "$csv" -NoTypeInformation } catch { Write-Log $Error[0] Write-Log -End Write-Log -LogOutputPath $LogOutputPath throw $Error[0] } Compress-Archive -Path "$csv" -DestinationPath "$zip" -Verbose Write-Log [0] } End { if ($SendMailMessage) { if ($Password) { <# Send Attachement using O365 email account and password. Must exclude from conditional access legacy authentication policies. #> Send-AuditEmail -smtpServer $SMTPServer -port $Port -username $Username ` -body $script:Logs -pass $Password -from $From -to $to -attachmentfilePath "$zip" -ssl $Password.Dispose() } # End if else { Send-AuditEmail -smtpServer $SMTPServer -port $Port -username $Username ` -body $script:Logs -from $From -to $to -attachmentfilePath "$zip" -ssl } } elseif ($FunctionApp) { <# Send Attachement using O365 email account and Keyvault retrived password. Must exclude email account from conditional access legacy authentication policies. #> Write-Log "Email Generated @ $(Get-Date)" Write-Log -End Send-AuditEmail -smtpServer $SMTPServer -port $Port -username $Username ` -body $Script:Logs -Function $Function -FunctionApp $FunctionApp -token $ApiToken -from $from -to $to -attachmentfilePath "$zip" -ssl } elseif ($LocalDisk) { #Confirm output path to console. Write-Log "The archive have been saved to: `n" Write-Log "$zip" } if ($Clean) { try { # Remove Modules Write-Log "Removing Send-MailKitMessage Module" Remove-Module -Name "Send-MailKitMessage" -Force -Confirm:$false } catch { Write-Log "Error removing Send-MailKitMessage Module" Write-Log $Error[0] } try { # Uninstall Modules Write-Log "Uninstalling Send-MailKitMessage Module" Uninstall-Module -Name "Send-MailKitMessage" -AllowPrerelease -Force -Confirm:$false } catch { Write-Log "Error uninstalling Send-MailKitMessage Module" Write-Log $Error[0] } if (!($LocalDisk)) { try { Remove-Item -Path $DirPathCheck -Recurse } catch { Write-Log "Directory Cleanup error!" Write-Log $Error[0] throw $Error[0] } } } Clear-Variable -Name "Function", "FunctionApp", "ApiToken" -Scope Local # End Logging Write-Log -End #If Log switch for future. Write-Log -LogOutputPath $LogOutputPath } } #EndRegion '.\Public\Get-ADDSActiveAccountAudit.ps1' 342 |