<# Get-AD User Activity Report (single-DC, PS 5.1 safe) - Queries users at/under a specified OU DN - No -Server parameter - Creates hidden output folders (userprofile\Scripts\Output-Reports) unless OutCsv is provided - Outputs to screen + CSV #> [CmdletBinding()] param( # Pass a full DN (recommended), e.g. "OU=Asana-Onsite,DC=asana,DC=local". [Parameter(Mandatory=$false)] [string]$SearchBase, # Optional: Output CSV path. If omitted, a timestamped file is written to %USERPROFILE%\Scripts\Output-Reports (hidden). [string]$OutCsv, # Include disabled accounts too [switch]$IncludeDisabled ) # ---------- Helpers ---------- function Format-DateLocal { param([Nullable[DateTime]]$Value, [string]$Fallback = "N/A") if ($null -eq $Value) { return $Fallback } try { return $Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") } catch { return $Fallback } } function From-FileTimeLocal { param([long]$Value, [string]$Fallback = "Never Logged In") if ($Value -eq 0) { return $Fallback } try { return ([DateTime]::FromFileTime($Value)).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") } catch { return $Fallback } } function Ensure-Folder { param( [Parameter(Mandatory)][string]$Path, [switch]$Hidden, [switch]$System ) if (-not (Test-Path -LiteralPath $Path)) { New-Item -Path $Path -ItemType Directory -Force | Out-Null } $di = Get-Item -LiteralPath $Path if ($Hidden) { $di.Attributes = $di.Attributes -bor [System.IO.FileAttributes]::Hidden } if ($System) { $di.Attributes = $di.Attributes -bor [System.IO.FileAttributes]::System } return $di.FullName } # --- Interactive builder if SearchBase not supplied --- if (-not $SearchBase) { Write-Host "Build the SearchBase DN..." $baseOU = Read-Host -Prompt "OU path (e.g. OU=TopOU,OU=SubOU)" $domainNameSolo = Read-Host -Prompt "Domain name (no TLD), e.g. 'turnernet'" $domainTLD = Read-Host -Prompt "TLD only, e.g. 'co'" $SearchBase = "$baseOU,DC=$domainNameSolo,DC=$domainTLD" } # ---------- Output path ---------- Import-Module ActiveDirectory -ErrorAction Stop $domainName = (Get-ADDomain).NetBIOSName $ts = Get-Date -Format "yyyyMMdd-HHmmss" if (-not $OutCsv) { $root = "$($env:SystemDrive)\vip" $scriptsDir = Join-Path $root 'Scripts' $reportsDir = Join-Path $scriptsDir 'Output-Reports' Ensure-Folder -Path $root -Hidden | Out-Null Ensure-Folder -Path $scriptsDir | Out-Null Ensure-Folder -Path $reportsDir | Out-Null $OutCsv = Join-Path $reportsDir "$domainName-DomainUsersReport-$ts.csv" } else { # If user provided a path, ensure parent exists (not hidden by default) $parent = Split-Path -Parent $OutCsv if ($parent -and -not (Test-Path -LiteralPath $parent)) { Ensure-Folder -Path $parent | Out-Null } } # ---------- Basic checks ---------- try { # Validate OU exists (use -Identity; no -Server anywhere) $null = Get-ADOrganizationalUnit -Identity $SearchBase -ErrorAction Stop } catch { Write-Error "SearchBase DN not found: $SearchBase ($($_.Exception.Message))" return } # ---------- Query ---------- $filter = if ($IncludeDisabled) { '*' } else { 'Enabled -eq $true' } $props = @( 'DisplayName','SamAccountName','Enabled','LockedOut', 'lastLogon','lastLogonTimestamp','lastLogonDate', 'whenCreated','whenChanged', 'PasswordLastSet','AccountExpirationDate' ) Write-Host "Querying users under: $SearchBase" try { $users = Get-ADUser -SearchBase $SearchBase -Filter $filter -Properties $props -ErrorAction Stop } catch { Write-Error "Failed querying AD users. $($_.Exception.Message)" return } # ---------- Project results ---------- $i = 0; $total = ($users | Measure-Object).Count $results = foreach ($u in $users) { $i++; Write-Progress -Activity "Building report..." -Status "$i of $total" -PercentComplete (($i/$total)*100) $created = Format-DateLocal $u.whenCreated $changed = Format-DateLocal $u.whenChanged $pwdLastSet = Format-DateLocal $u.PasswordLastSet $acctExpires = if ($u.AccountExpirationDate) { Format-DateLocal $u.AccountExpirationDate } else { 'Never' } $ll = From-FileTimeLocal ($u.lastLogon) $llts = From-FileTimeLocal ($u.lastLogonTimestamp) $llDate = if ($u.lastLogonDate) { $u.lastLogonDate.ToLocalTime().ToString('yyyy-MM-dd HH:mm:ss') } else { 'Never Logged In' } [PSCustomObject]@{ Name = if ($u.DisplayName) { $u.DisplayName } else { $u.SamAccountName } SamAccountName = $u.SamAccountName Enabled = $u.Enabled LockedOut = $u.LockedOut CreatedDate = $created ModifiedDate = $changed PasswordLastSet = $pwdLastSet AccountExpires = $acctExpires LastLogon = $ll LastLogonTimeStamp = $llts LastLogonDate = $llDate } } # ---------- Sort & export ---------- $sorted = $results | Select-Object *, @{n='__LLSort'; e={ if ($_.'LastLogon' -eq 'Never Logged In') { $null } else { Get-Date $_.'LastLogon' } }}, @{n='__LLTSSort';e={ if ($_.'LastLogonTimeStamp' -eq 'Never Logged In') { $null } else { Get-Date $_.'LastLogonTimeStamp' } }} | Sort-Object -Property @{Expression='__LLSort';Descending=$true}, @{Expression='__LLTSSort';Descending=$true} | Select-Object Name,SamAccountName,Enabled,LockedOut,CreatedDate,ModifiedDate,PasswordLastSet,AccountExpires,LastLogon,LastLogonTimeStamp,LastLogonDate $sorted | Format-Table -AutoSize try { $sorted | Export-Csv -Path $OutCsv -NoTypeInformation -Encoding UTF8 -Force Write-Host "`nCSV written to: $OutCsv" } catch { Write-Error "Export failed: $($_.Exception.Message)" }