Active Directory

PowerShell: Mapping GPOs to their Linked Organizational Units

Posted on Updated on

As an Active Directory environment grows, keeping track of where specific Group Policy Objects (GPOs) are linked becomes a significant challenge. The “Group Policy Management Console” (GPMC) is great for looking at one GPO at a time, but if you need a bird’s-eye view of your entire inheritance structure, you need automation.

This PowerShell script sweeps through all Organizational Units (OUs), identifies the unique GUIDs of linked policies, resolves those GUIDs into human-readable GPO names, and exports the mapping to a CSV file.


The PowerShell Script

Before running, create a folder at C:\temp\GroupPolicyandLinkedOU\. This script requires the Active Directory and Group Policy modules (included with RSAT).

PowerShell
# Initialize the output file with headers
$Header = "GPO_Name;OU_Name;OU_DistinguishedName"
$Path = "C:\temp\GroupPolicyandLinkedOU\out.csv"
if (!(Test-Path "C:\temp\GroupPolicyandLinkedOU\")) { New-Item -ItemType Directory -Path "C:\temp\GroupPolicyandLinkedOU\" }
$Header | Out-File $Path
# Get all OUs with their linked GPO attributes
$policies = Get-ADOrganizationalUnit -Filter * -Properties LinkedGroupPolicyObjects
$policies | ForEach-Object {
$OUName = $_.Name
$OUDN = $_.DistinguishedName
$LinkedGPOs = $_.LinkedGroupPolicyObjects
foreach($LinkedGPO in $LinkedGPOs) {
# Extract the GUID from the DistinguishedName string
# String format is usually: cn={GUID},cn=policies,cn=system,DC=domain...
$GUID = $LinkedGPO.Split(",")[0].Replace("cn={","").Replace("}","").Replace("CN={","")
try {
# Resolve the GUID to a friendly Display Name
$GPO = Get-GPO -Guid $GUID
$msg = "$($GPO.DisplayName);$OUName;$OUDN"
# Output to console and file
Write-Host "Mapped: $($GPO.DisplayName) -> $OUName" -ForegroundColor Cyan
$msg | Out-File $Path -Append
}
catch {
Write-Warning "Could not resolve GPO GUID: $GUID linked at $OUName"
}
}
}

How it Works

  • LinkedGroupPolicyObjects Property: The script looks at the raw attribute on the OU object. In Active Directory, links aren’t stored as names; they are stored as the DistinguishedName of the GPO container, which includes the GUID.
  • String Manipulation: The script uses .Split and .Replace to strip away the LDAP syntax, leaving only the raw GUID string.
  • Get-GPO -Guid: This cmdlet takes that raw ID and queries the domain for the actual GPO metadata, allowing us to retrieve the DisplayName.
  • Semicolon Delimited: The output uses ; as a delimiter. When opening the file in Excel, you can easily use “Text to Columns” to separate the data into clean fields.

Why Use This Script?

  1. Inheritance Audits: Quickly see if a legacy GPO is linked to an OU it shouldn’t be.
  2. Troubleshooting: If a user is getting a strange setting, you can search the CSV for their OU and see every policy applied.
  3. Clean-up: Identify “ghost” links—SIDs/GUIDs that remain linked to an OU even though the GPO itself has been deleted.

#PowerShell #ActiveDirectory #GroupPolicy #SysAdmin #WindowsServer #ITAutomation #LazyAdmin #TechTips #ITPro #Infrastructure

PowerShell: Audit Local Administrators on Remote Servers

Posted on Updated on

One of the most common security risks in a Windows environment is “Privilege Creep”—where users or service accounts are added to the Local Administrators group and never removed. Manually checking every server is impossible, and Group Policy Preferences don’t always show you the “extra” accounts that might have been added manually.

This PowerShell script allows you to sweep your network, query any local group (Administrators, Remote Desktop Users, etc.), and categorize the members into Local vs. Domain accounts in a clean CSV report.


The PowerShell Script

Save this code as Get-LocalGroupMembers.ps1. It uses the [ADSI] (Active Directory Service Interfaces) provider to connect to the local SAM database of remote computers, which is highly compatible across different Windows versions.

PowerShell
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]$ComputerName = $env:ComputerName,
[Parameter()]
[string]$LocalGroupName = "Administrators",
[Parameter()]
[string]$OutputDir = "C:\temp\localadmin\"
)
Begin {
# Ensure directory exists and initialize CSV
if (!(Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir }
$OutputFile = Join-Path $OutputDir "LocalGroupMembers.csv"
Add-Content -Path $OutPutFile -Value "ComputerName, LocalGroupName, Status, MemberType, MemberDomain, MemberName"
}
Process {
ForEach($Computer in $ComputerName) {
Write-Host "Working on $Computer..." -ForegroundColor Cyan
If(!(Test-Connection -ComputerName $Computer -Count 1 -Quiet)) {
Add-Content -Path $OutputFile -Value "$Computer,$LocalGroupName,Offline"
Continue
} else {
try {
$group = [ADSI]"WinNT://$Computer/$LocalGroupName"
$members = @($group.Invoke("Members"))
if(!$members) {
Add-Content -Path $OutputFile -Value "$Computer,$LocalGroupName,NoMembersFound"
continue
}
}
catch {
Add-Content -Path $OutputFile -Value "$Computer,,FailedToQuery"
Continue
}
foreach($member in $members) {
try {
$MemberName = $member.GetType().Invokemember("Name","GetProperty",$null,$member,$null)
$MemberType = $member.GetType().Invokemember("Class","GetProperty",$null,$member,$null)
$MemberPath = $member.GetType().Invokemember("ADSPath","GetProperty",$null,$member,$null)
# Determine if member is Local or Domain
if($MemberPath -match "^Winnt\:\/\/(?<domainName>\S+)\/(?<CompName>\S+)\/") {
$MemberType = if($MemberType -eq "User") { "LocalUser" } else { "LocalGroup" }
$MemberDomain = $matches["CompName"]
} elseif($MemberPath -match "^WinNT\:\/\/(?<domainname>\S+)/") {
$MemberType = if($MemberType -eq "User") { "DomainUser" } else { "DomainGroup" }
$MemberDomain = $matches["domainname"]
} else {
$MemberType = "Unknown"; $MemberDomain = "Unknown"
}
Add-Content -Path $OutPutFile -Value "$Computer, $LocalGroupName, SUCCESS, $MemberType, $MemberDomain, $MemberName"
} catch {
Add-Content -Path $OutputFile -Value "$Computer,,FailedQueryMember"
}
}
}
}
}

How to Use This Script

Audit All Servers from a List

Create a servers.txt file with your hostnames and run:

PowerShell
.\Get-LocalGroupMembers.ps1 -ComputerName (Get-Content C:\temp\servers.txt) -OutputDir C:\temp\Reports\

Query a Specific Group (e.g., Remote Desktop Users)

PowerShell
.\Get-LocalGroupMembers.ps1 -ComputerName "SRV-PROD-01" -LocalGroupName "Remote Desktop Users"

Key Benefits

  • Member Classification: The script identifies if an account is a LocalUser or a DomainUser, which is vital for identifying accounts that shouldn’t be there.
  • Offline Handling: It pings the computer first to prevent the script from hanging on a dead connection.
  • ADSI Speed: Using [ADSI] (WinNT provider) is often faster than using WMI for specific group queries and doesn’t require WinRM to be enabled like Invoke-Command.

VBScript: Batch Audit Service Status Across Multiple Windows Servers

Posted on Updated on

Keeping track of critical services—like SQL, IIS, or Print Spooler—across a large server farm is a common headache for admins. While PowerShell is the modern go-to, many legacy environments and specific automation workflows still rely on the reliability of VBScript and WMI (Windows Management Instrumentation).

This script allows you to pull a full inventory of every service on a list of servers, including their start mode (Automatic/Manual), current state (Running/Stopped), and the Service Account being used.


Prerequisites & Setup

  1. Create the workspace: Create a folder named C:\Temp\ServiceDetails.
  2. The Server List: Create a file named Servers.txt in that folder. List your server names or IP addresses, one per line.
  3. Permissions: You must run this script from an account that has Local Administrator rights on all target servers to query WMI.

The VBScript Solution

Save the code below as ServiceDetails.vbs in your C:\Temp\ServiceDetails folder.

VBScript
' --- START OF SCRIPT ---
ServerList = "C:\Temp\ServiceDetails\Servers.txt"
arrServices = Array("") ' Leave empty to get all services
Dim objFSO : Set objFSO = CreateObject("Scripting.FileSystemObject")
Dim objOut : Set objOut = objFSO.CreateTextFile("C:\Temp\ServiceDetails\ServiceQuery.csv")
arrComputers = Split(objFSO.OpenTextFile(ServerList).ReadAll, vbNewLine)
' Write CSV Headers
ObjOut.WriteLine "SERVER, SERVICE DISPLAY NAME, SERVICE STARTMODE, SERVICE STATUS, SERVICE ACCOUNT"
For Each strComputer In arrComputers
If Trim(strComputer) <> "" Then
strAlive = IsAlive(strComputer)
objFound = 0
If strAlive = "Alive" Then
On Error Resume Next
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
If Err.Number <> 0 Then
ObjOut.WriteLine strComputer & ", WMI ERROR, N/A, N/A, N/A"
Err.Clear
Else
Set colItems = objWMIService.ExecQuery("SELECT * FROM Win32_Service")
For Each objItem In colItems
ObjOut.WriteLine strComputer & "," & objItem.DisplayName & "," & objItem.StartMode & "," & objItem.State & "," & objItem.StartName
objFound = 1
Next
End If
Else
ObjOut.WriteLine strComputer & "- UnResolved, N/A, N/A, N/A, N/A"
End If
End If
Next
objOut.Close
MsgBox "Service Export Complete!", 64, "LazyAdmin Notification"
' Function to Ping the server before attempting WMI connection
Function IsAlive(strComputer)
Set WshShell = WScript.CreateObject("WScript.Shell")
Set objExecObject = WshShell.Exec("%comspec% /c ping -n 1 -w 500 " & strComputer)
strText = objExecObject.StdOut.ReadAll()
If Instr(strText, "Reply from") > 0 Then
IsAlive = "Alive"
Else
IsAlive = "Dead"
End If
End Function

How it Works

  • WMI (Win32_Service): The script connects to the root\CIMV2 namespace on the remote machine to query the Win32_Service class. This is the same data you see in services.msc.
  • The Ping Check: Before trying to connect (which can be slow if a server is down), the IsAlive function pings the host. This significantly speeds up the script if you have offline servers in your list.
  • CSV Output: All data is appended to a .csv file, making it ready for a pivot table in Excel to find services running under old service accounts or identifying disabled critical services.

#SysAdmin #WindowsServer #VBScript #WMI #ITAutomation #ServerManagement #TechTips #LazyAdmin #Infrastructure #ITAudit

PowerShell Script: Quickly Convert SIDs to Usernames

Posted on Updated on

Have you ever looked at a security log or a orphaned folder permission and seen a string like S-1-5-21-3623811015-3361044348-30300820-1013? Those are SIDs (Security Identifiers). While they are great for the Windows OS, they are nearly impossible for humans to read.

If you have a list of these SIDs from an audit or a log file, you don’t have to look them up one by one. This PowerShell script will take a bulk list of SIDs and “translate” them into readable Usernames (UIDs).


The PowerShell Script

Save this script as SIDtoUID.ps1. It uses the .NET SecurityIdentifier class to perform the translation locally or against your Active Directory domain.

PowerShell
# Create or clear the output file
Out-File UID.txt
# Loop through each SID in the source text file
foreach ($SID in (Get-Content SID.txt))
{
# Create a SID object
$objSID = New-Object System.Security.Principal.SecurityIdentifier ($SID)
Try
{
# Attempt to translate the SID to an NT Account name
$objUser = $objSID.Translate( [System.Security.Principal.NTAccount])
# Append the Username to the output file
$objUser.Value >> UID.txt
Write-Host "Translated: $SID -> $($objUser.Value)" -ForegroundColor Green
}
Catch
{
# If translation fails (e.g., deleted account), keep the original SID
$SID >> UID.txt
Write-Warning "Failed to translate: $SID"
}
}

How to Use It

  1. Create your input: Create a file named SID.txt in the same folder as the script. Paste your SIDs there, one per line.
  2. Run the script: Open PowerShell and execute .\SIDtoUID.ps1.
  3. Check your results: A new file named UID.txt will appear, containing the translated usernames in the same order as your original list.

Why do SIDs sometimes fail to translate?

In the Catch block of the script, we tell PowerShell to just output the original SID if it can’t find a match. This usually happens for two reasons:

  • Deleted Accounts: The user or group no longer exists in Active Directory, leaving behind an “orphaned” SID.
  • Connectivity: Your machine cannot reach the Domain Controller to perform the lookup.

#PowerShell #ActiveDirectory #SysAdmin #ITPro #CyberSecurity #WindowsServer #Automation #LazyAdmin #TechTips #ITAudit

PowerShell Script: Export User Group Memberships to CSV

Posted on Updated on

Auditing which users belong to which groups is one of the most frequent requests for a System Administrator. Whether it’s for a security audit, a helpdesk ticket, or a “copy permissions” request, digging through the Member Of tab in Active Directory is slow and prone to error.

This PowerShell script simplifies the process by generating a clean, object-based list of memberships that you can easily export to CSV, HTML, or plain text.


The PowerShell Script

Save the following code as Get-UserGroupMembership.ps1. It is designed to handle single users, lists from text files, or entire Organizational Units (OUs) via the pipeline.

PowerShell
Param (
[Parameter(Mandatory=$true,ValueFromPipeLine=$true)]
[Alias("ID","Users","Name")]
[string[]]$User
)
Begin {
Try { Import-Module ActiveDirectory -ErrorAction Stop }
Catch { Write-Host "Unable to load Active Directory module. Is RSAT installed?"; Break }
}
Process {
ForEach ($U in $User) {
Try {
$UN = Get-ADUser $U -Properties MemberOf
$Groups = ForEach ($Group in ($UN.MemberOf)) {
(Get-ADGroup $Group).Name
}
# Sort groups alphabetically for a cleaner report
$Groups = $Groups | Sort
ForEach ($Group in $Groups) {
New-Object PSObject -Property @[ordered]@{
User = $UN.Name
Group = $Group
}
}
}
Catch {
Write-Warning "Could not find user: $U"
}
}
}

How to Use the Script

1. Single User Lookup

To quickly see the groups for one specific user:

PowerShell

.\Get-UserGroupMembership.ps1 -User "John.Doe"

2. Bulk Export from a Text File

If you have a list of usernames in users.txt, use this command to generate a full CSV report:

PowerShell

Get-Content C:\Temp\users.txt | .\Get-UserGroupMembership.ps1 | Export-CSV C:\Temp\UserMemberships.csv -NoTypeInformation

3. Audit an Entire OU

To see the memberships for every user within a specific department or location:

PowerShell

Get-ADUser -Filter * -SearchBase "OU=Users,DC=yourdomain,DC=local" | .\Get-UserGroupMembership.ps1 | Export-CSV C:\audit_output.csv -NoTypeInformation

Why This Method Beats the GUI

  • Alphabetical Sorting: Groups are presented A-Z, making it much easier to read than the random order in ADUC.
  • Pipeline Support: Because it outputs a PSObject, you can pipe it directly into ConvertTo-HTML for a report or Out-GridView for an interactive window.
  • Automation Ready: You can schedule this script to run weekly to maintain a “snapshot” of your environment’s security posture.

#PowerShell #ActiveDirectory #SysAdmin #WindowsServer #ITAdmin #CyberSecurity #Automation #LazyAdmin #TechTips #ITAudit

Automating Active Directory: Export All AD Groups and Members to CSV

Posted on Updated on

Auditing Active Directory groups is a fundamental part of identity management. Whether you are performing a quarterly security review or preparing for a domain migration, knowing exactly who is in which group—and what the scope of those groups is—is essential.

This PowerShell script does more than just list group names; it iterates through every group in your domain, identifies the members (skipping disabled users to keep your data clean), and exports everything into a dated CSV file.


The PowerShell Script

Save this script as ADGroupsExport.ps1 in C:\Temp\ExportADgroups. Ensure you are running this from a machine with the RSAT (Remote Server Administration Tools) installed and logged in with a domain account that has read permissions.

PowerShell
# Get year and month for the filename
$DateTime = Get-Date -f "yyyy-MM"
# Set CSV file destination
$CSVFile = "C:\Temp\ExportADgroups\AD_Groups_"+$DateTime+".csv"
if (!(Test-Path "C:\Temp\ExportADgroups")) { New-Item -ItemType Directory -Path "C:\Temp\ExportADgroups" }
$CSVOutput = @()
# Fetch all AD groups
$ADGroups = Get-ADGroup -Filter *
$i = 0
$tot = $ADGroups.count
foreach ($ADGroup in $ADGroups) {
$i++
$status = "{0:N0}" -f ($i / $tot * 100)
Write-Progress -Activity "Exporting AD Groups" -status "Processing Group $i of $tot : $status% Completed" -PercentComplete ($i / $tot * 100)
$Members = ""
# Fetch members and filter for enabled objects
$MembersArr = Get-ADGroup $ADGroup.DistinguishedName -Properties Member | Select-Object -ExpandProperty Member
if ($MembersArr) {
foreach ($Member in $MembersArr) {
$ADObj = Get-ADObject -Filter "DistinguishedName -eq '$Member'" -Properties Enabled
# Skip disabled users to keep the report relevant
if ($ADObj.ObjectClass -eq "user" -and $ADObj.Enabled -eq $false) {
continue
}
$Members = $Members + "," + $ADObj.Name
}
if ($Members) {
$Members = $Members.Substring(1)
}
}
# Create ordered hash table for clean CSV columns
$HashTab = [ordered]@{
"Name" = $ADGroup.Name
"Category" = $ADGroup.GroupCategory
"Scope" = $ADGroup.GroupScope
"Members" = $Members
}
$CSVOutput += New-Object PSObject -Property $HashTab
}
# Sort by name and export
$CSVOutput | Sort-Object Name | Export-Csv $CSVFile -NoTypeInformation
Write-Host "Export Complete: $CSVFile" -ForegroundColor Green

Key Features of this Script

  • Progress Bar: Since large domains can take a long time to process, the Write-Progress bar gives you a real-time percentage of the completion.
  • Clean Membership Lists: The script concatenates all members into a single “Members” column, separated by commas, making it easy to read in Excel.
  • Disabled User Filtering: It intelligently checks the Enabled status of user objects. If a user is disabled, they are omitted from the report to focus on active security risks.
  • Scope & Category: Clearly identifies if a group is Security vs. Distribution and Global vs. Universal.

#ActiveDirectory #PowerShell #SysAdmin #ITAutomation #WindowsServer #IdentityManagement #LazyAdmin #TechTips #Reporting #CyberSecurity

Mapping Your AD: VBScript to List OUs in Parent-Child Order | Lazy Admin Blog

Posted on Updated on

When you’re managing a complex Active Directory environment, getting a clear “birds-eye view” of your structure is essential. While the Active Directory Users & Computers (dsa.msc) snap-in is great for manual navigation, sometimes you need a flat text output that preserves the visual hierarchy of your Organizational Units (OUs).

The following VBScript crawls your LDAP directory and mirrors the parent-child nesting you see in your GUI tools.


📜 The Script: ListAllOUs_ParentChild.vbs

Copy the code below and save it as ListAllOUs_ParentChild.vbs.

VBScript
Option Explicit
Const ADS_SCOPE_SUBTREE = 2
Dim ObjConn, ObjRS, ObjRootDSE
Dim StrSQL, StrDomName, ObjOU
' Get the local domain name
Set ObjRootDSE = GetObject("LDAP://RootDSE")
StrDomName = Trim(ObjRootDSE.Get("DefaultNamingContext"))
Set ObjRootDSE = Nothing
' SQL Query to find OUs (Excluding Domain Controllers)
StrSQL = "Select Name, ADsPath From 'LDAP://" & StrDomName & "' Where ObjectCategory = 'OrganizationalUnit' And Name <> 'Domain Controllers'"
Set ObjConn = CreateObject("ADODB.Connection")
ObjConn.Provider = "ADsDSOObject"
ObjConn.Open "Active Directory Provider"
Set ObjRS = CreateObject("ADODB.Recordset")
ObjRS.Open StrSQL, ObjConn
If Not ObjRS.EOF Then
ObjRS.MoveLast: ObjRS.MoveFirst
WScript.Echo vbNullString
WScript.Echo "Total OU: " & Trim(ObjRS.RecordCount)
WScript.Echo "==================="
WScript.Echo vbNullString
While Not ObjRS.EOF
Set ObjOU = GetObject(Trim(ObjRS.Fields("ADsPath").Value))
' Check if it's a top-level Parent OU
If StrComp(Right(Trim(ObjOU.Parent), Len(Trim(ObjOU.Parent)) - 7), StrDomName, VbTextCompare) = 0 Then
WScript.Echo "Parent OU: " & Trim(ObjRS.Fields("Name").Value)
GetChild(ObjOU)
End If
ObjRS.MoveNext
Set ObjOU = Nothing
Wend
End If
ObjRS.Close: Set ObjRS = Nothing
ObjConn.Close: Set ObjConn = Nothing
' Subroutine to find first-level children
Private Sub GetChild(ThisObject)
Dim ObjChild
For Each ObjChild In ThisObject
If StrComp(Trim(ObjChild.Class), "OrganizationalUnit", VbTextCompare) = 0 Then
WScript.Echo vbTab & ">> Child OU: " & Right(Trim(ObjChild.Name), Len(Trim(ObjChild.Name)) - 3)
GetGrandChild (ObjChild.ADsPath)
End If
Next
End Sub
' Recursive subroutine to find all nested children
Private Sub GetGrandChild (ThisADsPath)
Dim ObjGrand, ObjItem
Set ObjGrand = GetObject(ThisADsPath)
For Each ObjItem In ObjGrand
If StrComp(Trim(ObjItem.Class), "OrganizationalUnit", VbTextCompare) = 0 Then
WScript.Echo vbTab & vbTab & ">> Child OU: " & Right(Trim(ObjItem.Name), Len(Trim(ObjItem.Name)) - 3)
GetGrandChild Trim(ObjItem.ADsPath)
End If
Next
Set ObjGrand = Nothing
End Sub

🚀 How to Execute

To run this script correctly and avoid “Windows Script Host” popup boxes for every line, you must use the command-line engine (CScript).

Example Command: CScript /NoLogo ListAllOUs_ParentChild.vbs

Output Preview:

Parent OU: Sales

Child OU: North_Region

Child OU: South_Region

>> Child OU: Retail_Stores

#ActiveDirectory #SysAdmin #WindowsServer #Automation #VBScript #ITAdmin #LazyAdmin #LDAP #DirectoryServices #InfrastructureAsCode #Scripting #ADUC

Deep Audit: Listing Nested Active Directory Group Members via VBScript | Lazy Admin Blog

Posted on Updated on

Have you ever looked at a “Domain Admins” group and thought it looked suspiciously small? The culprit is usually nesting. Standard AD queries often fail to “recurse,” meaning they show you the subgroup but not the people inside it.

This script, ListGroupMembers_IncludingNested.vbs, uses a recursive function to dive into every sub-group and extract the actual users, ensuring your security audits are 100% accurate.

The Script: How it Works

The script utilizes a Dictionary Object to keep track of groups it has already scanned. This is a critical “Lazy Admin” safety feature—it prevents the script from getting stuck in an infinite loop if two groups are members of each other.

Usage Instructions

  1. Copy the code below into Notepad.
  2. Edit the StrGroupName variable to match your target group.
  3. Save the file as ListGroupMembers.vbs.
  4. Run it from the command prompt using cscript ListGroupMembers.vbs.
VBScript
' -- Save as ListGroupMembers_IncludingNested.vbs
Option Explicit
Dim ObjRootDSE, ObjConn, ObjRS, ObjCustom
Dim StrDomainName, StrGroupName, StrSQL, StrGroupDN, StrEmptySpace
Set ObjRootDSE = GetObject("LDAP://RootDSE")
StrDomainName = Trim(ObjRootDSE.Get("DefaultNamingContext"))
' -- Edit the line below with your Group Name
StrGroupName = "YourGroupNameHere"
StrSQL = "Select ADsPath From 'LDAP://" & StrDomainName & "' Where ObjectCategory = 'Group' AND Name = '" & StrGroupName & "'"
Set ObjConn = CreateObject("ADODB.Connection")
ObjConn.Provider = "ADsDSOObject": ObjConn.Open "Active Directory Provider"
Set ObjRS = ObjConn.Execute(StrSQL)
If ObjRS.EOF Then
WScript.Echo "Group not found: " & StrGroupName
Else
StrGroupDN = Trim(ObjRS.Fields("ADsPath").Value)
Set ObjCustom = CreateObject("Scripting.Dictionary")
GetAllNestedMembers StrGroupDN, " ", ObjCustom
End If

Why VBScript in 2026?

While PowerShell is the modern standard, many legacy environments and automated scheduled tasks still rely on VBScript because it requires zero execution policy changes and runs natively on every Windows machine since Server 2000. It is the “Old Reliable” of the AD world.

Key Features of this Script

  • Recursive Discovery: It doesn’t just stop at the first layer.
  • Class Identification: Clearly marks if a member is a User, Computer, or another Group.
  • Loop Protection: Uses the Scripting.Dictionary to escape circular nesting traps.

#ActiveDirectory #WindowsServer #CyberSecurity #SysAdmin #ITAudit #VBScript #Automation #LazyAdmin #TechArchive

The Ultimate Server Audit: Deep-Dive Inventory to Excel (VBScript) | Lazy Admin Blog

Posted on Updated on

If you are facing a massive compliance audit or a data center migration, “basic” info isn’t enough. You need to know exactly what is under the hood: What roles are active? How much disk space is actually left? What random software was installed three years ago?

This VBScript is a one-stop-shop. It checks network connectivity and then scrapes WMI and the Registry to build a massive, multi-column Excel report.

What this Script Collects:

  • Hardware: Manufacturer, Model, CPU Type, and RAM (converted to GB).
  • OS Details: Version, Caption, and the exact Installation Date.
  • Storage: Total Size vs. Free Space (with a Red-Alert highlight if space is < 20%).
  • Network: DHCP status, IP, Subnet, and Gateway.
  • Software & Roles: Every Windows Server Role/Feature and every application listed in the Registry’s Uninstall key (including version and install date).

Preparation

  1. Directory: Create C:\Temp on your local machine.
  2. Input: Create a file named ServerList.txt in C:\Temp with your server names (one per line).
  3. Excel: Ensure Microsoft Excel is installed.

The Script: Server_Inventory.vbs

VBScript
' Save as Server_Inventory.vbs in C:\Temp
' lazyadminblog.com - Ultimate Inventory Script
On Error Resume Next
dtmDate = Date
strMonth = Month(Date)
strDay = Day(Date)
strYear = Right(Year(Date),2)
strFileName = "C:\Temp\ServerInventory_" & strMonth & "-" & strDay & "-" & strYear & ".xls"
Set objExcel = CreateObject("Excel.Application")
objExcel.Visible = True
objExcel.Workbooks.Add
Set fso1 = CreateObject("Scripting.FileSystemObject")
Set pcfile = fso1.OpenTextFile("C:\Temp\ServerList.txt",1)
Wscript.Echo "Audit in progress... Please wait!"
'--- Setup Header Row ---
Sub SetupHeader(col, text)
objExcel.Cells(1, col).Value = text
objExcel.Cells(1, col).Font.Colorindex = 2
objExcel.Cells(1, col).Font.Bold = True
objExcel.Cells(1, col).Interior.ColorIndex = 23
objExcel.Cells(1, col).Alignment = -4108 ' Center
End Sub
SetupHeader 1, "Computer Name"
SetupHeader 2, "Manufacturer"
SetupHeader 3, "Model"
SetupHeader 4, "RAM (GB)"
SetupHeader 5, "Operating System"
SetupHeader 6, "Installed Date"
SetupHeader 7, "Processor"
SetupHeader 8, "Drive"
SetupHeader 9, "Drive Size (GB)"
SetupHeader 10, "Free Space (GB)"
SetupHeader 11, "Adapter Description"
SetupHeader 12, "DHCP Enabled"
SetupHeader 13, "IP Address"
SetupHeader 14, "Subnet"
SetupHeader 15, "Gateway"
SetupHeader 16, "Roles & Features"
SetupHeader 17, "Installed Software"
SetupHeader 18, "Install Date"
SetupHeader 19, "Version"
SetupHeader 20, "Size"
y = 2
Do While Not pcfile.AtEndOfStream
computerName = pcfile.ReadLine
Err.Clear
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & computerName & "\root\cimv2")
If Err.Number = 0 Then
' Fetch Queries
Set colSettings = objWMIService.ExecQuery("SELECT * FROM Win32_ComputerSystem")
Set colOSSettings = objWMIService.ExecQuery("SELECT * FROM Win32_OperatingSystem")
Set colProcSettings = objWMIService.ExecQuery("SELECT * FROM Win32_Processor")
Set colDiskSettings = objWMIService.ExecQuery("Select * from Win32_LogicalDisk Where DriveType=3")
Set colAdapters = objWMIService.ExecQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True")
For Each objComputer In colSettings
strManufacturer = objComputer.Manufacturer
strModel = objComputer.Model
strRAM = FormatNumber((objComputer.TotalPhysicalMemory / (1024^3)), 2)
For Each objOS In colOSSettings
strOS = objOS.Caption
OSinstDate = CDate(Mid(objOS.InstallDate,1,4)+"/"+Mid(objOS.InstallDate,5,2)+"/"+Mid(objOS.InstallDate,7,2))
For Each objProc In colProcSettings
strProc = objProc.Name
' Populate Static Info
objExcel.Cells(y, 1).Value = computerName
objExcel.Cells(y, 2).Value = strManufacturer
objExcel.Cells(y, 3).Value = strModel
objExcel.Cells(y, 4).Value = strRAM
objExcel.Cells(y, 5).Value = strOS
objExcel.Cells(y, 6).Value = OSinstDate
objExcel.Cells(y, 7).Value = strProc
' Drive Logic
a = y
For Each objDisk In colDiskSettings
objExcel.Cells(a, 8).Value = objDisk.DeviceID
sz = objDisk.Size / (1024^3)
fr = objDisk.FreeSpace / (1024^3)
objExcel.Cells(a, 9).Value = FormatNumber(sz, 2)
objExcel.Cells(a, 10).Value = FormatNumber(fr, 2)
If fr < (sz * 0.2) Then objExcel.Cells(a, 10).Interior.ColorIndex = 3 ' Low Space Alert
a = a + 1
Next
' Network Logic
b = y
For Each objAdapter In colAdapters
objExcel.Cells(b, 11).Value = objAdapter.Description
objExcel.Cells(b, 12).Value = objAdapter.DHCPEnabled
If Not IsNull(objAdapter.IPAddress) Then objExcel.Cells(b, 13).Value = objAdapter.IPAddress(0)
If Not IsNull(objAdapter.IPSubnet) Then objExcel.Cells(b, 14).Value = objAdapter.IPSubnet(0)
If Not IsNull(objAdapter.DefaultIPGateway) Then objExcel.Cells(b, 15).Value = objAdapter.DefaultIPGateway(0)
b = b + 1
Next
' Roles & Features
x = y
Set colRoleFeatures = objWMIService.ExecQuery("Select * from Win32_ServerFeature")
If colRoleFeatures.Count > 0 Then
For Each objRole In colRoleFeatures
objExcel.Cells(x, 16).Value = objRole.Name
x = x + 1
Next
Else
objExcel.Cells(x, 16).Value = "None Found"
End If
' Software Registry Scan
s = y
Const HKLM = &H80000002
strKey = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"
Set objReg = GetObject("winmgmts://" & computerName & "/root/default:StdRegProv")
objReg.EnumKey HKLM, strKey, arrSubkeys
For Each strSubkey In arrSubkeys
objReg.GetStringValue HKLM, strKey & strSubkey, "DisplayName", strVal1
If strVal1 <> "" Then
objExcel.Cells(s, 17).Value = strVal1
objReg.GetStringValue HKLM, strKey & strSubkey, "InstallDate", strVal2
objExcel.Cells(s, 18).Value = strVal2
objReg.GetDWORDValue HKLM, strKey & strSubkey, "VersionMajor", vMaj
objReg.GetDWORDValue HKLM, strKey & strSubkey, "VersionMinor", vMin
objExcel.Cells(s, 19).Value = vMaj & "." & vMin
s = s + 1
End If
Next
' Advance Row to next available empty spot
y = a
If b > y Then y = b
If x > y Then y = x
If s > y Then y = s
y = y + 1 ' Buffer line
Next
Next
Next
Else
objExcel.Cells(y, 1).Value = computerName
objExcel.Cells(y, 2).Value = "OFFLINE"
objExcel.Cells(y, 2).Interior.ColorIndex = 3
y = y + 1
End If
Loop
' Final Formatting
For col = 1 To 20: objExcel.Columns(col).AutoFit: Next
objExcel.ActiveWorkbook.SaveAs strFileName
Wscript.Echo "Complete! Report saved to " & strFileName

Why it’s a Game Changer

  • The “Red Flag” Feature: It automatically highlights any disk with less than 20% free space in Red. This instantly tells you which servers need urgent cleanup.
  • Software Archeology: Most scripts skip software lists because they are messy. This script pulls directly from the Uninstall registry keys, capturing even the apps that don’t show up in standard WMI queries.
  • Intelligent Row Management: Because software, roles, and disks all have different counts, the script calculates the “max row” used for each server and jumps to the next clear space for the next machine.

Stop Hunting for Web Servers: How to Auto-Discover Every IIS Instance in Your Domain | Lazy Admin Blog

Posted on Updated on

IIS Discovery

Have you ever been asked for a list of every active web server in your environment, only to realize your documentation is six months out of date? You could check your DNS records manually, or you could let PowerShell do the detective work for you.

This script scans your Active Directory for Windows Servers, checks if the World Wide Web Publishing Service (W3SVC) is actually running, and then pulls a deep-profile of the hardware, OS, and network configuration for every active hit.

The Setup

  1. Create the workspace: Create a folder at C:\Temp\ServersRunningIIS.
  2. Prepare the list: The script will automatically generate a list of all Windows Servers from AD, but ensure you have the Active Directory PowerShell module installed.
  3. Run with Privileges: Since the script uses WMI to query remote system info (RAM, OS Version, etc.), run your PowerShell ISE or Console as a Domain Admin.

The PowerShell Script

PowerShell
# Script: IIS Server Discovery & Profiler
# Location: lazyadminblog.com
# Purpose: Identify active IIS nodes and collect hardware/OS specs
Import-Module ActiveDirectory
# 1. Harvest all Windows Servers from AD
Write-Host "Gathering server list from Active Directory..." -ForegroundColor Cyan
$servers = Get-ADComputer -Filter {operatingsystem -Like "Windows server*"} | Select-Object -ExpandProperty Name
$servers | Out-File "C:\Temp\ServersRunningIIS\serverlist.txt"
# 2. Load the list for processing
$serversall = Get-Content "C:\Temp\ServersRunningIIS\serverlist.txt"
Start-Transcript -Path "C:\Temp\ServersRunningIIS\log_output.txt" -Append
foreach($vm in $serversall) {
try {
# Check if IIS Service (W3SVC) exists and is running
$iis = Get-WmiObject Win32_Service -ComputerName $vm -Filter "name='W3SVC'" -ErrorAction SilentlyContinue
if($iis.State -eq "Running") {
Write-Host "FOUND: IIS is active on $vm" -BackgroundColor DarkBlue -ForegroundColor DarkYellow
# Collect Network Info
$ipinfo = Get-WmiObject Win32_NetworkAdapterConfiguration -ComputerName $vm |
Where-Object {$_.IPEnabled -eq $true -and $_.IPAddress -like "1*"} | Select-Object -First 1
# Collect Hardware Info
$hwinfo = Get-WmiObject Win32_Computersystem -ComputerName $vm
# Collect OS Info
$osinfo = Get-WmiObject Win32_OperatingSystem -ComputerName $vm
# Flattening data for CSV-style output
$allinfo = "$($hwinfo.Name);$($hwinfo.Domain);$($ipinfo.IPAddress);$($ipinfo.IPSubnet);$($ipinfo.DefaultIPGateway);$($hwinfo.TotalPhysicalMemory);$($hwinfo.Manufacturer);$($hwinfo.Model);$($osinfo.Caption);$($osinfo.OSArchitecture);$($osinfo.ServicePackMajorVersion);$($osinfo.SystemDrive);$($osinfo.Version)"
# Save results to our 'Running' list
$allinfo | Out-File "C:\Temp\ServersRunningIIS\RunningWebServers.txt" -Append
}
}
catch {
Write-Host "Could not connect to $vm" -ForegroundColor Red
}
}
Stop-Transcript
Write-Host "Audit Complete! Check C:\Temp\ServersRunningIIS\RunningWebServers.txt" -ForegroundColor Green

What’s inside the report?

The output file (RunningWebServers.txt) uses a semicolon (;) delimiter, making it easy to import into Excel. It captures:

  • Network: IP Address, Subnet, and Gateway.
  • Hardware: Manufacturer, Model, RAM, and Domain membership.
  • Software: OS Version, Architecture (x64/x86), and System Drive.

Lazy Admin Tip

If you want to open the results immediately in Excel, just rename the output file from .txt to .csv and use the “Text to Columns” feature in Excel with the semicolon as the separator!