Audit Windows Network Settings with Powershell

We have been consolidating IT groups at the university for several years now. During this time we have to convert both clients and servers to use our central division Active Directory and other centralized services. However gathering all of the networking information can be quite the task and prone to error. One of the easiest ways I have found to gather all of this information is by running a script to generate CSV file of these settings.

I used this existing script I found over on techibee.com and took it a few steps further.

I added that ability to query Active Directory and find all the computers within an OU and the ability to adjust the filter. This can be handy if you want to find just windows servers or windows clients or just plain windows. I also fixed the output for multiple DNS servers or gateways to a CSV file as well as added some standard header and footers to make the screen output a bit cleaner. This script does require you to have the Active Directory Powershell Module Installed. It will save the CSV to the local directory from which the script is executed and report the path at the end of the screen output. This script should be run with a highly privileged user as it will need to query multiple remote computer using WMI and access Active Directory.

Script

Import-Module ActiveDirectory

$localpath = Get-Location;
$CSVFileName = $localpath.Path + "\Department-Network-Information.csv";

$ComputerName = Get-ADComputer -SearchBase "OU=Department,OU=Division,DC=domain,DC=local" -Filter {OperatingSystem -Like "Windows*"} -Property "Name";

$Results = @()

foreach ($Computer in $ComputerName)
{
    if(Test-Connection -ComputerName $Computer.Name -Count 1 -ea 0) 
    {
        $Networks = Get-WmiObject Win32_NetworkAdapterConfiguration -ComputerName $Computer.Name | ? {$_.IPEnabled};
        foreach ($Network in $Networks) 
        {
            $IPAddress  = $Network.IpAddress[0];
            $SubnetMask  = $Network.IPSubnet[0];
            $DefaultGateway = $Network.DefaultIPGateway;
            $DNSServers  = $Network.DNSServerSearchOrder;
            $IsDHCPEnabled = $false;
            If($network.DHCPEnabled) 
            {
                $IsDHCPEnabled = $true;
            }
            $MACAddress  = $Network.MACAddress;

            if ($DNSServers) 
            {
                $StringDNSServers = [string]::join("; ",$DNSServers);
            }
            else
            {
                $StringDNSServers = " ";
            }

            if($DefaultGateway)
            {
                $StringDefaultGateway = [string]::join("; ",$DefaultGateway);
            }
            else
            {
                $StringDefaultGateway = " ";
            }

            $ReturnedObj = New-Object -Type PSObject;
            $ReturnedObj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $Computer.Name.ToUpper();
            $ReturnedObj | Add-Member -MemberType NoteProperty -Name IPAddress -Value $IPAddress;
            $ReturnedObj | Add-Member -MemberType NoteProperty -Name SubnetMask -Value $SubnetMask;
            $ReturnedObj | Add-Member -MemberType NoteProperty -Name Gateway -Value $StringDefaultGateway;
            $ReturnedObj | Add-Member -MemberType NoteProperty -Name IsDHCPEnabled -Value $IsDHCPEnabled;
            $ReturnedObj | Add-Member -MemberType NoteProperty -Name DNSServers -Value $StringDNSServers;
            $ReturnedObj | Add-Member -MemberType NoteProperty -Name MACAddress -Value $MACAddress;
            $ReturnedObj;
            $Results += $ReturnedObj;
        }
    }  
}

$Results | export-csv $CSVFileName -notype;

Write-Host
Write-Host "File Saved to: $CSVFileName";
Write-Host
Write-Host "Press any key to close ..."
$host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

Screen Output Example

ComputerName  : Computer1
IPAddress     : 194.90.128.89
SubnetMask    : 255.255.255.0
Gateway       : 194.90.128.254
IsDHCPEnabled : True
DNSServers    : 8.8.8.8; 8.8.4.4
MACAddress    : 18:13:73:2A:98:FA

ComputerName  : Computer2
IPAddress     : 194.90.128.92
SubnetMask    : 255.255.255.0
Gateway       : 194.90.128.254
IsDHCPEnabled : True
DNSServers    : 8.8.8.8; 8.8.4.4
MACAddress    : F8:B1:56:AC:FB:2E


File Saved to: D:\Powershell-Scripts\Department-Network-Information.csv

Press any key to close ...

CSV Output

"Computer1","194.90.128.89","255.255.255.0","194.90.128.254","True","8.8.8.8; 8.8.4.4","18:13:73:2A:98:FA"
"Computer2","194.90.128.92","255.255.255.0","194.90.128.254","True","8.8.8.8; 8.8.4.4","F8:B1:56:AC:FB:2E"

Read More

Allow User To Run Applicaton as Administrator Without a Password

A few days ago I came across a software application that just wouldn’t execute correctly without the user being an administrator on the computer. Since all of my users run as basic / limited users they were unable to use program. After contacting the vendor and looking for all type of rights that we could grant the user so the could execute the program properly we were unable to fix it without making the user an administrator. So rather than making them an administrator or giving them the administrator password I made a little application that calls the other application as a run as but has the administrator credentials complied in. I realize that you can probably decompile the application and get the password, but for many users that is too much work, or they lack the expertise, so I view this as a small security issue. To further protect the account I made one that only exists on that computer. Below is the code that you can use to build a similar application it is only a few lines but it can solve a headache and keep a password relatively secure.

This is a VB.NET application
This application will produce an error if it is unable to login as that account or if the target program cannot be found.

Public Class Form1
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim DomainName As String
        Dim UserName As String
        Dim Password As String
        Dim SysPassword As System.Security.SecureString = New System.Security.SecureString()
        DomainName = System.Environment.GetEnvironmentVariable("ComputerName")
        UserName = "administrator"
        Password = "supersecretpassword"

        For Each c As Char In Password
            SysPassword.AppendChar(c)
        Next
        SysPassword.MakeReadOnly()

        Try
            System.Diagnostics.Process.Start("notepad.exe", UserName, SysPassword, DomainName)
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try
        Me.Close()
    End Sub
End Class

Read More

Report Workstation Uptime in a CSV using Active Directory and VBS

Have you ever been left wondering which computers on your domain have been neglected by their user and not restarted in forever? This is a question that come up in my office every once and a while. One of the easiest ways to solve this problem is to ask WMI for when the computer was last restarted and subtract it from the current time. Also, while asking WMI questions you might as well ask which user is currently logged on the PC that way you know who to blame. This is exactly what the following script does for your domain. It grabs the list of workstations from the domain then queries WMI for the last time the computer is restarted and does some conversion and math and makes you an nice CSV that you can play with.

Script Configuration
Before running this script there is some minor configuration that must be done so it can communicate with your Active Directory setup.

  1. Find objConnection.Open “Active Directory Server” change Active Directory Server to the name of your Domain Controller
  2. Find objCommand.CommandText = _
    “Select Name, Location from ‘LDAP://OU=Workstations,DC=west,DC=domain,DC=edu’ ” _
    & “Where objectClass=’computer'”
    change subdomain, domain, and suffix to the name of your domain i.e. west domain edu (respectively)
  3. Find GetUptime objRecordSet.Fields(“Name”).Value, “C:\uptime.csv” and change C:\uptime.csv to the location where you want the file saved. Be sure to save it with the extension CSV
Const ADS_SCOPE_SUBTREE = 2

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand =   CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Server" 

Set objCOmmand.ActiveConnection = objConnection
objCommand.CommandText = _
    "Select Name, Location from 'OU=Workstations,DC=west,DC=domain,DC=edu' " _
        & "Where objectClass='computer'"  
objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 
Set objRecordSet = objCommand.Execute
objRecordSet.MoveFirst

Do Until objRecordSet.EOF
	GetUptime objRecordSet.Fields("Name").Value, "C:\uptime.csv"
    objRecordSet.MoveNext
Loop

Sub GetUptime(strComputer, strFilename)
	On Error Resume Next
	Set StdOut = WScript.StdOut
	 
	Set objFSO = CreateObject("scripting.filesystemobject")
	Set logStream = objFSO.opentextfile(strFilename, 8, True)
	 
	Set oReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\default:StdRegProv")
	If Err.Number Then
	      logStream.writeline(strComputer & ",Offline")
	      Err.Clear
	Else
		Set objWMIService = GetObject _
			("winmgmts:\\" & strComputer & "\root\cimv2")
		Set colOperatingSystems = objWMIService.ExecQuery _
			("Select * from Win32_OperatingSystem")
		For Each objOS in colOperatingSystems
			dtmBootup = objOS.LastBootUpTime
			dtmLastBootupTime = WMIDateStringToDate(dtmBootup)
			dtmSystemUptime = DateDiff("h", dtmLastBootUpTime, Now()) 
		Next
		Set objWMIService = GetObject _
			("winmgmts:\\" & strComputer & "\root\cimv2")
		Set colComputerSys = objWMIService.ExecQuery _
			("Select UserName from Win32_ComputerSystem")
		For Each objCS in colComputerSys
			username = objCS.UserName
			logStream.writeline(strComputer & ",Online," & dtmSystemUptime & "," & dtmLastBootupTime & "," & username) 
		Next
				
	End If
	logStream.Close
End Sub
Function WMIDateStringToDate(dtmBootup)
    WMIDateStringToDate = CDate(Mid(dtmBootup, 5, 2) & "/" & _
         Mid(dtmBootup, 7, 2) & "/" & Left(dtmBootup, 4) _
         & " " & Mid (dtmBootup, 9, 2) & ":" & _
         Mid(dtmBootup, 11, 2) & ":" & Mid(dtmBootup, _
         13, 2))
End Function

Read More

Weekly Terminal Services Connection Report using VBS

A few weeks ago we had some state auditors come by and mention that we should review our logs for any sort of outside / vendor access. I knew that going to each server and reviewing the logs manually would be very time consuming and not really provide solid documentation that it was done. I decided that the only way to solve this problem was with a report of some nature. I fired up my trusty Crystal Reports and started to view the logs using that, once I got in to more I realized that when I added the description field of the event log it always crashed Crystal Reports. This left me going to plan B which is writing the reports from scratch using Visual Basic Scripting language.

I already knew that you can use VBS to connect to WMI (Windows Management Interface) and view different parts of the system including the event log, so I spent the morning writing the report and parsing it down to the detail that I really needed. Then I decided to take it to the next level by adding in recursion for multiple servers and also set it up to send an HTML email so it is easy to review every week. Why every week you may ask, well in looking at my event log on my domain server I noticed that I start losing Security events at about 10-14 days out since it is authorizing so much, and a weekly task is a very manageable one.

Script Configuration

  1. Configure the servers that this script will report on. Modify the Servers array for each server that needs to be checked. (Note: all servers need the same login credentials for the script to work)
  2. Find the objMessage.From field and update it with who the email is coming from
  3. Find the objMessage.To Field and update with the email address of the person who will be receiving the report, if you have multiple addresses to send to separate them with a semi-colon (;)
  4. Find the (“http://schemas.microsoft.com/cdo/configuration/smtpserver”) = “smtp-relay.waynezim.com” and update this with your SMTP server, if your server requires authentication you will need to modify this script to include that, a simple Google search should show you what needs to be changed.
  5. This script should be setup to be a scheduled task on one of your servers, the credentials used in setting up the job will be used to connect to the other servers, this account needs to exist on all servers to view the Security Event Log and make the report.
  6. To setup a scheduled task, go to your Control Panel, open Scheduled Tasks, right click New > Scheduled Task, name it, then right click and modify the Properties, Browse to where the script is saved, set the Run as at the bottom for the user that exists on all Servers and set the password. Then go to the Schedule tab and set it to Weekly and change it to run when you want it to.
Dim objWMI, objEvent ' Objects
Dim strComputer ' Strings
Dim intEvent, intNumberID, intRecordNum, colLoggedEvents
'--------------------------------------------
' Server List to Parse Logs
Dim Servers(5)
Servers(0) = "server1"
Servers(1) = "server2"
Servers(2) = "server3"
Servers(3) = "server4"
Servers(4) = "server5"
Servers(5) = "server6"
'--------------------------------------------
' Email Body Heading
HTMLMsg = "<html><body><h3>Remote Desktop Connections from " & cDate(Now() - 7) & " to " & cDate(Now()) & "</h3>"
HTMLMsg = HTMLMsg & "<table border=1><tr><td><b>Computer Name</b></td><td><b>Logon Type</b></td><td><b>Remote IP</b></td><td><b>Date / Time</b></td><td><b>User</b></td></tr>"
'--------------------------------------------
' Next section creates the file to store Events
' Then creates WMI connector to the Logs

'Range Variable - Out of Loop for Common Report Time
WeekAgo = cDate(Now() - 7)

'Start Each Computer Loop
For Each strComputer in Servers
' --------------------------------------------
' Set your variables for Events Loop
intEvent = 1
intRecordNum = 1

Set objWMI = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" _
& strComputer & "\root\cimv2")
Set colLoggedEvents = objWMI.ExecQuery _
("Select * from Win32_NTLogEvent Where Logfile = 'Security' AND EventCode = 528 AND TimeWritten > '" & WeekAgo & "'")
' -----------------------------------------
' Next section loops through ID properties
intEvent = 1
	For Each objEvent in colLoggedEvents

	HTMLMsg = HTMLMsg & "<tr><td>" & objEvent.ComputerName & "</td>"
	LogonType = RTrim(Mid(objEvent.Message,InStr(objEvent.Message,"Logon Type:")+12,2))
	If LogonType = 2 Then HTMLMsg = HTMLMsg & "<td>Interactive</td>" End if
	If LogonType = 3 Then HTMLMsg = HTMLMsg & "<td>Network</td>" End if
	If LogonType = 4 Then HTMLMsg = HTMLMsg & "<td>Batch</td>" End if
	If LogonType = 5 Then HTMLMsg = HTMLMsg & "<td>Service</td>" End if
	If LogonType = 7 Then HTMLMsg = HTMLMsg & "<td>Unlock</td>" End if
	If LogonType = 8 Then HTMLMsg = HTMLMsg & "<td>Network using Clear Text</td>" End if
	If LogonType = 9 Then HTMLMsg = HTMLMsg & "<td>New Credentials</td>" End if
	If LogonType = 10 Then HTMLMsg = HTMLMsg & "<td>Remote Interactive</td>" End if
	If LogonType = 11 Then HTMLMsg = HTMLMsg & "<td>Cached Interaction</td>" End if

	IPlen = InStr(InStr(objEvent.Message,"Source Network Address:")+24,objEvent.Message,"	") - InStr(objEvent.Message,"Source Network Address:") - 28
	RemoteAddress = RTrim(Mid(objEvent.Message,InStr(objEvent.Message,"Source Network Address:")+24,IPlen))
	HTMLMsg = HTMLMsg & "<td>" & RemoteAddress & "</td>"
	EventTime = Mid(objEvent.TimeWritten, 5, 2) & "/" & Mid(objEvent.TimeWritten, 7, 2) & "/" & Mid(objEvent.TimeWritten, 1, 4) & " " & Mid(objEvent.TimeWritten, 9, 2) & ":" & Mid(objEvent.TimeWritten, 11, 2) & "." & Mid(objEvent.TimeWritten, 13, 2)
	HTMLMsg = HTMLMsg & "<td>" & EventTime & "</td>"
	HTMLMsg = HTMLMsg & "<td>" & objEvent.User & "</td></tr>"
	intRecordNum = intRecordNum +1
	IntEvent = intEvent +1

	Next
Next

Set objMessage = CreateObject("CDO.Message")
objMessage.Subject = "Remote Connections Report: " & cDate(Now())
objMessage.From = "root@waynezim.com"
objMessage.To = "waynezim@waynezim.com"
objMessage.HTMLBody = HTMLMsg
'==This section provides the configuration information for the remote SMTP server.
'==Normally you will only change the server name or IP.
objMessage.Configuration.Fields.Item _
("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
'Name or IP of Remote SMTP Server
objMessage.Configuration.Fields.Item _
("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "smtp-relay.waynezim.com"
'Server port (typically 25)
objMessage.Configuration.Fields.Item _
("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
objMessage.Configuration.Fields.Update
'==End remote SMTP server configuration section==

objMessage.Send
WScript.Quit

Report Preview
If you need help decoding what Logon Type really means check out this great article.

remote-connection-report-preview

Read More

How to Remove Old Cached Roaming Profiles from Workstations

Earlier this year I was tasked with cleaning up the workstations on our network to help reduce the amount of time needed for our daily virus scan to complete. One of the issues I took on was cleaning up old cached profiles from the use of roaming profiles. This was not something I wanted to do manually for the 150 PCs that we have across our building, so I made a script that would look for profiles that had not been modified in the last 90 days and wasn’t a system account (localservice, networkservice, default user, all users). Also, an advantage of using a script to do this is it can produce a report of what it will remove without actually doing it. That way you can be sure that you are not deleting things that you do want to keep.

This script does depending on file and print sharing being turned on for the workstation so the script can access the administrative shares on each computer. It does make the assumption that your profiles are saved in the default windows location C:\Documents and Settings\%username% and that you are the administrator for the domain.

Configuration

  1. Be sure to update the LDAP string ‘LDAP://OU=workstations,DC=subdomain,DC=domain,DC=com’ to match your Active Directory structure. The script needs to know where all the workstation are in Active Directory
  2. Find objConnection.Open “DomainController” and modify the put your Domain Controller in place of DomainController
  3. Find OldProfile objRecordSet.Fields(“Name”).Value, “C:\deletedprofiles.csv” and modify the filename to save the file where you and and named what you want, just be sure to leave the extension as CSV so it will open properly with your spreadsheet application.
  4. Most Importantly – Comment out fsoFolder.DeleteFolder objSubfolder, TRUE if you just want a report of what it will delete when run, if not it is currently setup to remove the unwanted profiles
Const ADS_SCOPE_SUBTREE = 2

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand =   CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "shs-login"

Set objCOmmand.ActiveConnection = objConnection
objCommand.CommandText = _
    "Select Name, Location from 'LDAP://OU=workstations,DC=subdomain,DC=domain,DC=com' " _
        & "Where objectClass='computer'"  
objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 
Set objRecordSet = objCommand.Execute
objRecordSet.MoveFirst

Do Until objRecordSet.EOF
	OldProfile objRecordSet.Fields("Name").Value, "C:\deletedprofiles.csv"
    objRecordSet.MoveNext
Loop

Sub OldProfile(strComputer, strFilename)
	On Error Resume Next
	Set StdOut = WScript.StdOut
	 
	Set objFSO = CreateObject("scripting.filesystemobject")
	Set logStream = objFSO.opentextfile(strFilename, 8, True)
	 
	Set oReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\default:StdRegProv")
	If Err.Number Then
	      logStream.writeline(strComputer & ",Offline")
	      Err.Clear
	Else
		On Error Resume Next
		Set objShell = CreateObject("Shell.Application")
		Set fsoFolder = CreateObject("Scripting.FileSystemObject")

		root = "\\" & strComputer &"\C$\Documents and Settings"

		Set objFolder = fsoFolder.GetFolder(root)
		Set colSubfolders = objFolder.Subfolders
		
			For Each objSubfolder in colSubfolders
				If (lcase(objSubfolder.Name) <> "localservice" AND lcase(objSubfolder.Name) <> "networkservice"_
					AND lcase(objSubfolder.Name) <> "default user" AND lcase(objSubfolder.Name) <> "all users") then
						
						If (DateDiff("D", objSubfolder.DateLastModified, Date()) > 90) then
							logStream.writeline(strComputer & ",Online,Delete," & objSubfolder & "," & objSubfolder.DateLastModified)
							fsoFolder.DeleteFolder objSubfolder, TRUE
						else
							logStream.writeline(strComputer & ",Online,Active," & objSubfolder & "," & objSubfolder.DateLastModified)
						End If
						
				else
					logStream.writeline(strComputer & ",Online,System," & objSubfolder & "," & objSubfolder.DateLastModified)
				End If
			Next
	End If
	logStream.Close
End Sub

Read More