Powershell: Real-Time Check of Domain Server’s Uptime

There are lots of methods available to server administrators for checking the last reboot time of Windows machines. One of the quickest and most useful continues to be provided via Microsoft’s super CLI, PowerShell.

$ErrorActionPreference = “SilentlyContinue”
$Servers = Get-ADComputer -Filter ‘Operatingsystem -Like “*server*”‘ -Properties dnshostname|
    Select-Object dnshostname -ExpandProperty dnshostname
Function CheckReboot {
    Foreach ($Server in $Servers) {
    Invoke-Command -ComputerName $Server {(Get-Date)-(gcim Win32_OperatingSystem).LastBootUpTime}|Select PSComputerName, Days, Hours,Minutes
    }
}

Those few lines will scan the Active Directory database for computer accounts with the word “server” in their names. Once located, the DNS name property of each matching record is stored in the variable $Servers.

The function named CheckReboot processes each DNS name stored in $Servers by sending it through a logic loop. The for each loop uses WMI to subtract the boot up time from the current time. Then, it displays a table showing the computer’s name, along with how many days, hours, and minutes each system has been running for.

You will need to run the script from an administrative  terminal session. If it doesn’t work for you, try adding a lime to import the Active Directory Powershell module and check your execution policy. The machine and account running the script also need remote access to WMI.

PowerShell Script to Check Active Directory Member Servers for Automatic Services’ Status

I’ve been caught by an automatic service not starting after system reboots from things like patching. I’ve written several versions of the script below over the years. This is my most recent edition. You’ll need the Active Directory module installed on the system that executes the script.

The code will scan your entire AD for member systems with a Windows Server operating system. It will present you with a list to choose from. It will then test RPC (135) connectivity and scan the automatic services on those that are reachable. The script will report any servers that do not have a status of “running” along with any that were not reachable.

   <#
        .SYNOPSIS
        Checks Acitive Directory Memeber Servers for Automatic Serices that are not currently running.
        .DESCRIPTION
        Dynamically generates list of Active Directory Servers.
        Uses WMI to examine the status of all services set to automatically start on selected servers.
        Filters common automatic services that do not stay started by default: mapsbroker, cdpsvc, gupdate, remoteregistry, sppsvc, wbiosrvc, 
        iphlpsvc, tiledatamodelsvc, clr_optimization, and Microsoft Edge Update are currently excluded from the report.
        .INPUTS
        Get-ServerAutoSerivcesStatus displays a gridview of selectable Active Directory memeber servers. 
        Shift+ and CTRL+ select are enabled.
        CTRL+A to select all.
        Criteria to filter.
        .OUTPUTS
        System.String. / Gridview Get-ServerAutoSerivcesStatus returns a string showing all status on selected servers running, 
        or a gridview of the servers and services that are not. 
        Get-ServerAutoSerivcesStatus also displays a string listing servers that did not respond on TCP 135 (RPC). 
        .EXAMPLE
        PS> Get-ServerAutoSerivcesStatus.ps1
    #>
$ErrorActionPreference = "SilentlyContinue"
$Servers = Get-ADComputer -Filter 'Operatingsystem -Like "*server*"' -Properties dnshostname|
    Select-Object dnshostname -ExpandProperty dnshostname|
    Out-GridView -Title "Select Servers To Enumerate AutoServices. CTRL+A to Select All" -PassThru
$Report = @()
$ErrorLog = @()
$ServersOnline = @()
Write-Host -ForegroundColor Yellow "Please wait, testing connectivity to selected servers....."
Foreach ($Server in $Servers) {
    If ((Test-NetConnection -WarningAction SilentlyContinue -ComputerName $Server -Port 135).tcptestsucceeded){$Serversonline += $Server}
    Else {$Errorlog += $Server}
    }
ForEach ($Server in $ServersOnline) {
    $Wmi = Get-WMIObject win32_service -ComputerName $Server -Filter 'State != "Running" AND StartMode = "Auto"'|
        Select-Object @{n="ServerName"; e={$server}}, @{n="ServiceName";e={$_.name}},@{n="Status";e={$_.state}},@{n="Start Account";e={$_.startname}}
    $Report += $Wmi | Write-Host
    }
$Report | Where-Object {($_.ServiceName -notlike "mapsbroker") -and ($_.ServiceName -notlike "cdpsvc") -and ($_.ServiceName -notlike "gupdate") -and 
    ($_.ServiceName -notlike "remoteregistry") -and ($_.ServiceName -notlike "sppsvc") -and ($_.ServiceName -notlike "wbiosrvc") -and ($_.ServiceName -notlike "iphlpsvc") -and
    ($_.ServiceName -notlike "tiledatamodelsvc") -and ($_.ServiceName -notlike "*clr_optimization*") -and ($_.ServiceName -notlike "Microsoft Edge Update") | 
    Select-Object @{n="Server";e={$server}}, @{n="Stopped Service";e={$_.displayname}
        }
    }
If ($Rerport -ne $null) {$Report | Out-GridView -Title "These automatic serivces are not running"}
    Else {Write-Host -ForegroundColor Green "All Automatic Services on $($Serversonline.count) reachable servers are started."}
If ($ErrorLog -ne $null) {Write-Host -ForegroundColor Red "These $($ErrorLog.count) servers were not reachable via RPC (port 135)`n `n" ($ErrorLog -join ",`n")}
    Else {Write-Host "No connection issues to selected servers detected."}
Pause
Exit

PowerShell; Find and List All Active Directroy Nested Sub-Groups with Get-ADNestedGroups

Recently I needed to reduce the licenses we were consuming with a particular cloud service. I was told that a couple of Active Directory groups served as the service’s ACL. All the accounts in question were disabled but the disabled status didn’t synchronize to the cloud service provider. I needed to remove all disabled user accounts from the ACLs in our AD. My first thought was, “I love it when I get the easy ones”. That should always be a sign that something isn’t going to be easy, LOL.

I immediately opened PowerShell, imported the AD Module and ran a recursive Get-ADGroupMemeber query on the two groups I was told about. I added all the returned objects to an array and then populated that array with the disabled user account’s SamAccountNames. I made another array and added the groups/sub-groups to it. Finally, I used nested foreach loops to remove each user from each of my two AD groups.


Import-Module ActiveDirectory

$allusers = @()
$group1 = Get-ADGroupMember -Identity "Name of Group 1" -Recursive
$group2 = Get-ADGroupMember -Identity "Name of Group 2" -Recursive
$allusers += $group1.SamAccountName
$allusers += $group2.SamAccountName

$users2remove = $allusers | % {Get-ADUser $_ |Select samaccountname, DistinguishedName, enabled|where enabled -ne "true"}

$removes = @()
$removes += $users2remove.samaccountname

$subgroups = @()
$subgroups1 = Get-ADGroupMember -Identity "Name of Parent Group1" |Where {$_.ObjectClass -eq "Group"}
$subgroups2 = Get-ADGroupMember -Identity "Name of Parent Group2"| Where {$_.ObjectClass -eq "Group"}

Foreach ($group in $subgroups) {
     Foreach ($remove in $removes) {
     Remove-ADGroupMember -Identity $group -Members $remove -Confirm:$false
     Write-Host "Disabled Account $remove removed from $group"
     }
}

I ran my script, watched the output, marveled at my own genius, and sent my boss a list of the users I had removed.  An hour or two later my manager pinged me to say that when he looks in the application’s portal he sees that most of the disabled users are gone but there are a couple hundred that still show up as having a license. Hmmmmm.

I reviewed my logic and found the problem. I didn’t do a recursive search on the sub-groups. I opened ADUC and looked, sure enough, there were a ton of nested sub-groups under the two I was told about and some of the users appeared to be in multiple groups. So even though I had removed them from the top-level they were still consuming a license through their membership in a nested group.

“Easy enough to fix”, my internal dialog told me. I just added the -recursive parameter to my sub-group searches and ran my script again.  Well, that didn’t work. After some trial and error, I was able to determine that the recursive parameter of the cmdlet does find all of the user objects no matter how deep they are. However, it only returns the first layer of group objects. Now what?

I needed my code to say, “if a group is returned run the search again” in a loop until no more groups were returned. I was disappointed that none of my Internet searches turned up working code. I ended up writing the function below. If you have landed on this page from similar circumstances, I hope it helps you out of your jam.


## Get-ADNestedGroups
## Author: Kevin Trent, https://whatdouknow.com, kevin-trent@hotmail.com
## Distribute freely but please leave author's information attached

function Get-ADNestedGroups {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]$Group
)

## Find all parent group objects
$members = Get-ADGroupMember -Identity $Group

## Array to hold each child group
$ChildGroups = @()

## Foreach loop to find all objects in child groups
## If any objects are groups we call the function again
## Will loop until no more groups are found
## Displays each group name on screen and adds to childgroups array

     Foreach ($member in $members){
          If ($member.objectClass -eq "Group"){
          $ChildGroups += $member.name|where {$_ -ne $null}
          Write-Host $member.name|where {$_ -ne $null}
          Get-ADNestedGroups -Group $member
          }
      }
#Outputs the contents of the childgroups array to text file
$ChildGroups|out-file $env:userprofile\documents\SubGroups.txt
}

P.S.

If you are trying to run the first script I started my story with; replace the subgroup arrary and variables with the function. Then change the subgroups variable in the nested foreach to childgroups.