2013/06/10

Scripting Games 2013 - Advanced Event 4 - An Auditing Adventure

This is my solution for the Advanced Event 4 of the Scripting Games 2013.
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
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.


Article about LastLogonTimeStamp and LastLogon properties :
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 [email protected] I invite you to follow me on Twitter: @lazywinadm

No comments:

Post a Comment