This script can be (modifed) and used to sync all radius clients over multiple NPS servers (the server running the script is master). In addition it will create Custom Event views, for each Radius client, making it so much easier to use the logs.
#Add list of NPS Servers here $NPSServers = @("Server1","Server2","Server3") [string]$EventViewPath = "C:\ProgramData\Microsoft\Event Viewer\Views\NPS" [string]$EventViewPath_REMOTE = "c$\ProgramData\Microsoft\Event Viewer\Views\NPS" function VerifyAdmin { $Elevated = New-Object Security.Principal.WindowsPrincipal( [Security.Principal.WindowsIdentity]::GetCurrent() ) & { if ($Elevated.IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator )) { write-host "PowerShell is running as an administrator." -ForegroundColor Green } Else { throw "Powershell must be run as an adminstrator." } if( [IntPtr]::size * 8 -eq 64 ) { Write-Host "You are running 64-bit PowerShell" -ForegroundColor Green } else { Write-Host "You are running 32-bit PowerShell" -ForegroundColor Red Throw "Please run using 64-bit PowerShell as administrator" } } } # ---------------------------------------- # END OF POWERSHELL CHECK # ---------------------------------------- function EmptyViews-Local() { if ((Test-Path -Path $EventViewPath) -eq $false) { New-Item -Path $EventViewPath -type directory } Remove-Item -Path "$EventViewPath\*" -Recurse } function EmptyViews([string]$Server) { [string] $Path = "\\$Server\$EventViewPath_REMOTE" if ((Test-Path -Path $Path) -eq $false) { New-Item -Path $Path -type directory } Remove-Item -Path "$Path\*" -Recurse } # Get the Location from the Name of the Switch function GetRadiusClientEnvironment([string]$Name) { $NameArray = $Name.Split("-") if ($NameArray.Count -le 2) { return $Null #Not a standard name } return $NameArray[1] } function CreateCustomView-Local([string]$IP,[string]$Name,[int]$ID) { [string]$XmlData="<ViewerConfig><QueryConfig><QueryParams><UserQuery /></QueryParams><QueryNode><Name LanguageNeutralValue=`"$Name`">$Name</Name><QueryList><Query Id=`"0`" Path=`"Security`"><Select Path=`"Security`">*[EventData[Data[@Name='ClientName']='$NAME']]</Select></Query></QueryList></QueryNode></QueryConfig></ViewerConfig>" [string]$Location = GetRadiusClientEnvironment $Name if ($Location -ne "") # Powershell does not really handle $Null correct, check for empty string instead { if ((Test-Path -Path "$EventViewPath\$Location" -PathType Container) -eq $false) { $D = New-Item -Path "$EventViewPath\$Location" -type directory } Add-Content -Path "$EventViewPath\$Location\View_$ID.xml" -Value $XmlData } else { Add-Content -Path "$EventViewPath\View_$ID.xml" -Value $XmlData } } function CreateCustomView([string]$IP,[string]$Name,[int]$ID,[string]$Server) { [string]$XmlData="<ViewerConfig><QueryConfig><QueryParams><UserQuery /></QueryParams><QueryNode><Name LanguageNeutralValue=`"$Name`">$Name</Name><QueryList><Query Id=`"0`" Path=`"Security`"><Select Path=`"Security`">*[EventData[Data[@Name='ClientName']='$NAME']]</Select></Query></QueryList></QueryNode></QueryConfig></ViewerConfig>" [string]$Location = GetRadiusClientEnvironment $Name if ($Location -ne "") # Powershell does not really handle $Null correct, check for empty string instead { if ((Test-Path -Path "\\$Server\$EventViewPath_REMOTE\$Location" -PathType Container) -eq $false) { $D = New-Item -Path "\\$Server\$EventViewPath_REMOTE\$Location" -type directory } Add-Content -Path "\\$Server\$EventViewPath_REMOTE\$Location\View_$ID.xml" -Value $XmlData } else { Add-Content -Path "\\$Server\$EventViewPath_REMOTE\View_$ID.xml" -Value $XmlData } } # This function will create the quick access search list function CreateCustomHelperViews([string]$Server) { if ((Test-Path -Path "\\$Server\$EventViewPath_REMOTE\_QuickSearches" -PathType Container) -eq $false) { $D = New-Item -Path "\\$Server\$EventViewPath_REMOTE\_QuickSearches" -type directory } [string]$FindByHostName="<ViewerConfig><QueryConfig><QueryParams><UserQuery /></QueryParams><QueryNode><Name LanguageNeutralValue=`"Find by HostName`">Find by HostName</Name><QueryList><Query Id=`"0`" Path=`"Security`"><Select Path=`"Security`">*[EventData[Data[@Name='FullyQualifiedSubjectUserName']='PERTRA\DNDZ27370$']]</Select></Query></QueryList></QueryNode></QueryConfig></ViewerConfig>" Add-Content -Path "\\$Server\$EventViewPath_REMOTE\_QuickSearches\View_ByHostName.xml" -Value $FindByHostName [string]$FindByMac="<ViewerConfig><QueryConfig><QueryParams><UserQuery /></QueryParams><QueryNode><Name LanguageNeutralValue=`"Find by MAC`">Find by MAC</Name><QueryList><Query Id=`"0`" Path=`"Security`"><Select Path=`"Security`">*[EventData[Data[@Name='CallingStationID']='6c-c2-17-7f-25-64']]</Select></Query></QueryList></QueryNode></QueryConfig></ViewerConfig>" Add-Content -Path "\\$Server\$EventViewPath_REMOTE\_QuickSearches\View_ByMac.xml" -Value $FindByMac [string]$FindByUserName="<ViewerConfig><QueryConfig><QueryParams><UserQuery /></QueryParams><QueryNode><Name LanguageNeutralValue=`"Find by UserName`">Find by UserName</Name><QueryList><Query Id=`"0`" Path=`"Security`"><Select Path=`"Security`">*[EventData[Data[@Name='SubjectUserName']='terped']]</Select></Query></QueryList></QueryNode></QueryConfig></ViewerConfig>" Add-Content -Path "\\$Server\$EventViewPath_REMOTE\_QuickSearches\View_ByUserName.xml" -Value $FindByUserName [string]$AllWarnings="<ViewerConfig><QueryConfig><QueryParams><UserQuery /></QueryParams><QueryNode><Name LanguageNeutralValue=`"All Denied`">All Denied</Name><QueryList><Query Id=`"0`" Path=`"System`"><Select Path=`"System`">*[System[Provider[@Name='NPS']]]</Select><Select Path=`"System`">*[System[Provider[@Name='HRA']]]</Select><Select Path=`"System`">*[System[Provider[@Name='Microsoft-Windows-HCAP']]]</Select><Select Path=`"Security`">*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID = 6273]]</Select></Query></QueryList></QueryNode></QueryConfig></ViewerConfig>" Add-Content -Path "\\$Server\$EventViewPath_REMOTE\_QuickSearches\View_Warings.xml" -Value $AllWarnings } # This function will create the quick access search list function CreateCustomHelperViews-local() { if ((Test-Path -Path "$EventViewPath\_QuickSearches" -PathType Container) -eq $false) { $D = New-Item -Path "$EventViewPath\_QuickSearches" -type directory } [string]$FindByHostName="<ViewerConfig><QueryConfig><QueryParams><UserQuery /></QueryParams><QueryNode><Name LanguageNeutralValue=`"Find by HostName`">Find by HostName</Name><QueryList><Query Id=`"0`" Path=`"Security`"><Select Path=`"Security`">*[EventData[Data[@Name='FullyQualifiedSubjectUserName']='PERTRA\DNDZ27370$']]</Select></Query></QueryList></QueryNode></QueryConfig></ViewerConfig>" Add-Content -Path "$EventViewPath\_QuickSearches\View_ByHostName.xml" -Value $FindByHostName [string]$FindByMac="<ViewerConfig><QueryConfig><QueryParams><UserQuery /></QueryParams><QueryNode><Name LanguageNeutralValue=`"Find by MAC`">Find by MAC</Name><QueryList><Query Id=`"0`" Path=`"Security`"><Select Path=`"Security`">*[EventData[Data[@Name='CallingStationID']='6c-c2-17-7f-25-64']]</Select></Query></QueryList></QueryNode></QueryConfig></ViewerConfig>" Add-Content -Path "$EventViewPath\_QuickSearches\View_ByMac.xml" -Value $FindByMac [string]$FindByUserName="<ViewerConfig><QueryConfig><QueryParams><UserQuery /></QueryParams><QueryNode><Name LanguageNeutralValue=`"Find by UserName`">Find by UserName</Name><QueryList><Query Id=`"0`" Path=`"Security`"><Select Path=`"Security`">*[EventData[Data[@Name='SubjectUserName']='terped']]</Select></Query></QueryList></QueryNode></QueryConfig></ViewerConfig>" Add-Content -Path "$EventViewPath\_QuickSearches\View_ByUserName.xml" -Value $FindByUserName [string]$AllWarnings="<ViewerConfig><QueryConfig><QueryParams><UserQuery /></QueryParams><QueryNode><Name LanguageNeutralValue=`"All Denied`">All Denied</Name><QueryList><Query Id=`"0`" Path=`"System`"><Select Path=`"System`">*[System[Provider[@Name='NPS']]]</Select><Select Path=`"System`">*[System[Provider[@Name='HRA']]]</Select><Select Path=`"System`">*[System[Provider[@Name='Microsoft-Windows-HCAP']]]</Select><Select Path=`"Security`">*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID = 6273]]</Select></Query></QueryList></QueryNode></QueryConfig></ViewerConfig>" Add-Content -Path "$EventViewPath\_QuickSearches\View_Warings.xml" -Value $AllWarnings } function RemoveRadiusClient([string]$Server,[string]$Name) { $Error.Clear() $RemoteScript = "Remove-NpsRadiusClient -Name `"$Name`"" $Result = Invoke-Command -ComputerName $Server -ScriptBlock { Invoke-Expression $args[0] } -ArgumentList $RemoteScript if ($Error.Count -ne 0) { Write-Host "Failed Delete RadiusClient $Name on Server $Server. Error: ($Error)" return $false } Write-Host "Deleted Radius Client($Name) on server: $Server" return $true } function CreateNewRadiusClient([string]$Server,[object]$MasterClient) { $Error.Clear() if ($MasterClient.Enabled -eq $true) { $RemoteScript = "New-NpsRadiusClient -Name `"$($MasterClient.Name)`" -Address `"$($MasterClient.Address)`" -SharedSecret `"$($MasterClient.SharedSecret)`" -VendorName `"$($MasterClient.VendorName)`" -NapCompatible `$$($MasterClient.NapCompatible)" } else { $RemoteScript = "New-NpsRadiusClient -Name `"$($MasterClient.Name)`" -Address `"$($MasterClient.Address)`" -SharedSecret `"$($MasterClient.SharedSecret)`" -VendorName `"$($MasterClient.VendorName)`" -NapCompatible `$$($MasterClient.NapCompatible) -Disabled:`$true" } $Result = Invoke-Command -ComputerName $Server -ScriptBlock { Invoke-Expression $args[0] } -ArgumentList $RemoteScript if ($Error.Count -ne 0) { Write-Host "Failed Createe NewRadiusClient $($MasterClient.Name) on Server $Server. Error: ($Error)" return $false } Write-Host "Created new Radius Client($($MasterClient.Name)) on server: $Server" return $true } function UpdateRemoveRadiusClientByName([string]$Server,[string]$Name,[string]$UpdateString) { $RemoteScript = "Set-NpsRadiusClient -Name $Name $UpdateString" $Error.Clear() $Result = Invoke-Command -ComputerName $Server -ScriptBlock { Invoke-Expression $args[0] } -ArgumentList $RemoteScript if ($Error.Count -ne 0) { WriteLogError "Failed to update RadiusClient $Name on Server $Server. Error: ($Error)" return $false } Write-Host "RadiusClient $Name on Server $Server. was updated." return $true } function CheckAndDeleteClients([object]$ValidClients,[object]$ClientToCheck,[string]$Server) { [bool]$found = $false foreach ($CL in $ValidClients) { if ($CL.Name -eq $ClientToCheck.Name) { $found = $true } } if ($found -eq $false) { WriteLog "Deleted Radius Client on $Server, Not configured on Master. Name: $($ClientToCheck.Name) IP: $($ClientToCheck.Address) Secret: $($ClientToCheck.SharedSecret) Enabled: $($ClientToCheck.Enabled) NAP: $($ClientToCheck.NapCompatible) Vendor: $($ClientToCheck.VendorName)" $Result = RemoveRadiusClient $Server $ClientToCheck.Name } } # ListOfClient on the REMOTE Radius server # RadiusClient is a single Rclient from the "Master" list function CheckIfClientExists([object]$ListofClients,[object]$RadiusClient,[string]$Server) { foreach ($C in $ListofClients) { [int]$NeedUpdate = 0 [string]$UpdateString ="" if ($C.Address -eq $RadiusClient.Address) { # Client Exists, Check if data is the same if ($C.Name -ne $RadiusClient.Name) { Write-Host "RadiusClient with IP: $($RadiusClient.Address) has changed name. Needs to be deleted and recreated" $Res = RemoveRadiusClient $Server $C.Name if ($Res -eq $true) { $Res = CreateNewRadiusClient $Server $RadiusClient } } else { if ($C.Enabled -ne $RadiusClient.Enabled) { $NeedUpdate++ $UpdateString += "-Enabled `$$($RadiusClient.Enabled) " } if ($C.SharedSecret -ne $RadiusClient.SharedSecret) { $NeedUpdate++ $UpdateString += "-SharedSecret `"$($RadiusClient.SharedSecret)`" " } if ($C.VendorName -ne $RadiusClient.VendorName) { $NeedUpdate++ $UpdateString += "-VendorName `"$($RadiusClient.VendorName)`" " } if ($C.NapCompatible -ne $RadiusClient.NapCompatible) { $NeedUpdate++ $UpdateString += "-NapCompatible `$$($RadiusClient.NapCompatible) " } if ($NeedUpdate -ne 0) { $Res = UpdateRemoveRadiusClientByName $Server $C.Name $UpdateString } } return $true } } # Check for any client that has been changed IP address foreach ($C in $ListofClients) { if ($C.Name -eq $RadiusClient.Name) { if ($C.Address -ne $RadiusClient.Address) { [string]$UpdateString ="" #Radius Client has new IP address, but same name $UpdateString += "-Address `"$($RadiusClient.Address)`" " if ($C.Enabled -ne $RadiusClient.Enabled) { $UpdateString += "-Enabled `$$($RadiusClient.Enabled) " } if ($C.SharedSecret -ne $RadiusClient.SharedSecret) { $UpdateString += "-SharedSecret `"$($RadiusClient.SharedSecret)`" " } if ($C.VendorName -ne $RadiusClient.VendorName) { $UpdateString += "-VendorName `"$($RadiusClient.VendorName)`" " } if ($C.NapCompatible -ne $RadiusClient.NapCompatible) { $UpdateString += "-NapCompatible `$$($RadiusClient.NapCompatible) " } #Write-Host $UpdateString $Res = UpdateRemoveRadiusClientByName $Server $C.Name $UpdateString return $true } } } return $false } # Get a list of Local Radius Clients VerifyAdmin $CU = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name Write-Host "---Run RadiusClientsSync on $([DateTime]::Now) by $CU." $Clients = Get-NpsRadiusClient # Validate All Local Clients [bool]$Validated = $true foreach ($C in $Clients) { if (($C.SharedSecret -eq "") -or ($C.SharedSecret -eq $null)) { Write-Host "Master has an invalid Radius Client: $($C.Name) this must be removed before sync is preformed!" $Validated = $false } } if ($Validated -eq $false) { return # Break out of script.. There is some errors... } # Update Local Custom Views EmptyViews-Local [int]$IDCounter = 1 foreach ($C in $Clients) { CreateCustomView-Local $C.Address $C.Name $IDCounter $IDCounter++ } CreateCustomHelperViews-local foreach ($Server in $NPSServers) { WriteLog "Updating Radius Clients" $RemoteClients = Invoke-Command -ComputerName $Server -ScriptBlock { Get-NpsRadiusClient} #Check what's missing foreach ($C in $Clients) { $Res = CheckIfClientExists $RemoteClients $C $Server if ($Res -eq $false) { # Create NEW CreateNewRadiusClient $Server $C } } # Check if Remote Server has Clients we are not aware off # Re-Read clients, incase we have deleted/created any new clients $RemoteClients = Invoke-Command -ComputerName $Server -ScriptBlock { Get-NpsRadiusClient} foreach ($RC in $RemoteClients) { CheckAndDeleteClients $Clients $RC $Server } Write-Host "Creating Custom Event Views" EmptyViews $Server # RE-Read Again to run CustomViews $RemoteClients = Invoke-Command -ComputerName $Server -ScriptBlock { Get-NpsRadiusClient} [int]$ID = 0 foreach ($RC in $RemoteClients) { CreateCustomView $RC.Address $RC.Name $ID $Server $ID++; } CreateCustomHelperViews $Server }