How-To: Active Directory Workstation Logons (Part 2) – Enterprise Management with ADManager Plus

Content Type
Versions
ADM-v8043

AI Preview

Is this content worth your time? Use the quick preview to decide.

Welcome to the second part of our Active Directory Workstation Logons series.

In Part 1, we built the policy-based local sign-in mechanism and the workstation-side synchronization process. In this video, we take that design into a more centralized management model with ManageEngine ADManager Plus.

We cover single and bulk computer changes, on-demand policy synchronization, delegated technician access, reporting and auditing, and an offboarding workflow that removes a departing user’s workstation logon assignments while preserving unrelated entries. The goal is to show how the same custom solution can be managed more practically as the environment grows.

🔗 Helpful Links
  • There are no helpful links in this one.

Video Tutorial

Used Commands


Trigger workstation policy sync

<#
.SYNOPSIS
Triggers the Techaptic Workstation Access policy synchronization task on a remote Windows computer.

.DESCRIPTION
Queries the existing scheduled task before starting it, triggers a new remote run,
waits for the new run to finish, and validates the scheduled task Last Result.

The command is designed for Techaptic/ADManager Plus orchestration and preserves a
single final success message on the output stream. Errors are raised with throw so
the orchestration layer can detect a failed run.

.NOTES
Author : Techaptic
Scope : Remote workstation policy synchronization.
Task : Techaptic Workstation Access - Policy Sync
#>

param(
[Parameter(Mandatory = $true)]
[string]$ComputerName
)

# Variables
$TaskName = 'Techaptic Workstation Access - Policy Sync'

# Helper Functions
function Get-RemoteTaskInfo {
$raw = & "$env:SystemRootSystem32schtasks.exe" /Query /S $ComputerName /TN $TaskName /V /FO CSV 2>&1
if ($LASTEXITCODE -ne 0) {
throw "Could not query '$TaskName' on '$ComputerName': $($raw -join ' ')"
}

return ($raw | ConvertFrom-Csv | Select-Object -First 1)
}

# 1. Read the Current Task State
$before = Get-RemoteTaskInfo
$beforeLastRun = [string]$before.'Last Run Time'

# 2. Trigger the Remote Policy Sync
$runOutput = & "$env:SystemRootSystem32schtasks.exe" /Run /S $ComputerName /TN $TaskName 2>&1
if ($LASTEXITCODE -ne 0) {
throw "Run request failed on '$ComputerName': $($runOutput -join ' ')"
}

# 3. Wait for a New Completed Run
$deadline = (Get-Date).AddSeconds(30)
$after = $null

do {
Start-Sleep -Seconds 1
$after = Get-RemoteTaskInfo
$newRunDetected = ([string]$after.'Last Run Time' -ne $beforeLastRun)

if ($newRunDetected -and [string]$after.Status -ne 'Running') {
break
}
}
while ((Get-Date) -lt $deadline)

if (-not $newRunDetected) {
throw "The remote run request was accepted, but '$TaskName' did not record a new run on '$ComputerName'."
}

# 4. Validate the Scheduled Task Result
$lastResult = [string]$after.'Last Result'
if ($lastResult -notin @('0', '0x0', '0X0')) {
throw "'$TaskName' ran on '$ComputerName' but finished with Last Result '$lastResult'."
}

# 5. Return the Final Orchestration Result
Write-Output "SUCCESS: '$TaskName' completed on '$ComputerName'. Last Run Time=$($after.'Last Run Time'); Last Result=$lastResult"
exit 0

Offboarded user access cleanup

<#
.SYNOPSIS
Removes an offboarded user's Active Directory DN from Techaptic workstation access assignments.

.DESCRIPTION
Receives the user's sAMAccountName from ADManager Plus, resolves the user's exact
Active Directory distinguished name, finds computer objects whose
techapticLogonPrincipals attribute contains that DN, and removes only that matching
DN. Other users and groups on the attribute remain untouched.

The script writes an operational log under ProgramData and returns a single final
SUCCESS or FAILED message for the orchestration layer.

.NOTES
Author : Techaptic
Scope : Workstation access offboarding cleanup.
Argument : %ad.sAMAccountName%
Log : %ProgramData%TechapticLogsWorkstation-Offboarding-Cleanup.log
#>

$ErrorActionPreference = 'Stop'

# Variables
$UserName = if ($args.Count -ge 1) { [string]$args[0] } else { '' }
$UserName = $UserName.Trim().Trim('"').Trim("'")

$LogDir = Join-Path $env:ProgramData 'TechapticLogs'
$LogFile = Join-Path $LogDir 'Workstation-Offboarding-Cleanup.log'

New-Item -Path $LogDir -ItemType Directory -Force | Out-Null

# Helper Functions
function Write-TechapticLog {
param([string]$Message)
"$(Get-Date -Format s) $Message" | Add-Content -Path $LogFile
}

function Escape-LdapFilterValue {
param([Parameter(Mandatory = $true)][string]$Value)

$Value = $Value -replace '\', '5c'
$Value = $Value -replace '*', '2a'
$Value = $Value -replace '(', '28'
$Value = $Value -replace ')', '29'
$Value = $Value -replace ([char]0), '0'
return $Value
}

function Get-FirstPropertyValue {
param(
[Parameter(Mandatory = $true)]$Result,
[Parameter(Mandatory = $true)][string]$PropertyName
)

if (
$null -eq $Result.Properties[$PropertyName] -or
$Result.Properties[$PropertyName].Count -eq 0
) {
return $null
}

return [string]$Result.Properties[$PropertyName][0]
}

# Disposable DirectoryServices Objects
$computerResults = $null
$computerSearcher = $null
$userSearcher = $null
$directoryRoot = $null
$rootDse = $null

try {
# 1. Validate the ADManager Plus Argument
if ([string]::IsNullOrWhiteSpace($UserName)) {
throw 'No user name was supplied by ADManager Plus.'
}

# 2. Connect to the Active Directory Naming Context
Add-Type -AssemblyName System.DirectoryServices

$rootDse = New-Object System.DirectoryServices.DirectoryEntry('LDAP://RootDSE')
$defaultNamingContext = [string]$rootDse.Properties['defaultNamingContext'].Value

if ([string]::IsNullOrWhiteSpace($defaultNamingContext)) {
throw 'Could not determine the Active Directory default naming context.'
}

$directoryRoot = New-Object System.DirectoryServices.DirectoryEntry(
"LDAP://$defaultNamingContext"
)

# 3. Resolve the User's Exact Distinguished Name
$escapedUserName = Escape-LdapFilterValue -Value $UserName

$userSearcher = New-Object System.DirectoryServices.DirectorySearcher(
$directoryRoot
)

$userSearcher.Filter = (
"(&(objectCategory=person)(objectClass=user)" +
"(sAMAccountName=$escapedUserName))"
)

[void]$userSearcher.PropertiesToLoad.Add('distinguishedName')

$userResult = $userSearcher.FindOne()

if ($null -eq $userResult) {
throw "User '$UserName' was not found."
}

$userDn = Get-FirstPropertyValue `
-Result $userResult `
-PropertyName 'distinguishedname'

if ([string]::IsNullOrWhiteSpace($userDn)) {
throw "The distinguishedName of user '$UserName' could not be read."
}

# 4. Find Computers That Contain the User DN
$escapedUserDn = Escape-LdapFilterValue -Value $userDn

$computerSearcher = New-Object System.DirectoryServices.DirectorySearcher(
$directoryRoot
)

$computerSearcher.Filter = (
"(&(objectCategory=computer)" +
"(techapticLogonPrincipals=$escapedUserDn))"
)

[void]$computerSearcher.PropertiesToLoad.Add('name')

$computerResults = $computerSearcher.FindAll()
$affectedCount = 0

# 5. Remove Only the Matching DN from Each Computer
foreach ($result in $computerResults) {
$computer = $result.GetDirectoryEntry()

try {
$computer.RefreshCache(@(
'name',
'techapticLogonPrincipals'
))

$computerName = [string]$computer.Properties['name'].Value

$currentPrincipals = @(
$computer.Properties['techapticLogonPrincipals'] |
ForEach-Object { [string]$_ }
)

$updatedPrincipals = @(
$currentPrincipals |
Where-Object {
-not [string]::Equals(
$_,
$userDn,
[System.StringComparison]::OrdinalIgnoreCase
)
}
)

$principalProperty = $computer.Properties[
'techapticLogonPrincipals'
]

$principalProperty.Clear()

foreach ($principal in $updatedPrincipals) {
[void]$principalProperty.Add($principal)
}

$computer.CommitChanges()
$affectedCount++

Write-TechapticLog (
"SUCCESS Computer=[$computerName] RemovedUser=[$UserName] RemovedDN=[$userDn]"
)
}
finally {
$computer.Dispose()
}
}

# 6. Record and Return the Final Success Result
Write-TechapticLog (
"COMPLETE User=[$UserName] ComputersAffected=[$affectedCount]"
)

Write-Output (
"SUCCESS Removed '$UserName' from workstation principals on $affectedCount computer(s)."
)

exit 0
}
catch {
$message = $_.Exception.Message

Write-TechapticLog (
"FAILED User=[$UserName] Error=[$message]"
)

Write-Output "FAILED Workstation access cleanup: $message"
exit 1
}
finally {
# 7. Dispose DirectoryServices Objects
foreach ($item in @(
$computerResults,
$computerSearcher,
$userSearcher,
$directoryRoot,
$rootDse
)) {
if ($null -ne $item -and $item -is [System.IDisposable]) {
$item.Dispose()
}
}
}

Quiz


QUIZ-EN-Custom-Solution-Active-Directory-Workstation-Logons-Part-2-Enterprise-Management-with-ADManager-Plus-5
  • Question
  • Question
  • Question
  • Question
  • Question

Full Transcript

Rating

Post Rating (EN)
Scroll to Top