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:
- Get-AllScheduledTasks: From Elliott Berglund. Has multiple functions used as is.
- Get-LocalGroupMember: From Boe Prox. Used as is.
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