Get Windows Server Information

Problem Statement:

There are times when having historical information on the configuration of a server is useful for trouble shooting. Or to check for readiness for some major upcoming change in the environment.

Goals:

  • Data gathering must be automated
  • Reports to be generated on an as-needed basis
  • Monthly reports to be generated and stored in a monthly folder
  • Reports must be in Microsoft Excel readable file format
  • Script must provide functions for gathering the following Windows server information
    • All Processes running at the time the information is collected
    • All users and Groups that are members of the Local Administrators Group
    • All Services configured at the time the information is collected
    • All User Profiles
    • Scheduled Task information

Solution Approach and Implementation:

Solution to be implemented using Microsoft Windows PowerShell version 5.1 with latest patches. The scripts to be deployed on a Windows server 2012 or later connected to the target network.

Required PowerShell Modules:

  • ActiveDirectory: This module is installed when you install the Remote Server Administration Tools (RSAT). As of Windows 10 RSAT is installed as a optional feature.
  • ScheduledTasks: This module is included with the base Windows PowerShell install.
  • Microsoft.PowerShell.Utility: Introduced with PowerShell 5.1. This module is included with the base Windows PowerShell 5.1 install.
  • NetTCPIP: This module is included with the base Windows PowerShell 5.1 install.
  • CimCmdlets: Introduced with PowerShell 5.1. This module is included with the base Windows PowerShell 5.1 install.
  • ImportExcel: Douglas Finke has created an amazing module for creating Excel workbooks, even when Excel is not installed on the system you run the script from. This is a must have for your pragmatic PowerShell tool box. I use it all the time. I’ve linked to it’s page on the PowerShell gallery.
  • PowerShell code contributed from the Internet:

Required .NET Framework classes:

Take Note

This script can take several hours to run, depending on the number of servers.

# start set the event source and start logging
New-EventLog -LogName "Application" -Source "CustomScripts" -ErrorAction SilentlyContinue
Write-EventLog -LogName "Application" -Source "CustomScripts" -EventId 1010 -EntryType Information -Message "Server Data Collection Started"
# end set the event source and start logging
# Start Folder structure
# Get the current date in the format YEAR-MONTH-DAY
$fdate = Get-Date -Format "yyyy-MM-dd"
# All monthly files
# will be stored in a folder for that month
# Now create the folder structure if it doesn't exist
# The key is that the New-Item command will not
# overwrite if creating a directory
$rootpath = "D:\PowerShell_Scripts"
$inpath = "$rootpath\serverdata\input"
$outpath = "$rootpath\serverdata\out\" + $fdate.Split('-')[0] + '\' + $fdate.Split('-')[1] +'\'
# Does not overwrite if $outpath exists
New-Item -Path $outpath -ItemType Directory -Force
# End Folder structure
# Start get servers from AD
# Array to hold the computer objects
$adSvrs = @()
# Filter out all the client operating systems
$OsFilter = "(OperatingSystem -notlike 'Windows 10*') -and (OperatingSystem -notlike 'Windows xp*') -and (OperatingSystem -notlike 'Windows 8.1*') -and (OperatingSystem -notlike 'Windows 7*')"
# Get subset of all available attributes. Will use DNSHostname to match
# with the CMDB data
$select = 'Name','DNSHostName','IPv4Address','OperatingSystem','Distinguishedname','managedby','PasswordLastSet','enabled','Description','MemberOf'
# set the SearchBase's
$srchBaseArray = @()
$srchBase1 = 'OU=...' #based on your AD OU structure and domain
$srchBase2 = 'OU=...' #based on your AD OU structure and domain
$srchBaseArray += $srchBase1
$srchBaseArray += $srchBase2
Foreach ($sb in $srchBaseArray) {
   $adSvrs += Get-ADComputer -filter $osfilter  -property * -SearchBase $sb | Select-Object $select
}
$adCount = $adSvrs.Count
$adOutString = "Have gotten $adCount server objects from AD"
Write-EventLog -LogName Application -Source "CustomScripts" -EventId 1010 -EntryType Information $adOutString
# End get the servers from AD.
# Credential must have been created using the same identity that
# the script will run under
$Cred = Import-CliXml -Path "${env:\userprofile}\server.Cred"
# Start Functions from the Internet
# From https://github.com/exevolution/Get-AllScheduledTasks/blob/master/Get-AllScheduledTasks.ps1
Function Connect-TaskScheduler {...}
Function Get-AllScheduledTasks {...}
Function Get-TaskSchedulerPaths {...}
Function Get-TaskSchedulerTasks {...}
# From https://gallery.technet.microsoft.com/scriptcenter/Get-LocalGroupMember-d2ddad6f
Function Get-LocalGroupMember {...}
# End Functions from the Internet
# Start collecting the server data
# Add properties to hold results of network test
$adSvrs | Add-Member -NotePropertyName 'WINRM' -NotePropertyValue ''
$adSvrs | Add-Member -NotePropertyName 'RPC' -NotePropertyValue ''
$adSvrs | Add-Member -NotePropertyName 'PING' -NotePropertyValue ''
# Begin looping through server list
foreach ($svr in $adSvrs){
    $allservices = $null
    $allprocesses = $null
    $alladministrators = $null
    $alluserprofiles = $null
    $members = $null
    $serverName = $svr.Name
# Create the path and filename for the server that is getting processed
    $file = $serverName + "_" + [String]$fdate + "_s-accounts_Report.xlsx"
    $fullpath = $outpath + $file
# Perform the network tests   
    $netTest = $null
    $test5985 = $null
    $test135 = $null
    $netTest = Test-Connection -ComputerName $serverName -Count 1 -Quiet
    $test5985 = (Test-NetConnection -port 5985 -ComputerName $serverName).TcpTestSucceeded
    $test135 = (Test-NetConnection -port 135 -ComputerName $serverName).TcpTestSucceeded
# Record network test results
    $svr.WINRM = $test5985
    $svr.RPC = $test135
    $svr.PING = $netTest
# Try a couple different methods to connect to the server See https://mikefrobbins.com/2012/09/20/targeting-down-level-clients-with-the-get-ciminstance-powershell-cmdlet/
    $cs1 = $cs2 = $null 
    $Opt = New-CimSessionOption -Protocol Dcom
    $cs = @()
    $cs1 = New-CimSession -ComputerName $serverName -Credential $cred
    $cs2 = New-CimSession -ComputerName $serverName -Credential $cred -SessionOption $opt
   $Tasks = @()
   if ($netTest) {
      $svr.onLine = $True
      $cs += $cs1
      $cs += $cs2
      if($cs1 -or $cs2) {
# Start get services
         $allServices = Get-CimInstance Win32_Service -CimSession $cs1 | select PSComputerName, DisplayName, StartName, State, StartMode
# if that fails try the other session to get the services
         If($null -eq $allServices) {
            $allServices = Get-CimInstance Win32_Service -CimSession $cs2 | select PSComputerName, DisplayName, StartName, State, StartMode
         }        
#End get services
#Start get processes
      $allProcesses = Get-CimInstance Win32_Process -CimSession $cs1 
      If($null -eq $allprocesses) {
# trying cs2 with DCOM option for processes
         $allProcesses = Get-CimInstance Win32_Process -CimSession $cs2 
      }
# Add a property to old the process owner that is gotten with next pass
      $allprocesses | Add-Member -NotePropertyName UserName -NotePropertyValue ''
      Foreach ($p in $allprocesses){
# Trying cs1 to get Process owner
         $p.UserName = '{0}\{1}' -f (Invoke-CimMethod -CimSession $cs1 -InputObject $p -MethodName GetOwner ).domain, (Invoke-CimMethod -CimSession $cs1 -InputObject $p  -MethodName GetOwner).user

# If user name was not retrieved
         if($p.UserName -eq '\'){
#Trying cs2 to get Process owner"
         $p.UserName = '{0}\{1}' -f (Invoke-CimMethod -CimSession $cs2 -InputObject $p -MethodName GetOwner ).domain, (Invoke-CimMethod -CimSession $cs2 -InputObject $p -MethodName GetOwner).user
          }
      }
# End get processes      
# Start get user profiles
      $allUserProfiles = $null
      $alluserprofiles = Get-CimInstance -Classname  Win32_UserProfile -CimSession $cs1 | select -Property LocalPath | Where-Object -Property LocalPath  -like "C:\Users\*"

      if($null -eq $allUserProfiles) {
         $alluserprofiles = Get-CimInstance -Classname  Win32_UserProfile -CimSession $cs2 | select -Property LocalPath | Where-Object -Property LocalPath  -like "C:\Users\*"
      }
# End get user profiles     
# close the sessions
      $cs | remove-CimSession
      }

# Start get local administrators
# Function defaults to Administrators group
      $members = Get-LocalGroupMember -Computername $serverName
# End get local administrators
# Start Get scheduled tasks
      $Connection = Connect-TaskScheduler -ComputerName $ServerName -Verbose
      $Tasks = Get-AllScheduledTasks -Session $Connection      
#End get scheduled tasks      

# Export data to server file
  $allServices | Export-Excel -Path $fulloutpath -WorksheetName 'allServices'
  $alluserprofiles | Export-Excel -Path $fulloutpath -WorksheetName 'allUserProfiles'
  $allprocesses | Export-Excel -Path $fullpath -WorksheetName 'allProcesses'
  $members | Export-Excel -Path $fulloutpath -WorksheetName 'LocalAdmins'
  $Tasks | Export-Excel -Path $fulloutpath -WorksheetName 'scheduledTasks'
    }
}
# Export the summary report for this run in the proper folder
$servers | Export-Excel -Path $outpath + "servers.xlsx" -WorksheetName 'servers'

Exit