This event was a bit challenging for me... In the past, I played with Quest Active Directory snap-in to create a bunch of Monitoring tools and some other small automation tasks, but that's about it. (Example Monitor Active Directory Groups membership change).
Let's see how I solved it.
Instruction
Download [Skydrive]
Dr. Scripto isn't saying he dislikes auditors, but they do seem to show up at the most inconvenient times, and with the oddest requests. So he's tossing this particular auditor request over to you.
This auditor would like a report that shows 20 randomly selected (well, as random as you can get) users from Active Directory. For each user, the auditor wants to see their user name, their department and title, and the last time they logged in. You also need to show the date their password was last changed, and whether or not the account is disabled or locked out. So that's seven pieces of information total. You're to put that information into an HTML-based report file, and the file must show the date and time that the report was generated. Please make sure that all of the dates are normal looking, human-readable dates and times.
Auditors being like they are, that "20 randomly" selected number will probably change in the future — so you'd better build this as a reusable tool. Have a parameter that specifies the number of users to pull, and default it to 20. Also parameterize the output HTML filename. Oh, but if the specified filename doesn't end in ".html" or reject it. Get PowerShell to do as much of that validation as possible — Dr. Scripto has to review your code, and doesn't want to read a lot of manual validation script.
A Domain Admin will always run the command (on behalf of the auditor), and the resulting HTML file will be manually e-mailed to the requesting auditor.
Solution
Dr. Scripto Requirements
- Random Active Directory User account(s)
- AD User Object properties :
- UserName
- Department
- Title
- Last time the user logged in
- When the Password was changed
- Is the account disabled
- Is the account locked-out
- HTML Report
- Show the date and time generated
- Human readable Format
- Reusable Tool
- Parameter for the number of users to pull (default should be 20)
- Parameter HTML Filename
- HTM and HTML extension only, other should be rejected.
- Validations
- Assume Domain Admin will always run it
Dr Scripto mentioned a last point about emailing the report to the auditor.
This could be part of our Function too, but I decided not to.
Querying the Active Directory
These are some of the ways to query AD (from what I know)
- ADSI (which stands for Active Directory Service Interfaces)
- Net Framework (via System.DirectoryServices Namespace)
- Active Directory Module
- Quest Active Directory Snapin
ADSI
It has clearly an advantage here since it does not require any installation or to be loaded, and It's really super fast. The only problem with this one is the usage. I feel that I need to spend a lot of time to figuring out how the language works...
I found some nice resources here: SelfADSI
Active Directory Module Quest Active Directory Snap-in, you will need to install and load them prior calling their respective Cmdlets.
Finally, I chose to use the Active Directory Module since it is easier to learn and widely used, available on most of Windows Client/Server versions.
Working with Active Directory Module
Installation
The Active Directory module can be installed as part of the RSAT feature from Windows 7 and 2008 R2 server (or by default, with the AD DS or AD LDS server roles.)
Import-Module ServerManager Add-WindowsFeature RSAT-AD-Tools
Loading the module
After the reboot, Load the Active Directory module from Windows Powershell using
Import-Module ActiveDirectory
Finding Active Directory User accounts
# Find the command available to manage AD user accounts Get-Command *user* -Module ActiveDirectory
# Show the standard output of Get-ADUser Get-ADUser -filter *
# Using Get-Member we see that not all the properties are returned Get-ADUser -filter * | Get-Member
Why are we only seeing a few properties here ? :-/
The documentation on Technet about Get-ADUser specify the following:
This cmdlet retrieves a default set of user object properties. To retrieve additional properties use the Properties parameter. For more information about the how to determine the properties for user objects, see the Properties parameter description.Properties parameter description:
Properties
Specifies the properties of the output object to retrieve from the server. Use this parameter to retrieve properties that are not included in the default set.
Specify properties for this parameter as a comma-separated list of names. To display all of the attributes that are set on the object, specify * (asterisk).
# Using the parameter properties with an asterisk Get-ADUser -identify admcatfx -properties *
Properties required by Dr Scripto
From the default output, only UserName (via "SamAccountName") and Disabled/Enabled information (via "Enabled") can be retrieved. The other required property need to be specified.
Department
Title
LastLogonDate
PasswordLastSet
Enabled
LockedOut
Get-ADUser -Identify xavierc -properties Department,Title,LastLogonDate,passwordlastset,lockedout
Note about LastLogon, LastLogonTimestamp or LastLogonDate property.
The reason why most people want to obtain these values is to find out if a user or computer object can be safely deleted. If you want to know when someone logged on to a computer in your network for the exact last time, search for the last logon value. If you want to know when someone last accessed a resource in your network (accessed webmail or one of your file systems etc), search for the last logon timestamp value. If you want to know when someone last used any network resource, search for the most recent of both values.
Random User
PowerShell comes with a Cmdlet called Get-Random since the version 2.
Check more information here Get-Random (TechNet) and PowerShell Team Blog.
This is exactly what I need!
Get-ADUser -filter * | Get-Random -Count 5
Building the function around the command
Function Format
I like to build my function that way.
It got everything I need: Help, BEGIN/PROCESS/BLOCK and Error Handling.
Error Handling
Help about Throw and Try/Catch/Finally
Article about Error Handling here.
What I missed ?
Check if Active Directory module is installed, I should add the following line
#Requires -Modules ActiveDirectory
Out-File Error Handling
I got some comments on the fact that I did not put Error Handling on the Out-File Cmdlet, which is part of the error handling itself in my script...see below the part Highlighted
TRY{...}
CATCH{ # ERROR HANDLING $Everything_is_OK = $false Write-Warning -Message "Wow! Something Went wrong !" Write-Warning -Message "$($_.Exception.Message)" IF ($PSBoundParameters['ErrorLog']) { $GetADUserError | Out-file -FilePath $ErrorLog -Append -ErrorAction Continue $GetRandomError | Out-file -FilePath $ErrorLog -Append -ErrorAction Continue Write-Warning -Message "Logged in $ErrorLog"} }#CATCH
I could add an extra layer...I guess... see below the part Highlighted
TRY{...}
CATCH{ # ERROR HANDLING $Everything_is_OK = $false Write-Warning -Message "Wow! Something Went wrong !" Write-Warning -Message "$($_.Exception.Message)" IF ($PSBoundParameters['ErrorLog']) { TRY{ $GetADUserError | Out-file -FilePath $ErrorLog -Append -ErrorAction Continue $GetRandomError | Out-file -FilePath $ErrorLog -Append -ErrorAction Continue Write-Warning -Message "Logged in $ErrorLog" } CATCH{ Write-Warning -Message "Couldn't write in $ErrorLog" }#CATCH ErrorLog File
}#IF PSBoundParameters ErrorLog }#CATCH
Final Output
Script
#requires -Version 3 function Get-ADUserAudit { <# .SYNOPSIS The function Get-ADUserAudit reports random user account(s) from the current Active Directory to a HTML File. .DESCRIPTION The function Get-ADUserAudit reports random user account(s) from the current Active Directory to a HTML File. Properties returned are SamAccountName (Renamed 'UserName' in the final output), Department, Title, PasswordLastSet (Date Value reformatted in the final output), LockedOut, lastlogonDate (Date Value reformatted in the final output) and Enabled (Renamed to 'Disabled' in the final report and reverse the output, this is to match the instruction requirements of Dr. Scripto) PowerShell version 3 is required to take advantage of PSDefaultValue. Function tested against a Domain Windows Server 2012 functional level. .PARAMETER Count Specifies the number of user account(s) to return in the report Default is 20. .PARAMETER HTMLfile Specifies the full path of the HTML Report file. The file must finish by the HTML or HTM extention. .PARAMETER SearchBase Specifies the SearchBase, DistinguishedName of the Organizational Unit root where the search will start. Can be useful for large Active Directory Database and if your Auditors want a specific OU. .PARAMETER ResultSetSize Specifies the maximum number of users to return for an Active Directory Domain Services query. Can be useful for large Active Directory Database. .PARAMETER Credential Specifies the credential if different from the current user .PARAMETER ErrorLog Specifies the full path of the Error log file. .EXAMPLE Get-ADUserAudit -HTMLfile Report.htm This example report information about random user account(s) from the Active Directory. By Default, 20 random user account(s) will be returned. The report will be Saved in the current directory. .EXAMPLE Get-ADUserAudit -HTMLfile D:\Report.htm -SearchBase "OU=TEST,DC=FX,DC=lab" This example report information about random user account(s) from the Active Directory, from the Organizational Unit: TEST (Distinguished name: "OU=TEST,DC=FX,DC=lab"). This will report also any user account(s) in the Child Organizational Units. By Default, 20 random user account(s) will be returned. In this example the report is saved under d:\report.htm using the parameter HTMLfile .EXAMPLE Get-ADUserAudit -HTMLfile D:\Report.htm -SearchBase "OU=TEST,DC=FX,DC=lab" -ErrorLog ErrorsAudit.log This example report information about random user account(s) from the Active Directory, from the Organizational Unit: TEST (Distinguished name: "OU=TEST,DC=FX,DC=lab"). This will report also any user account(s) in the Child Organizational Units. By Default, 20 random user account(s) will be returned. In this example the report is saved under d:\report.htm using the parameter HTMLfile Errors will be logged in ErrorsAudit.log in the current directory. .EXAMPLE Get-ADUserAudit -count 10 -HTMLfile D:\Report.htm -ResultSetSize 30 This example report information about 10 random user account(s) from the Active Directory, Note that the parameter ResultSetSize will limit the query to 30 user accounts. In this example the report is saved under d:\report.htm using the parameter HTMLfile. .INPUTS String Int32 PSCredential .OUTPUTS HTML/HTM File .NOTES Scripting Games 2013 - Advanced Event #4 #> [CmdletBinding()] PARAM( [PSDefaultValue(Help='Number of random Active Directory User Account(s) to output')] [Int]$Count = 20, [Parameter(Mandatory,HelpMessage = "FullPath to HTML file (must end by .HTML or .HTM else it will be rejected)")] [PSDefaultValue(Help='FullPath to HTML file. (Extension must be HTML or HTM')] [Alias("SaveAs")] [ValidateScript({($_ -like "*.html") -or ($_ -like "*.htm")} )] [String]$HTMLFile, [PSDefaultValue(Help='Enter the Active Directory DistinguishedName SearchBase of the Organizational Unit')] [Alias("DN","DistinguishedName")] [String]$SearchBase, [PSDefaultValue(Help='Specifies the maximum number of users to return for an Active Directory Domain Services query')] [Alias("Max")] [Int]$ResultSetSize, [Alias("RunAs")] [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, [PSDefaultValue(Help='FullPath to Error log file.')] [ValidateScript({Test-path -Path $_ -IsValid})] [String]$ErrorLog )#Param BEGIN{ # Load the Active Directory module if not already loaded if (-not(Get-Module -Name ActiveDirectory)){ Write-Verbose -Message "Loading ActiveDirectory Module..." Import-Module -Name ActiveDirectory -Force} }#BEGIN BLOCK PROCESS{ TRY{ $Everything_is_OK = $true # Get-AdUser Splatting Parameters $ADUserParams = @{ Filter = "*" Properties = "SamAccountName","Department","Title","PasswordLastSet","LockedOut","lastlogonDate","Enabled" ErrorAction= "Stop" ErrorVariable="GetADUserError" } # Get-AdUser Splatting Parameters - If Specified by the user, Adding the Credential to $ADUserParams IF ($PSBoundParameters['Credential']) { $ADUserParams.credential = $Credential} # Get-AdUser Splatting Parameters - If Specified by the user, Adding the SearchBase to $ADUserParams IF ($PSBoundParameters['SearchBase']) { $ADUserParams.SearchBase = $SearchBase} # Get-AdUser Splatting Parameters - If Specified by the user, Adding the ResultSetSize to $ADUserParams IF ($PSBoundParameters['ResultSetSize']) { $ADUserParams.ResultSetSize = $ResultSetSize} # Querying AD Users Write-Verbose -Message "Querying Active Directory Users and getting $Count random accounts" $RandomUsers = Get-ADUser @ADUserParams | Get-Random -Count $Count -ErrorAction Stop -ErrorVariable GetRandomError }#TRY CATCH{ # ERROR HANDLING $Everything_is_OK = $false Write-Warning -Message "Wow! Something Went wrong !" Write-Warning -Message "$($_.Exception.Message)" IF ($PSBoundParameters['ErrorLog']) { $GetADUserError | Out-file -FilePath $ErrorLog -Append -ErrorAction Continue $GetRandomError | Out-file -FilePath $ErrorLog -Append -ErrorAction Continue Write-Warning -Message "Logged in $ErrorLog"} }#CATCH IF ($Everything_is_OK){ # FORMATTING DATA Write-Verbose -Message "Formatting the data..." $report = $RandomUsers | Select-Object -Property @{Label="UserName";Expression={$_.SamAccountName}}, Department, Title, @{Label="LastLogonDate";Expression={'{0:yyyy/MM/dd H:mm:ss}' -f ($_.LastLogonDate)}}, @{Label="PasswordLastSet";Expression={'{0:yyyy/MM/dd H:mm:ss}' -f ($_.PasswordLastSet)}}, @{Label="Disabled";Expression={if($_.Enabled -eq $true){$false}else{$true}}}, LockedOut | ConvertTo-Html -Fragment # HTML Style (using Here String) Write-Verbose -Message "Adding some HTML style..." $style = @" <style> body { color:#333333; font-family:Calibri,Tahoma; font-size: 10pt; } h1 { text-align:center; } h2 { border-top:1px solid #666666; } th { font-weight:bold; color:#eeeeee; background-color:#333333; } .odd { background-color:#ffffff; } .even { background-color:#dddddd; } </style> "@ # HTML REPORT - Splatting information, inserting the HTML $Style and $report data. $HTMLParams = @{ Head = "<title>Active Directory Users Audit</title><h2>Random $Count Active Directory User Account(s)</h2>$style" As = "Table" PostContent = "$Report<br><hr><br>Generated by $env:UserDomain\$env:UserName on $('{0:yyyy/MM/dd H:mm:ss}' -f (Get-Date))" } # GENERATING FINAL REPORT Write-Verbose -Message "Generating our final HTML report and outputting to: $HTMLFile" ConvertTo-Html @htmlparams | Out-File -FilePath $HTMLFile }#IF $Everything_is_OK }#PROCESS BLOCK END{ Write-Verbose -Message "Script Completed!" }#END BLOCK }# Function Get-ADUserAudit
Thanks for Reading! If you have any questions, leave a comment or send me an email at fxcat@lazywinadmin.com. I invite you to follow me on Twitter: @lazywinadm
No comments:
Post a Comment