Active Directory Group Monitoring

This script can be used to monitor certain AD groups for changes. It uses the AD RSAT tools to gather information and can email you a summary of what has changed. This is very useful when monitoring important or restricted groups for changes.

The script is however limited to finding only changes at the time it checks (every 5 minutes). If a user were to be put in and taken out of a group within the 5 minutes between checks then it would not be logged.

I added the ability for the script to save the cached data to file. This way each time it loads it would then have the old data. This is handy when doing maintenance on servers so you won’t be afraid to stop the script.

Be sure to update what groups you want to monitor and the SMTP command with correct addresses.

write-host "AD Group Monitoring Script"
write-host "Written by Josh Yuhasey on 2 MAY 2017"
$ver = "1.0.0.1"
write-host "Version: $($ver)"
write-host "Script Dependencies: AD RSAT, AD Read Right, SMTP for email"

$script:DB_File_Location = $env:temp + "\" + "AD_Group_Monitoring.DB"

# 1 = debug mode / 0 = production mode
$script:debugMode = 0

#defines list of AD Groups to monitor for changes (format is: "[groupName]" = "[Decription]" )
$Groups_To_Monitor = @{
"Domain Admins" = "Rights to domain"
"Enterprise Admins" = "Rights to domain and forest"
"Schema Admins" = "Rights to AD schema"
"Administrators" = "Rights to all domain joined servers as administrator"
"Group Policy Creator Owners" = "Rights to all domain joined servers as administrator & Group Policy"
"Remote Desktop Users" = "Rights to remote desktop into any server including AD DS servers"
}


#table of user data
$script:dt_GroupTracking = New-Object System.Data.Datatable { TableName = "ADGroupTracking"}
$script:dt_GroupTracking.Columns.Add("ADGroupName",[string]) | out-null
$script:dt_GroupTracking.Columns.Add("LastScan",[DateTime]) | out-null
$script:dt_GroupTracking.Columns.Add("LastChange",[DateTime]) | out-null
$script:dt_GroupTracking.Columns.Add("ParrentGroup",[string]) | out-null
$script:dt_GroupTracking.Columns.Add("MemberADName",[string]) | out-null
$script:dt_GroupTracking.Columns.Add("MemberADSamAccountName",[string]) | out-null
$script:dt_GroupTracking.Columns.Add("MemberSID",[string]) | out-null
$script:dt_GroupTracking.Columns.Add("Removed",[Int]) | out-null
$script:dt_GroupTracking.Rows.Clear()


Function CreateList() {

	write-warning "Starting new inital scan of groups..."
	
	$cdt = (get-date)
	
	$Groups_To_Monitor.GetEnumerator() | foreach {
		$c_name = $_.Name
		$c_description = $_.Value
		
		Get-ADGroupMember -Identity $c_name -Recursive | foreach {
			DisplayAlert-Change $c_name $_.SID $_.name $cdt "Added (inital add)"
			$script:dt_GroupTracking.Rows.Add($c_name, $cdt, $cdt, "", $_.Name, $_.SamAccountName, $_.SID, 0) | out-null
		}
	}
}

function SaveList($DB_File_Location) {
	if (test-path $DB_File_Location) { remove-item -force $DB_File_Location }
	$script:dt_GroupTracking.WriteXml($DB_File_Location)
}

function LoadList($DB_File_Location) {
	if (test-path $DB_File_Location) { 
		$script:dt_GroupTracking.ReadXml($DB_File_Location) | out-null
		write-host "$($script:dt_GroupTracking.rows.count) records loaded from file $($DB_File_Location)"
	} else { 
		write-host "$($DB_File_Location) - File not found. Loading new set of data from WSUS"
	}
}


Function ScanForDifferences() {

	$cdt = (get-date)

	$Groups_To_Monitor.GetEnumerator() | foreach {
		$c_name = $_.Name
		$c_description = $_.Value
		
		#get current membership for this group from AD
		$c_ADGroup_Members = Get-ADGroupMember -Identity $c_name -Recursive
		
		#scan for existing mebers and new members
		$c_ADGroup_Members | foreach { 
			$c_member = $_
			
			#assume we need to add unless we already have the record
			$addItem = 1
			
			$script:dt_GroupTracking | where { ($_.ADGroupName -eq $c_name) -and ($_.MemberSID -eq $c_member.SID) } | foreach {
				if ($_.Removed -eq 0) {
					#already exists, just update scan time
					if ($script:debugMode -gt 0) { write-host "Update record $($c_Member.Name) in group $($c_name) at $($cdt)" }
					$_.LastScan = $cdt
					$addItem = 0
				} else {
					#new entry as was deleted already 
					$addItem = 1
				}
			}
			
			#alert and add to list
			if ($addItem -eq 1) { 
				DisplayAlert-Change $c_name $c_member.SID $c_member.name $cdt "Added"
				Alert-GroupChange $c_name $c_member.SID $c_member.name $cdt "Added"
				$script:dt_GroupTracking.Rows.Add($c_name, $cdt, $cdt, "", $c_member.Name, $c_member.SamAccountName, $c_member.SID, 0) | out-null
			}
		}
	}

	#scan for removed members
	$script:dt_GroupTracking | where {( ($_.LastScan -lt $cdt) -and ($_.Removed -eq 0) )} | foreach {
		#if last scan date is wrong, it's not already moved, and we are looking at the right group. It must be gone.
		DisplayAlert-Change $_.ADGroupName $_.MemberSID $_.MemberADName (get-date) "Removed"
		Alert-GroupChange $_.ADGroupName $_.MemberSID $_.MemberADName (get-date) "Removed"
		#$_.LastChange = get-date
		$_.Removed = 1
	}
	
	
	#make list of current membership if we are alerting on changes
	if ($script:AlertDetails_New) {
		$script:dt_GroupTracking | where {($_.Removed -eq 0)} | sort ADGroupName | foreach {
			Alert-CurrentMembership $_.ADGroupName $_.MemberSID $_.MemberADName $_.LastScan $_.LastChange
		}
	}
	
}

Function DisplayAlert-Change($ADGroupName, $SID, $ADMemberName, $LastChange, $changetype) {

	switch ($changetype) {
		"Added" { $color = "green" }
		"Removed" { $color = "yellow" }
		default { $color = "white" }
	}

	write-host "Group change detected on group $($ADGroupName) with member of $($ADMemberName) at $($LastChange) being $($changetype)" -foregroundcolor $color
}

function Alert-GroupChange($ADGroupName, $SID, $ADMemberName, $LastChange, $changetype) {

	switch ($changetype) {
		"Added" { $color = "DarkGreen" }
		"Removed" { $color = "DarkRed" }
		default { $color = "Black" }
	}

	$script:AlertDetails_New += "<tr> <td> $($ADGroupName) </td> <td> <a href=$($SID)> $($ADMemberName) </a> </td> <td> <font color=$color> $($changetype) </font> </td> <td> $($LastChange) </td> </tr>"
}

function Alert-CurrentMembership($ADGroupName, $SID, $ADMemberName, $LastScan, $LastChange) {


	$script:AlertDetails_Current += "<tr> <td> $($ADGroupName) </td> <td> <a href=$($SID)> $($ADMemberName) </a> </td> <td> $($LastScan) </td> <td> $($LastChange) </td>  </tr>"
}

function SendAlert() {

	#only send email if we have new computers or updates available for computers
	if ( ($script:AlertDetails_New) ) {
		$body = ""
		
		if ($script:AlertDetails_New) {
			$body += "</br> <h3>Changes in Membership: </h3> </br>"
			$body += "<table border=1 width=90%>"
			$body += "<tr> <th> AD Group Name </th> <th> User Name </th> <th> Change Type </th> <th> Changed On </th> </tr>"
			$body += $script:AlertDetails_New
			$body += "</table>"
		}
		
		if ($script:AlertDetails_Current) {
			$body += "</br> <h3>Existing Membership: </h3> </br>"
			$body += "<table border=1 width=90%>"
			$body += "<tr> <th> AD Group Name </th> <th> User Name </th> <th> Last Scanned </th>  <th> Last Changed </th> </tr>"
			$body += $script:AlertDetails_Current
			$body += "</table>"
		}

		$body += "</br></br>" + "Detection at: $(get-date)"
		$body += "</br>" + "Generated from: $($env:computername)"
		$body += "</br>" + "Tracking code: [random code here]"
		$body += "</br>" + "Script version: $($ver)"
		
		Send-MailMessage -From "[from]" -To "[to]" -Subject "Protected Domain Groups Auditing" -Body $body -BodyAsHtml -SmtpServer "[mail server fqdn]"

		if ($script:debugMode -gt 0) { $body | out-file ($env:temp + "\" + "TempReport.htm") }
		
		write-host "Sending email report."
	}
}


#script starts here
$script:AlertDetails_New = ""
$script:AlertDetails_Current = ""

#load up the old list from disk on start, if no file found create and save it right away
if (test-path $script:DB_File_Location) { LoadList $script:DB_File_Location } else { CreateList ; SaveList $script:DB_File_Location }

while ($true) {
	ScanForDifferences
	SendAlert
	SaveList $script:DB_File_Location
	$script:AlertDetails_New = ""
	$script:AlertDetails_Current = ""
	write-host "Sleeping..."
	if ($script:debugMode -eq 0) { sleep (60*5) } else { sleep 5 }
}
Posted in IT | Tagged , , | Leave a comment

Exchange Reporting

This script will help you collect stats from Exchange. It can dump the data to file and output to screen. This is very similar to the Active Directory Reporting script too.

 

#Exchange Reporting 

#This script requires: Exchange remote access with domain admin rights

#connect to Exchange (change the URI to your own CAS server if needed)
$RemoteEx2013Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://[my exchange fqdn]/PowerShell/" -Authentication Kerberos
Import-PSSession $RemoteEx2013Session

$OutputPath = "$($env:userprofile)\Desktop\"

$cdt = get-date


#get inital data from Exchange
$Echx_Servers = Get-ExchangeServer

$Echx_Servers_Versions = $Echx_Servers | foreach { "   Name: $($_.Name) / Version: $($_.AdminDisplayVersion) / Site: $($_.Site.split('/')[3])" }
#$(($_.Site).Split('/')[(($_.Site).Split('/').count-1)]) #didn't work this way

#Summarize the data
$Echx_Servers_Versions = $Echx_Servers | foreach { "   Name: $($_.Name) / Version: $($_.AdminDisplayVersion) / Site: $($_.Site)" }
$Echx_Servers_CAS = ($Echx_Servers | where { $_.IsClientAccessServer -eq $true }).Name
$Echx_Servers_MBX = ($Echx_Servers | where { $_.IsMailboxServer -eq $true }).Name
$Echx_Servers_HT = ($Echx_Servers | where { $_.IsHubTransportServer -eq $true }).Name
$Echx_Servers_EG = ($Echx_Servers | where { $_.IsEdgeServer -eq $true }).Name

#Mailbox data (count of mailboxes, DLs / Mail-Enable SGs, Contacts)
$Echx_Mailboxes = Get-Mailbox -ResultSize Unlimited
$Echx_DLs = Get-DistributionGroup -ResultSize unlimited
$Echx_Groups = Get-Group -ResultSize unlimited
$Echx_Contacts = Get-Contact -ResultSize unlimited
#groups need work, not 100% correct for dl vs groups numbers


#Virtual Directories
$Echx_VirtDir_ActiveSync = Get-ActiveSyncVirtualDirectory | select Server,InternalURL,ExternalURL,BasicAuthEnabled,WindowsAuthEnabled
$Echx_VirtDir_AutoDiscover = Get-AutoDiscoverVirtualDirectory | select Server,InternalURL,ExternalURL,BasicAuthEnabled,WindowsAuthEnabled
$Echx_VirtDir_ECP = Get-ECPVirtualDirectory | select Server,InternalURL,ExternalURL,BasicAuthEnabled,WindowsAuthEnabled
$Echx_VirtDir_OAB = Get-OabVirtualDirectory | select Server,InternalURL,ExternalURL,BasicAuthEnabled,WindowsAuthEnabled
$Echx_VirtDir_WebServices = Get-WebServicesVirtualDirectory | select Server,InternalURL,ExternalURL,BasicAuthEnabled,WindowsAuthEnabled


#Exchange Outlook Anywhwere
$Echx_Outlook_Anywhere = Get-OutlookAnywhere | select Server,Name,SSLOffloading,ExternalHostname,InternalHostname,ExternalClientAuthenticationMethod,InternalClientAuthenticationMethod,IISAuthenticationMethods,ExternalClientsRequireSsl,InternalClientsRequireSsl

#Database Information
$Echx_Mailbox_Databases = Get-MailboxDatabase | select Name,Server,Recovery,EdbFilePath,LogFolderPath,DatabaseCopies,PublicFolderDatabase,ReplicationType,DeletedItemRetention,IsExcludedFromProvisioning,OfflineAddressBook,IssueWarningQuota,ProhibitSendQuota,ProhibitSendReceiveQuota

#Retention Policies
$Echx_Retention_Policies = Get-RetentionPolicy | select Name,IsDefault,RetentionPolicyTagLinks

#DAG Information
$Echx_DAG_Status = (Get-DatabaseAvailabilityGroup) | ForEach {$_.Servers | ForEach {Get-MailboxDatabaseCopyStatus -Server $_}} | Select Name,Status,CopyQueueLength,ReplayQueueLength,ContentIndexState

#Public folders
$Echx_Public_Folders = get-publicfolder

#MTL Maxes
$Echx_Transport_Service = Get-TransportService
$Echx_Transport_Service_Output = $Echx_Transport_Service | select MessageTrackingLog*

#Roles & Access
$Echx_ManagementRole_Assignments = Get-ManagementRoleAssignment | select Name, Role, RoleAssigneeName


$SB_Report = {

"Exchange Reporting"
" Reporting Date: $($cdt)"
" "
"Overall Counts"
"   Servers: $($Echx_Servers.count)"
"   Mailboxes: $($Echx_Mailboxes.count)"
"   Distrobution Lists: $($Echx_DLs.count)"
"   AD Groups: $($Echx_Groups.count)"
"   AD Contacts: $($Echx_Contacts.count)"
" "
"Server Details:"
$Echx_Servers_Versions
" "
"Mailbox Servers:"
$Echx_Servers_MBX
" "
"CAS Servers:"
$Echx_Servers_CAS
" "
"Hub Transport Servers:"
$Echx_Servers_HT
" "
"Edge Servers:"
$Echx_Servers_EG
" "
"ActiveSync Virtual Directories:"
$Echx_VirtDir_ActiveSync | fl
" "
"Auto Dicovery Virtual Directories:"
$Echx_VirtDir_AutoDiscover | fl
" "
"ECP Virtual Directories:"
$Echx_VirtDir_ECP | fl
" "
"OAB Virtual Directories:"
$Echx_VirtDir_OAB | fl
" "
"Web Services Virtual Directories:"
$Echx_VirtDir_WebServices | fl
" "
"Outlook Anywhere Settings:"
$Echx_Outlook_Anywhere | fl
" "
"Mailbox Databases:"
$Echx_Mailbox_Databases | ft -auto
" "
"Retention Policies:"
$Echx_Retention_Policies | ft -auto
" "
"DAG Status:"
$Echx_DAG_Status | ft -auto
" "
"Public Folders:"
$Echx_Public_Folders | ft -auto
" "
"Transport Service Settings:"
$Echx_Transport_Service_Output | fl
" "
"Managment Role Assignments:"
$Echx_ManagementRole_Assignments | ft -auto


}

$SB_OutputData = {

	$FileExtension = ".CSV"
	$FileOutputFolder = $OutputPath + "Exchange_Stats_" + (get-date -UFormat "%Y-%m-%d_%H-%M-%S") + "\"
	if (-not (test-path $FileOutputFolder)) { New-Item -Path $FileOutputFolder -ItemType directory | out-null }
	
	Invoke-Command $SB_Report | out-file -filepath ($FileOutputFolder + "Summary" + ".txt")
	
	$Echx_Servers_Versions | Export-CSV -notypeinformation -Path ($FileOutputFolder + "Server_Details" + $FileExtension)
	$Echx_Servers_MBX | Export-CSV -notypeinformation -Path ($FileOutputFolder + "Mailbox_Servers" + $FileExtension)
	$Echx_Servers_CAS | Export-CSV -notypeinformation -Path ($FileOutputFolder + "CAS_Servers" + $FileExtension)
	$Echx_Servers_HT | Export-CSV -notypeinformation -Path ($FileOutputFolder + "HubTransport_Servers" + $FileExtension)
	$Echx_Servers_EG | Export-CSV -notypeinformation -Path ($FileOutputFolder + "Edge_Servers" + $FileExtension)
	
	$Echx_VirtDir_ActiveSync | Export-CSV -notypeinformation -Path ($FileOutputFolder + "VirtualDirectories_ActiveSync" + $FileExtension)
	$Echx_VirtDir_AutoDiscover | Export-CSV -notypeinformation -Path ($FileOutputFolder + "VirtualDirectories_AutoDiscover" + $FileExtension)
	$Echx_VirtDir_ECP | Export-CSV -notypeinformation -Path ($FileOutputFolder + "VirtualDirectories_ECP" + $FileExtension)
	$Echx_VirtDir_OAB | Export-CSV -notypeinformation -Path ($FileOutputFolder + "VirtualDirectories_OAB" + $FileExtension)
	$Echx_VirtDir_WebServices | Export-CSV -notypeinformation -Path ($FileOutputFolder + "VirtualDirectories_WebServices" + $FileExtension)
	
	$Echx_Outlook_Anywhere | Export-CSV -notypeinformation -Path ($FileOutputFolder + "OutlookAnywhere_Details" + $FileExtension)
	$Echx_Mailbox_Databases | Export-CSV -notypeinformation -Path ($FileOutputFolder + "MailboxDatabases_Details" + $FileExtension)
	$Echx_Retention_Policies | Export-CSV -notypeinformation -Path ($FileOutputFolder + "RetentionPolicies_Details" + $FileExtension)
	$Echx_DAG_Status | Export-CSV -notypeinformation -Path ($FileOutputFolder + "DAG_Status" + $FileExtension)
	$Echx_Public_Folders | Export-CSV -notypeinformation -Path ($FileOutputFolder + "PublicFolder_Details" + $FileExtension)
	$Echx_Transport_Service_Output | Export-CSV -notypeinformation -Path ($FileOutputFolder + "TransportService_Details" + $FileExtension)
	$Echx_ManagementRole_Assignments | Export-CSV -notypeinformation -Path ($FileOutputFolder + "ManagmentRole_Assignments" + $FileExtension)

}

Invoke-Command $SB_Report
Invoke-Command $SB_OutputData
Posted in IT | Tagged , | Leave a comment

Active Directory Reporting

Here is a handy script that will help you collect information from Active Directory. It will dump this data to file and output to screen. I like to run this every month or so to gauge AD growth.

#AD Reporting

#This script requires: AD PowerShell tools from RSAT, read-only access to AD

import-module ActiveDirectory

$OutputPath = "$($env:userprofile)\Desktop\"

$cdt = get-date

#Get inital sets of data from AD
$AD_All_Objects = Get-ADObject -Filter {name -like '*'} -SearchBase 'CN=Schema,CN=Configuration,DC=[mydomain],DC=[com/local]' -ResultSetSize $null
$AD_Users = Get-ADUser -Filter * -Properties "msDS-UserPasswordExpiryTimeComputed", useraccountcontrol, PasswordNeverExpires, accountExpires
$AD_Groups = Get-ADGroup -Filter *
$AD_Computers = Get-ADComputer -Filter * -Properties OperatingSystem, PasswordLastSet, PasswordNeverExpires, accountExpires
$AD_Contacts = Get-ADObject -LDAPFilter "objectClass=Contact"
$AD_ManagedServiceAccounts = Get-ADServiceAccount -Filter *

#handy for seeing all values by count and type
# $AD_Groups | Group-Object GroupCategory
# $AD_Groups | Group-Object GroupScope
# $AD_Computers | Group-Object OperatingSystem

#users
$AD_Users_Enabled = $AD_Users | where {$_.Enabled -eq $true}
$AD_Users_Disabled = $AD_Users | where {$_.Enabled -eq $false}
$AD_Users_ExpiredPasswords = $AD_Users | where { if ($_."msDS-UserPasswordExpiryTimeComputed" -ne "9223372036854775807") { [datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed") -lt ($cdt)}}
$AD_Users_NonExpiredPasswords = $AD_Users | where { if ($_."msDS-UserPasswordExpiryTimeComputed" -ne "9223372036854775807") { [datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed") -gt ($cdt)}}
$AD_Users_NeverExpiredPasswords = $AD_Users | where { $_."msDS-UserPasswordExpiryTimeComputed" -eq "9223372036854775807" }
#$AD_Users_NeverExpiredPasswords2 = $AD_Users | where { $_.PasswordNeverExpires -eq $true } #shows same data as above, just a different way of getting it
$AD_Users_AccountNeverExpires = $AD_Computers | where { ($_.accountExpires -eq "9223372036854775807") -or ($_.accountExpires -eq "0") }
$AD_Users_AccountExpired = $AD_Computers | where { if( ($_.accountExpires -ne "9223372036854775807") -and ($_.accountExpires -ne "0")) { [datetime]::FromFileTime($_.accountExpires) -lt ($cdt) } }

#computers
$AD_Computers_Servers = $AD_Computers | where {$_.OperatingSystem -Like "Windows Server*"}
$AD_Computers_Workstation = $AD_Computers | where { ($_.OperatingSystem -NotLike "Windows Server*") -and ($_.OperatingSystem -Like "Windows*") }
$AD_Computers_Other = $AD_Computers | where {$_.OperatingSystem -NotLike "Windows*"}
$AD_Computers_Enabled = $AD_Computers | where {$_.Enabled -eq $true}
$AD_Computers_Disabled = $AD_Computers | where {$_.Enabled -eq $false}
$AD_Computers_ExpiredPasswords = $AD_Computers | where {$_.PasswordLastSet -le ($cdt).adddays(-90)}
#$AD_Computers_ExpiredPasswords2 = $AD_Computers | where { $_.PasswordNeverExpires -eq $true } #shows same data as above, just a different way of getting it
$AD_Computers_OKPasswords = $AD_Computers | where {$_.PasswordLastSet -ge ($cdt).adddays(-90)}
$AD_Computers_NeverExpire = $AD_Computers | where { $_.accountExpires -eq "9223372036854775807" }
$AD_Computers_Expire = $AD_Computers | where { $_.accountExpires -ne "9223372036854775807" }

#groups
$AD_Groups_Security = $AD_Groups | where {$_.GroupCategory -eq "Security"}
$AD_Groups_Distribution = $AD_Groups | where {$_.GroupCategory -eq "Distribution"}
$AD_Groups_Universal = $AD_Groups | where {$_.GroupScope -eq "Universal"}
$AD_Groups_DomainLocal = $AD_Groups | where {$_.GroupScope -eq "DomainLocal"}
$AD_Groups_Global = $AD_Groups | where {$_.GroupScope -eq "Global"}

#security stuff
$AD_Sec_UAC65536 = Get-ADUser -Filter 'useraccountcontrol -band 65536' -Properties useraccountcontrol
$AD_Sec_UAC32 = Get-ADUser -Filter 'useraccountcontrol -band 32' -Properties useraccountcontrol
$AD_Sec_UAC128 = Get-ADUser -Filter 'useraccountcontrol -band 128' -Properties useraccountcontrol
$AD_Sec_UAC524288 = Get-ADUser -Filter 'useraccountcontrol -band 524288' -Properties useraccountcontrol
$AD_Sec_UAC419304 = Get-ADUser -Filter 'useraccountcontrol -band 4194304' -Properties useraccountcontrol
$AD_Sec_UAC2097152 = Get-ADUser -Filter 'useraccountcontrol -band 2097152' -Properties useraccountcontrol

#OU listing
$info = ([adsisearcher]"objectclass=organizationalunit")
$info.PropertiesToLoad.AddRange("CanonicalName")
$OUs = $info.findall().properties.canonicalname | sort

$SB_Report = {

"AD Users, Groups, and Computers Total Counts"
" Reporting Date: $($cdt)"
" "
"Overall Counts"
"   Objects: $($AD_All_Objects.count)"
"   Users: $($AD_Users.count)"
"   Groups: $($AD_Groups.count)"
"   Computers: $($AD_Computers.count)"
"   Contacts: $($AD_Contacts.count)"
"   Managed Service Accounts: $($AD_ManagedServiceAccounts.count)"
"   Organizational Units: $($OUs.count)"
" "
"Users"
"   Enabled: $($AD_Users_Enabled.count)"
"   Disabled: $($AD_Users_Disabled.count)"
"   Password Not Expired: $($AD_Users_NonExpiredPasswords.count)"
"   Password Expired: $($AD_Users_ExpiredPasswords.count)"
"   Password Never Expire: $($AD_Users_NeverExpiredPasswords.count)"
#"   Password Never Expire2: $($AD_Users_NeverExpiredPasswords2.count)"
"   Expired: $($AD_Users_AccountExpired.count)"
"   Never Expires: $($AD_Computers_NeverExpire.count)"
" "
"Computers"
"   Server OS: $($AD_Computers_Servers.count)"
"   Workstation OS: $($AD_Computers_Workstation.count)"
"   Other OS: $($AD_Computers_Other.count)"
"   Enabled: $($AD_Computers_Enabled.count)"
"   Disabled: $($AD_Computers_Disabled.count)"
"   Password Expired: $($AD_Computers_ExpiredPasswords.count)"
"   Password Expired2: $($AD_Computers_ExpiredPasswords.count)"
#"   Password OK: $($AD_Computers_ExpiredPasswords2.count)"
"   Expired: $($AD_Computers_Expire.count)"
"   Never Expires: $($AD_Computers_NeverExpire.count)"
" "
"Groups by Category"
"   Security: $($AD_Groups_Security.count)"
"   Distribution: $($AD_Groups_Distribution.count)"
"Groups by Scope"
"   Universal: $($AD_Groups_Universal.count)"
"   DomainLocal: $($AD_Groups_DomainLocal.count)"
"   Global: $($AD_Groups_Global.count)"
" "
"AD Security"
"   Check for accounts that don't have password expiry set (UAC65536): $($AD_Sec_UAC65536.Count)"
"   Check for accounts that have no password requirement (UAC32): $($AD_Sec_UAC32.Count)"
"   Accounts that have the password stored in a reversibly encrypted format (UAC128): $($AD_Sec_UAC128.Count)"
"   List users that are trusted for Kerberos delegation (UAC524288): $($AD_Sec_UAC524288.Count)"
"   List accounts that don't require pre-authentication (UAC419304): $($AD_Sec_UAC419304.Count)"
"   List accounts that have credentials encrypted with DES (UAC2097152): $($AD_Sec_UAC2097152.Count)"

}

$SB_OutputData = {

	$FileExtension = ".CSV"
	$FileOutputFolder = $OutputPath + "ADDS_Stats_" + (get-date -UFormat "%Y-%m-%d_%H-%M-%S") + "\"
	if (-not (test-path $FileOutputFolder)) { New-Item -Path $FileOutputFolder -ItemType directory | out-null }
	
	Invoke-Command $SB_Report | out-file -filepath ($FileOutputFolder + "Summary" + ".txt")
	
	$AD_Users_Enabled | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Users_Enabled" + $FileExtension)
	$AD_Users_Disabled | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Users_Disabled" + $FileExtension)
	$AD_Users_ExpiredPasswords | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Users_ExpiredPasswords" + $FileExtension)
	$AD_Users_NeverExpiredPasswords | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Users_NeverExpiredPasswords" + $FileExtension)
	$AD_Users_AccountNeverExpires | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Users_AccountNeverExpires" + $FileExtension)
	$AD_Users_AccountExpired | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Users_AccountExpired" + $FileExtension)
	
	
	$AD_Computers_Servers | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Computers_Servers" + $FileExtension)
	$AD_Computers_Workstation | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Computers_Workstation" + $FileExtension)
	$AD_Computers_Other | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Computers_Other" + $FileExtension)
	$AD_Computers_Enabled | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Computers_Enabled" + $FileExtension)
	$AD_Computers_Disabled | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Computers_Disabled" + $FileExtension)
	$AD_Computers_ExpiredPasswords | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Computers_ExpiredPasswords" + $FileExtension)
	$AD_Computers_OKPasswords | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Computers_OKPasswords" + $FileExtension)
	$AD_Computers_NeverExpire | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Computers_NeverExpire" + $FileExtension)
	$AD_Computers_Expire | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Computers_Expire" + $FileExtension)
	

	$AD_Groups_Security | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Groups_Security" + $FileExtension)
	$AD_Groups_Distribution | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Groups_Distribution" + $FileExtension)
	$AD_Groups_Universal | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Groups_Universal" + $FileExtension)
	$AD_Groups_DomainLocal | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Groups_DomainLocal" + $FileExtension)
	$AD_Groups_Global | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Groups_Global" + $FileExtension)
	
	$AD_Sec_UAC65536 | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Sec_UAC65536" + $FileExtension)
	$AD_Sec_UAC32 | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Sec_UAC32" + $FileExtension)
	$AD_Sec_UAC128 | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Sec_UAC128" + $FileExtension)
	$AD_Sec_UAC524288 | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Sec_UAC524288" + $FileExtension)
	$AD_Sec_UAC419304 | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Sec_UAC419304" + $FileExtension)
	$AD_Sec_UAC2097152 | Export-CSV -notypeinformation -Path ($FileOutputFolder + "AD_Sec_UAC2097152" + $FileExtension)
	
	$OUs | Out-File -FilePath ($FileOutputFolder + "AD_OUS" + ".txt")
}

Invoke-Command $SB_Report
Invoke-Command $SB_OutputData
Posted in IT | Tagged , | Leave a comment

PowerShell Dummy File Generation

A few weeks back I needed to test a script’s ability to move older files to a new path. This is pretty simple to do but I didn’t want to test it on production systems. So I decided to use PowerShell to create a bunch of randomly filled text files with random creation, write, and last accessed date/time stamps. It will do this in whatever directory you are currently in so be careful.

The second function will move those files into random directories. This helped me simulate the structure of the production system.

Function FlatFiles() {
#will do flat files in whatever dir you are in

1..1000 | foreach {
	$c_file_name = ("File_" + $_ + ".txt")
	
	#generate file
	1..(Get-Random -minimum 10 -maximum 100) | foreach {  -join ((65..90) + (97..122) | Get-Random -Count 50 | % {[char]$_}) } | out-file -append $c_file_name
	
	#set timestamps of file
	(gci $c_file_name).LastWriteTime = (get-date).AddDays((Get-Random -minimum -9000 -maximum -90))
	(gci $c_file_name).CreationTime = (get-date).AddDays((Get-Random -minimum -9000 -maximum -90))
	(gci $c_file_name).LastAccessTime = (get-date).AddDays((Get-Random -minimum -9000 -maximum -90))
}



1001..1100 | foreach {
	$c_file_name = ("File_" + $_ + ".txt")
	
	#generate file
	1..(Get-Random -minimum 10 -maximum 100) | foreach {  -join ((65..90) + (97..122) | Get-Random -Count 50 | % {[char]$_}) } | out-file -append $c_file_name
	
	#set timestamps of file
	(gci $c_file_name).LastWriteTime = (get-date).AddDays((Get-Random -minimum -89 -maximum -1))
	(gci $c_file_name).CreationTime = (get-date).AddDays((Get-Random -minimum -89 -maximum -1))
	(gci $c_file_name).LastAccessTime = (get-date).AddDays((Get-Random -minimum -89 -maximum -1))
}

}


Function StructuredFiles() {
#run the above and then run this after to put files into random dirs

1..10 | foreach { New-Item -ItemType "directory" (-join ((65..90) + (97..122) | Get-Random -Count 10 | % {[char]$_})) }
$folders = Get-ChildItem -Recurse | ?{ $_.PSIsContainer }
GCI "File_*.txt" | foreach { Move-Item $_.FullName (get-random $folders) }

}
Posted in IT | Tagged , | Leave a comment

PowerShell Email Processor

One of the easiest ways to dump information from PowerShell is to just send an email. In the older versions of PoSH it was a bit more code to accomplish the same thing you can do in one line of the newer versions. This is great but what if you want to use a different mail server? Then you must update all of your scripts and point them to the new server address. Now most people would say this is just a bad practice, you should be using a CNAME in DNS or a load balanced IP or both. I completely agree and recommend it, but you don’t get reporting or redundancy (redundancy in terms of SMTP errors) by doing it that way.

I decided that using a email workflow would be the best way to send emails from all of my scripts. Most of them already have SQL access so it seemed logical to dump email information to a SQL table and have a service script do the SMTP work. I later refined this into another function for sending the emails so you can get it all down into one line for a new email.

Continue reading

Posted in IT | Tagged , , , | Leave a comment

Favorite Tools and Apps

Placeholder for all of the things I have used to help me.

Software (Windows):

Sysinternals Suit (Free)
A phenomenal set of tools from Microsoft. Free to use. Highly recommended for anyone managing Windows systems.

Password Managment:

KeePass (Free)

Passwordstate (Enterprise + $)

 

 

Networking:

Wireshark (Free)
A great tool for capturing and viewing network data in realtime. It has some great features for analyzing captured data, viewing packet captures from other programs such as tcpdump, playing back VoIP T.38 fax sessions, and so on. One of the really great features is remote capture. You can run winpcap from a server (say Windows Server Core) from command line and then run the Wireshark GUI back on your own PC and stream capture data through the network.

 

Software (Linux):

 

Hardware:

Posted in IT | Leave a comment

Windows Server 2016 Licensing on Server 2012 R2

As you know Windows Server 2016 was recently released and is now available to most people on the volume licensing site.

You may be using a KMS server to license your Microsoft operation systems. In my environment we are using a Server 2012 R2 server for KMS. I setup this server to host licensing for all of our Windows 8/8.1 and Server 2012/2012 R2 OSs. When Windows 10 came out we added a patch allowing it to host licensing for Windows 10 products.

Continue reading

Posted in IT | Tagged | Leave a comment

IT Checklist for New Environments

If you have to ever start working in environment due to a job change, an acquisition, or even starting a new environment, you may want to do a bit of digging before you do any work for them. These questions can help you establish a baseline for the environment and can save your butt later on.

Continue reading

Posted in Documentation, IT | Tagged | Leave a comment

Moving From On Premise Lync server to Skype for Business Online (Part 2)

This starts the technical side of moving users. This guide assumes you have a working edge server, have the correct ports NAT’ed, paid certificates installed, Office 365 Azure AD sync setup, domains validated in O365, and a healthy Lync environment.

Continue reading

Posted in IT, Recent Projects | Tagged , , , , , , | Leave a comment

PowerShell and Exception Reporting

So I have a lot of scripts and some of these scripts run as windows services. Well… they run under NSSM in PowerShell as a windows service in infinite loops. So you won’t see the output of the console.

I wrote a function that is used with all of my production scripts to help report back any errors encountered and log them to a MS SQL table. This was great but using try/catch for each line of code would be insane. So I found that I could use the trap command to trap any exception in a script and then deliver that exception to my function.

It works pretty well in most scripts. The only issue I’ve seen is in scripts that have functions, you must specify the trap each function to trap errors inside of functions (if you want to catch any error across any line).

After an error is logged to SQL I have another script running as a service that checks this table for new entries and then sends them out as an email. I’ve found this to be very useful so far when troubleshooting issues.

Continue reading

Posted in Development | Tagged , , | Leave a comment