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 = 'Hash File Name', Position = 3, ValueFromPipelineByPropertyName = $true )][string]$hash, [Parameter( HelpMessage = 'Log File Name', Position = 4, 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 $Sha256Hash | Out-File $hash -Encoding utf8 $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, $hash, $log -DestinationPath $zip -CompressionLevel Optimal Remove-Item $csv, $hash, $log -Force return [string[]]$zip } } #EndRegion '.\Private\Build-ReportArchive.ps1' 55 #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" $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 } } 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 } $time = (Get-Date).Adddays( - ($DaystoConsiderAHostInactive)) $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 } } 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 } } $propsArray = ` "Created", ` "Description", ` "DNSHostName", ` "Enabled", ` "IPv4Address", ` "IPv6Address", ` "KerberosEncryptionType", ` "lastLogonTimestamp", ` "Name", ` "OperatingSystem", ` "DistinguishedName", ` "servicePrincipalName", ` "whenChanged" } # End Begin process { $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 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 } $ADComps = @() foreach ($comp in $ActiveComputers) { Get-ADComputer -Identity $comp -Properties $propsArray | Select-Object $propsArray -OutVariable ADComp | Out-Null $ADComps += $ADComp } # End Foreach $ADCompExport = @() foreach ($item in $ADComps) { $ADCompExport += [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 $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 { $ExportMembers = "Export: $(($Export | Get-Member -MemberType noteproperty ).Name -join " | ")" $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 ($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" $hash = "$ExportFileName.$FileSuffix.csv.SHA256.txt" $log = "$ExportFileName.$FileSuffix.AuditLog.csv" Build-ReportArchive -Export $Export -csv $csv -zip $zip -hash $hash -log $log -ErrorVariable BuildErr } else { $Script:ADLogString += Write-AuditLog -Message "Returning output object." Start-Sleep 2 return $Export } } # End End } #EndRegion '.\Public\Get-ADHostAudit.ps1' 249 #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" $hash1 = "$ExportFileName.csv.SHA256.txt" $hash2 = "$ExportFileName.ExtendedPermissions.csv.SHA256.txt" $hash3 = "$ExportFileName.PossibleServiceAccounts.csv.SHA256.txt" $log = "$ExportFileName.AuditLog.csv" $Export | Export-Csv $csv1 $Export2 | Export-Csv $csv2 $Export3 | Export-Csv $csv3 $csv1Sha256Hash = (Get-FileHash $csv1).Hash $csv1Sha256Hash | Out-File $hash1 -Encoding utf8 $csv2Sha256Hash = (Get-FileHash $csv2).Hash $csv2Sha256Hash | Out-File $hash2 -Encoding utf8 $csv3Sha256Hash = (Get-FileHash $csv3).Hash $csv3Sha256Hash | Out-File $hash3 -Encoding utf8 $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, $hash1, $hash2, $hash3, $log -DestinationPath $zip1 -CompressionLevel Optimal Remove-Item $csv1, $csv2, $csv3, $hash1, $hash2, $hash3, $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' 252 #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" $hash = "$ExportFileName.csv.SHA256.txt" $log = "$ExportFileName.AuditLog.csv" Build-ReportArchive -Export $Export -csv $csv -zip $zip -hash $hash -log $log -ErrorVariable BuildErr } else { $Script:ADLogString += Write-AuditLog -Message "Returning output object." Start-Sleep 2 return $Export } } } #EndRegion '.\Public\Get-ADUserWildCardAudit.ps1' 169 #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 |