Add user Notes field stamping with computer name and login timestamp

This commit is contained in:
2026-04-21 14:59:28 +10:00
parent 7f23edc31e
commit cac43b4304
+49 -28
View File
@@ -1,19 +1,20 @@
#Requires -Version 5.1 #Requires -Version 5.1
<# <#
.SYNOPSIS .SYNOPSIS
Sets the Active Directory computer object's "Managed By" attribute to the currently logged-in user. Sets the AD computer's "Managed By" to the logged-in user, and stamps the
computer name into the user's "Notes" field.
.DESCRIPTION .DESCRIPTION
Designed to run as a GPO User Logon Script. On each login, it resolves the current Designed to run as a GPO User Logon Script. On each login it:
user and computer in AD, then stamps the user's Distinguished Name into the computer 1. Sets the computer object's "managedBy" attribute to the user's DN
object's "managedBy" attribute. 2. Sets the user object's "info" attribute (Notes / Telephones tab) to the computer name
PREREQUISITES: PREREQUISITES:
- The RSAT ActiveDirectory PowerShell module must be installed on client machines,
OR the script can fall back to ADSI/DirectorySearcher (no module needed).
- AD permissions must be delegated so that Authenticated Users (or Domain Users) - AD permissions must be delegated so that Authenticated Users (or Domain Users)
can WRITE the "managedBy" attribute on Computer objects in the relevant OU(s). can WRITE the "managedBy" attribute on Computer objects in the relevant OU(s).
See README.md for delegation instructions. - Users can write their own "info" attribute by default (Personal Information
property set). No extra delegation needed for the Notes field.
- RSAT AD module is optional; the script falls back to ADSI if unavailable.
.NOTES .NOTES
Deploy via: GPO > User Configuration > Policies > Windows Settings > Scripts > Logon Deploy via: GPO > User Configuration > Policies > Windows Settings > Scripts > Logon
@@ -29,7 +30,6 @@ function Write-Log {
param([string]$Message, [string]$Level = "INFO") param([string]$Message, [string]$Level = "INFO")
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$entry = "[$timestamp] [$Level] $Message" $entry = "[$timestamp] [$Level] $Message"
# Rotate log if oversized
if ((Test-Path $LogFile) -and ((Get-Item $LogFile).Length / 1KB -gt $MaxLogSizeKB)) { if ((Test-Path $LogFile) -and ((Get-Item $LogFile).Length / 1KB -gt $MaxLogSizeKB)) {
Remove-Item $LogFile -Force -ErrorAction SilentlyContinue Remove-Item $LogFile -Force -ErrorAction SilentlyContinue
} }
@@ -40,6 +40,7 @@ function Write-Log {
$currentUser = $env:USERNAME $currentUser = $env:USERNAME
$computerName = $env:COMPUTERNAME $computerName = $env:COMPUTERNAME
$userDomain = $env:USERDOMAIN $userDomain = $env:USERDOMAIN
$loginTimestamp = Get-Date -Format "yyyy-MM-dd HH:mm"
Write-Log "Script started. User=$userDomain\$currentUser, Computer=$computerName" Write-Log "Script started. User=$userDomain\$currentUser, Computer=$computerName"
@@ -59,21 +60,30 @@ try {
Write-Log "AD module not available. Falling back to ADSI/DirectorySearcher." "WARN" Write-Log "AD module not available. Falling back to ADSI/DirectorySearcher." "WARN"
} }
# ── Helper: build the notes string ──────────────────────────────────────────
$notesValue = "Last logon: $computerName ($loginTimestamp)"
try { try {
if ($useADModule) { if ($useADModule) {
# ── AD Module path ────────────────────────────────────────────────── # ── AD Module path ──────────────────────────────────────────────────
$userObj = Get-ADUser -Identity $currentUser -ErrorAction Stop $userObj = Get-ADUser -Identity $currentUser -Properties info -ErrorAction Stop
$computerObj = Get-ADComputer -Identity $computerName -ErrorAction Stop $computerObj = Get-ADComputer -Identity $computerName -Properties managedBy -ErrorAction Stop
$currentManagedBy = (Get-ADComputer -Identity $computerName -Properties managedBy).managedBy # — Set computer ManagedBy ——
if ($computerObj.managedBy -eq $userObj.DistinguishedName) {
if ($currentManagedBy -eq $userObj.DistinguishedName) {
Write-Log "ManagedBy already set to $currentUser. No change needed." Write-Log "ManagedBy already set to $currentUser. No change needed."
exit 0 } else {
}
Set-ADComputer -Identity $computerName -ManagedBy $userObj.DistinguishedName -ErrorAction Stop Set-ADComputer -Identity $computerName -ManagedBy $userObj.DistinguishedName -ErrorAction Stop
Write-Log "SUCCESS: Set ManagedBy on '$computerName' to '$($userObj.DistinguishedName)'" Write-Log "SUCCESS: Set ManagedBy on '$computerName' to '$($userObj.DistinguishedName)'"
}
# — Set user Notes (info attribute) ——
if ($userObj.info -eq $notesValue) {
Write-Log "User notes already current. No change needed."
} else {
Set-ADUser -Identity $currentUser -Replace @{info = $notesValue} -ErrorAction Stop
Write-Log "SUCCESS: Set Notes on '$currentUser' to '$notesValue'"
}
} else { } else {
# ── ADSI fallback (no module required) ────────────────────────────── # ── ADSI fallback (no module required) ──────────────────────────────
@@ -83,9 +93,9 @@ try {
$searcher = New-Object DirectoryServices.DirectorySearcher $searcher = New-Object DirectoryServices.DirectorySearcher
$searcher.SearchRoot = [ADSI]"LDAP://$domainDN" $searcher.SearchRoot = [ADSI]"LDAP://$domainDN"
# Find the user DN # Find the user
$searcher.Filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$currentUser))" $searcher.Filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$currentUser))"
$searcher.PropertiesToLoad.Add("distinguishedName") | Out-Null $searcher.PropertiesToLoad.AddRange(@("distinguishedName", "info"))
$userResult = $searcher.FindOne() $userResult = $searcher.FindOne()
if (-not $userResult) { if (-not $userResult) {
@@ -94,11 +104,10 @@ try {
} }
$userDN = $userResult.Properties["distinguishedname"][0] $userDN = $userResult.Properties["distinguishedname"][0]
# Find the computer DN # Find the computer
$searcher.Filter = "(&(objectCategory=computer)(sAMAccountName=$computerName$))" $searcher.Filter = "(&(objectCategory=computer)(sAMAccountName=$computerName$))"
$searcher.PropertiesToLoad.Clear() $searcher.PropertiesToLoad.Clear()
$searcher.PropertiesToLoad.Add("distinguishedName") | Out-Null $searcher.PropertiesToLoad.AddRange(@("distinguishedName", "managedBy"))
$searcher.PropertiesToLoad.Add("managedBy") | Out-Null
$computerResult = $searcher.FindOne() $computerResult = $searcher.FindOne()
if (-not $computerResult) { if (-not $computerResult) {
@@ -108,7 +117,7 @@ try {
$computerDN = $computerResult.Properties["distinguishedname"][0] $computerDN = $computerResult.Properties["distinguishedname"][0]
# Check current value # — Set computer ManagedBy ——
$currentManagedBy = $null $currentManagedBy = $null
if ($computerResult.Properties["managedby"].Count -gt 0) { if ($computerResult.Properties["managedby"].Count -gt 0) {
$currentManagedBy = $computerResult.Properties["managedby"][0] $currentManagedBy = $computerResult.Properties["managedby"][0]
@@ -116,18 +125,30 @@ try {
if ($currentManagedBy -eq $userDN) { if ($currentManagedBy -eq $userDN) {
Write-Log "ManagedBy already set to $currentUser. No change needed." Write-Log "ManagedBy already set to $currentUser. No change needed."
exit 0 } else {
}
# Set the attribute via ADSI
$computerEntry = [ADSI]"LDAP://$computerDN" $computerEntry = [ADSI]"LDAP://$computerDN"
$computerEntry.Put("managedBy", $userDN) $computerEntry.Put("managedBy", $userDN)
$computerEntry.SetInfo() $computerEntry.SetInfo()
Write-Log "SUCCESS: Set ManagedBy on '$computerName' to '$userDN'" Write-Log "SUCCESS: Set ManagedBy on '$computerName' to '$userDN'"
} }
# — Set user Notes (info attribute) ——
$currentNotes = $null
if ($userResult.Properties["info"].Count -gt 0) {
$currentNotes = $userResult.Properties["info"][0]
}
if ($currentNotes -eq $notesValue) {
Write-Log "User notes already current. No change needed."
} else {
$userEntry = [ADSI]"LDAP://$userDN"
$userEntry.Put("info", $notesValue)
$userEntry.SetInfo()
Write-Log "SUCCESS: Set Notes on '$currentUser' to '$notesValue'"
}
}
} catch { } catch {
Write-Log "FAILED to set ManagedBy: $($_.Exception.Message)" "ERROR" Write-Log "FAILED: $($_.Exception.Message)" "ERROR"
exit 1 exit 1
} }