Managing user permissions across multiple servers is a common task that requires efficiency and reliability. This post details my journey of creating a PowerShell script to add a service account to the "Event Log Readers" group across numerous servers using PsExec.
The Challenge
I needed to add a service account (bear\EventAgent) to the "Event Log Readers" local group on multiple Windows servers. The requirements were:
- Read server names from a text file
- Connect to each server remotely
- Add the account to the "Event Log Readers" group
- Verify the operation was successful
- Provide detailed feedback throughout the process
First Attempt: PowerShell Remoting
My initial approach used PowerShell remoting with Invoke-Command
:
# First attempt using PowerShell Remoting
foreach ($server in $servers) {
if (Test-Connection -ComputerName $server -Count 1 -Quiet) {
try {
# Attempt to connect and run command
$result = Invoke-Command -ComputerName $server -ScriptBlock {
net localgroup "Event Log Readers" bear\EventAgent /add
} -ErrorAction Stop
# Display the result
Write-Host "Command executed on $server with output:" -ForegroundColor Green
$result
}
catch {
Write-Host "ERROR: Failed to execute command on $server" -ForegroundColor Red
Write-Host "Error details: $_" -ForegroundColor Red
}
}
}
This approach failed on several servers with WinRM connection errors:
ERROR: Failed to execute command on mgrworkstation1
Error details: [mgrworkstation1] Connecting to remote server mgrworkstation failed with the
following error message : The client cannot connect to the destination specified in the
request. Verify that the service on the destination is running and is accepting requests...
Second Approach: PsExec
To overcome the WinRM limitations, I switched to Sysinternals' PsExec utility, which doesn't rely on WinRM being configured:
# Using PsExec to run the command
$command = "net localgroup `"Event Log Readers`" bear\eventagent /add"
$result = & $psExecPath "\\$server" -s cmd /c $command 2>&1
This worked better but produced confusing output. PsExec would show:
ERROR: Command execution failed on mgrworkstation1
Starting cmd on mgrworkstation1..
cmd exited on mgrworkstation1 with error code 0.
Despite the "ERROR" message, an exit code of 0 indicates success. This required better parsing logic.
Final Solution
The final script addresses all requirements with several enhancements:
Group Existence Check
Before attempting to add the user, verify the group exists:
# Check if the group exists
$checkCommand = "net localgroup `"$groupName`""
$checkResult = & $psExecPath "\\$server" -s cmd /c $checkCommand 2>&1
if ($checkResult -match "system error|does not exist") {
Write-Host "ERROR: The group '$groupName' does not exist on $server" -ForegroundColor Red
$failCount++
continue
}
Membership Verification
Check if the user is already a member of the group:
# Check if user is already in the group
if ($checkResult -match [regex]::Escape($userToAdd)) {
Write-Host "INFO: User $userToAdd is already a member of $groupName on $server"
ForegroundColor Cyan
$alreadyExistsCount++
$successCount++
continue
}
Handling PsExec's "Error Code 0" Messages
Properly interpret PsExec's seemingly misleading output:
# Special case for PsExec showing "error code 0" but command was successful
elseif ($result -match "exited .* with error code 0") {
Write-Host "Command executed successfully on $server (exit code 0)"
-ForegroundColor Green
$result | ForEach-Object { Write-Host " $_" -ForegroundColor White }
$successCount++
# Verify the user was added
$verifyCommand = "net localgroup `"$groupName`""
$verifyResult = & $psExecPath "\\$server" -s cmd /c $verifyCommand 2>&1
if ($verifyResult -match [regex]::Escape($userToAdd)) {
Write-Host "Verified: User $userToAdd is now a member of $groupName on $server"
-ForegroundColor Green
} else {
Write-Host "WARNING: Verification failed. Please check manually."
-ForegroundColor Yellow
}
}
4. Detailed Execution Summary
Provide a comprehensive breakdown of results:
# Display summary
Write-Host "`n===================================" -ForegroundColor Magenta
Write-Host "EXECUTION SUMMARY" -ForegroundColor Magenta
Write-Host "===================================" -ForegroundColor Magenta
Write-Host "Total servers processed: $serverCount" -ForegroundColor White
Write-Host "Successful operations: $successCount" -ForegroundColor Green
Write-Host " - Already in group: $alreadyExistsCount" -ForegroundColor Cyan
Write-Host " - Newly added: $($successCount - $alreadyExistsCount)" -ForegroundColor Green
Write-Host "Failed operations: $failCount" -ForegroundColor Red
Write-Host "===================================" -ForegroundColor Magenta
Lessons Learned
- WinRM vs. PsExec: While PowerShell remoting via WinRM is Microsoft's preferred method, it requires proper configuration on all target servers. PsExec provides a reliable alternative for environments where WinRM isn't universally configured.
- Output Interpretation: Tools like PsExec can produce misleading output ("ERROR" followed by success indicators). Always parse the entire output for a complete understanding.
- Verification is Key: Always verify operations were successful, especially when working with permissions across multiple servers.
- Idempotent Operations: Design scripts to be idempotent - checking if the action is needed before attempting it saves time and prevents errors.