diff --git a/Active Directory/AD Groups/Archive-ObsoleteGroups.ps1 b/Active Directory/AD Groups/Archive-ObsoleteGroups.ps1 index 230fa04..d93bd0f 100644 --- a/Active Directory/AD Groups/Archive-ObsoleteGroups.ps1 +++ b/Active Directory/AD Groups/Archive-ObsoleteGroups.ps1 @@ -5,13 +5,13 @@ # Author: Sam Erde # # Created: 11/04/2014 -# Description: Read a list of obsolete groups from a text file, export the members to a separate text file for each group, +# Description: Read a list of obsolete groups from a text file, export the members to a separate text file for each group, # and then empty the obsolete groups. They can be deleted after a week or two to prove they are no longer used. # The empty groups are also moved to the "Obsolete Groups" OU. # # DO NOT RUN TWICE ON THE SAME FILE OR THE ARCHIVE WILL BE OVERWRITTEN! # -# To Do: +# To Do: # Add error handling # Prompt for a job name at each run so their is a separate archive folder for each job to help prevent an archive from being overwritten. # Add handling of group names to it can discover DNs if needed. This may require a specific format within the input file, such as group DNs there. @@ -22,25 +22,24 @@ Import-Module ActiveDirectory #Set the Active Directory server name that will be used. Using a serverless domain name here may also work. -$Domain = "" +$Domain = '' #Read in the CSV or text file of group names. $File = Get-Content -Path C:\Scripts\ObsoleteGroups\ObsoleteGroups.csv #Loop through each line of the text file and run the following commands for each line: -Foreach ($Group in $File) -{ +Foreach ($Group in $File) { #Get the members of each group (recursively in case groups are nested) in the specified domain or domain controller. #Select the name of each member within the group and then write each name to a CSV file. Each CSV file is named with the name of each security group. Get-ADGroupMember -Server $Domain -Identity $Group -Recursive | Export-Csv -Path "C:\Scripts\ObsoleteGroups\Archive\$group.csv" -NoTypeInformation <# * * * * * * * * * * - This section will require special customization until we further develop the script to pull the full group DN. + This section will require special customization until we further develop the script to pull the full group DN. In the interest of time today, I have hard coded some of the information. * * * * * * * * * * /#> - .\Remove-AllGroupMembers.ps1 -group "CN=$Group" -ou "OU=" -domain "DC=" - Move-ADObject -Server $Domain -Identity "CN=ps,DC=" -TargetPath "" + .\Remove-AllGroupMembers.ps1 -group "CN=$Group" -ou 'OU=' -domain 'DC=' + Move-ADObject -Server $Domain -Identity 'CN=ps,DC=' -TargetPath '' } #Copy and rename the CSV file with a timestamp to keep as a record of run history. diff --git a/Active Directory/AD Groups/Get-GroupReport.ps1 b/Active Directory/AD Groups/Get-GroupReport.ps1 index 383075c..85beba2 100644 --- a/Active Directory/AD Groups/Get-GroupReport.ps1 +++ b/Active Directory/AD Groups/Get-GroupReport.ps1 @@ -105,16 +105,15 @@ #> #------------------------------------------------------------- -param([String]$TrustedDomain,[switch]$verbose) +param([String]$TrustedDomain, [switch]$verbose) Set-StrictMode -Version 2.0 -if ($verbose.IsPresent) { - $VerbosePreference = 'Continue' - Write-Verbose "Verbose Mode Enabled" -} -Else { - $VerbosePreference = 'SilentlyContinue' +if ($verbose.IsPresent) { + $VerbosePreference = 'Continue' + Write-Verbose 'Verbose Mode Enabled' +} Else { + $VerbosePreference = 'SilentlyContinue' } #------------------------------------------------------------- @@ -122,19 +121,21 @@ Else { # Set this to the OU structure where the you want to search to # start from. Do not add the Domain DN. If you leave it blank, # the script will start from the root of the domain. -$OUStructureToProcess = "" +$OUStructureToProcess = '' # Set the name of the attribute you want to populate for objects # to be evaluated as a stale or non-stale object. -$ExcludeAttribute = "comment" +$ExcludeAttribute = 'comment' +[void]$ExcludeAttribute # Set the text within the $ExcludeAttribute that you want to use # to evaluate if the object should be excluded from the stale # object collection. -$ExcludeText = "Decommission=False" +$ExcludeText = 'Decommission=False' +[void]$ExcludeText # Set this to the delimiter for the CSV output -$Delimiter = "," +$Delimiter = ',' # Set this to remove the double quotes from each value within the # CSV. @@ -151,72 +152,71 @@ $ProgressBar = $True # also the "Microsoft Exchange" OUs. $ExclusionGroups = @( -"DnsAdmins",` -"DnsUpdateProxy",` -"DHCP Users",` -"DHCP Administrators",` -"Offer Remote Assistance Helpers",` -"TelnetClients",` -"IIS_WPG",` -"Access Control Assistance Operators",` -"Cloneable Domain Controllers",` -"Hyper-V Administrators",` -"Protected Users",` -"RDS Endpoint Servers",` -"RDS Management Servers",` -"RDS Remote Access Servers",` -"Remote Management Users",` -"WinRMRemoteWMIUsers_",` -"RTC*" + 'DnsAdmins', ` + 'DnsUpdateProxy', ` + 'DHCP Users', ` + 'DHCP Administrators', ` + 'Offer Remote Assistance Helpers', ` + 'TelnetClients', ` + 'IIS_WPG', ` + 'Access Control Assistance Operators', ` + 'Cloneable Domain Controllers', ` + 'Hyper-V Administrators', ` + 'Protected Users', ` + 'RDS Endpoint Servers', ` + 'RDS Management Servers', ` + 'RDS Remote Access Servers', ` + 'Remote Management Users', ` + 'WinRMRemoteWMIUsers_', ` + 'RTC*' ) $ExclusionOUs = @( -"*Microsoft Exchange System Objects*" -"*Microsoft Exchange Security Groups*" + '*Microsoft Exchange System Objects*' + '*Microsoft Exchange Security Groups*' ) #------------------------------------------------------------- -$invalidChars = [io.path]::GetInvalidFileNamechars() -$datestampforfilename = ((Get-Date -format s).ToString() -replace "[$invalidChars]","-") +$invalidChars = [io.path]::GetInvalidFileNamechars() +$datestampforfilename = ((Get-Date -Format s).ToString() -replace "[$invalidChars]", '-') # Get the script path -$ScriptPath = {Split-Path $MyInvocation.ScriptName} +$ScriptPath = { Split-Path $MyInvocation.ScriptName } $ReferenceFileFull = $(&$ScriptPath) + "\GroupReport-Full-$($datestampforfilename).csv" $ReferenceFileSummary = $(&$ScriptPath) + "\GroupReport-Summary-$($datestampforfilename).csv" $ReferenceFileSummaryTotals = $(&$ScriptPath) + "\GroupReport-Summary-Totals-$($datestampforfilename).csv" -if (Test-Path -path $ReferenceFileFull) { - remove-item $ReferenceFileFull -force -confirm:$false +if (Test-Path -Path $ReferenceFileFull) { + Remove-Item $ReferenceFileFull -Force -Confirm:$false } -if (Test-Path -path $ReferenceFileSummary) { - remove-item $ReferenceFileSummary -force -confirm:$false +if (Test-Path -Path $ReferenceFileSummary) { + Remove-Item $ReferenceFileSummary -Force -Confirm:$false } -if (Test-Path -path $ReferenceFileSummaryTotals) { - remove-item $ReferenceFileSummaryTotals -force -confirm:$false +if (Test-Path -Path $ReferenceFileSummaryTotals) { + Remove-Item $ReferenceFileSummaryTotals -Force -Confirm:$false } if ([String]::IsNullOrEmpty($TrustedDomain)) { - # Get the Current Domain Information - $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() + # Get the Current Domain Information + $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } else { - $context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("domain",$TrustedDomain) - Try { - $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($context) - } - Catch [exception] { - write-host -ForegroundColor red $_.Exception.Message - Exit - } + $context = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('domain', $TrustedDomain) + Try { + $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($context) + } Catch [exception] { + Write-Host -ForegroundColor red $_.Exception.Message + Exit + } } # Get AD Distinguished Name -$DomainDistinguishedName = $Domain.GetDirectoryEntry() | select -ExpandProperty DistinguishedName +$DomainDistinguishedName = $Domain.GetDirectoryEntry() | Select-Object -ExpandProperty DistinguishedName -If ($OUStructureToProcess -eq "") { - $ADSearchBase = $DomainDistinguishedName +If ($OUStructureToProcess -eq '') { + $ADSearchBase = $DomainDistinguishedName } else { - $ADSearchBase = $OUStructureToProcess + "," + $DomainDistinguishedName + $ADSearchBase = $OUStructureToProcess + ',' + $DomainDistinguishedName } $TotalGroupsProcessed = 0 @@ -243,559 +243,558 @@ $TotalWithSIDHistory = 0 $TotalUnixEnabledObjects = 0 # Create an LDAP search for all groups -$ADFilter = "(objectClass=group)" +$ADFilter = '(objectClass=group)' # There is a known bug in PowerShell requiring the DirectorySearcher # properties to be in lower case for reliability. -$ADPropertyList = @("name","distinguishedname","samaccountname","mail","grouptype", ` - "displayname","description","member","memberof","info", ` - "isCriticalSystemObject","admincount","managedBy","objectsid", ` - "expirationtime","whencreated","whenchanged","sidhistory", ` - "proxyaddresses","legacyexchangedn","mailnickname", ` - "reporttooriginator","gidnumber","mssfu30name","mssfu30nisdomain") -$ADScope = "SUBTREE" +$ADPropertyList = @('name', 'distinguishedname', 'samaccountname', 'mail', 'grouptype', ` + 'displayname', 'description', 'member', 'memberof', 'info', ` + 'isCriticalSystemObject', 'admincount', 'managedBy', 'objectsid', ` + 'expirationtime', 'whencreated', 'whenchanged', 'sidhistory', ` + 'proxyaddresses', 'legacyexchangedn', 'mailnickname', ` + 'reporttooriginator', 'gidnumber', 'mssfu30name', 'mssfu30nisdomain') +$ADScope = 'SUBTREE' $ADPageSize = 1000 -$ADSearchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$($ADSearchBase)") +$ADSearchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$($ADSearchBase)") $ADSearcher = New-Object System.DirectoryServices.DirectorySearcher $ADSearcher.SearchRoot = $ADSearchRoot -$ADSearcher.PageSize = $ADPageSize -$ADSearcher.Filter = $ADFilter +$ADSearcher.PageSize = $ADPageSize +$ADSearcher.Filter = $ADFilter $ADSearcher.SearchScope = $ADScope if ($ADPropertyList) { - foreach ($ADProperty in $ADPropertyList) { - [Void]$ADSearcher.PropertiesToLoad.Add($ADProperty) - } + foreach ($ADProperty in $ADPropertyList) { + [Void]$ADSearcher.PropertiesToLoad.Add($ADProperty) + } } Try { - write-host -ForegroundColor Green "`nPlease be patient whilst the script retrieves all group objects and specified attributes..." - $colResults = $ADSearcher.Findall() - # Dispose of the search and results properly to avoid a memory leak - $ADSearcher.Dispose() - $GroupCount = $colResults.Count -} -Catch { - $GroupCount = 0 - Write-Host -ForegroundColor red "The $ADSearchBase structure cannot be found!" + Write-Host -ForegroundColor Green "`nPlease be patient whilst the script retrieves all group objects and specified attributes..." + $colResults = $ADSearcher.Findall() + # Dispose of the search and results properly to avoid a memory leak + $ADSearcher.Dispose() + $GroupCount = $colResults.Count +} Catch { + $GroupCount = 0 + Write-Host -ForegroundColor red "The $ADSearchBase structure cannot be found!" } if ($GroupCount -ne 0) { - write-host -ForegroundColor Green "`nProcessing $GroupCount group objects in the $domain Domain..." - - $colResults | ForEach-Object { - $group = $_.GetDirectoryEntry() - - $Name = $($group.Name) - $ParentOU = $($group.DistinguishedName) -split '(? @@ -45,12 +45,12 @@ Param $SERVER_TRUST_ACCOUNT = 0x2000 $TRUSTED_FOR_DELEGATION = 0x80000 -$TRUSTED_TO_AUTH_FOR_DELEGATION= 0x1000000 -$PARTIAL_SECRETS_ACCOUNT = 0x4000000 +$TRUSTED_TO_AUTH_FOR_DELEGATION = 0x1000000 +$PARTIAL_SECRETS_ACCOUNT = 0x4000000 $bitmask = $TRUSTED_FOR_DELEGATION -bor $TRUSTED_TO_AUTH_FOR_DELEGATION -bor $PARTIAL_SECRETS_ACCOUNT # LDAP filter to find all accounts having some form of delegation. -# 1.2.840.113556.1.4.804 is an OR query. +# 1.2.840.113556.1.4.804 is an OR query. $filter = @" (& (servicePrincipalname=*) @@ -66,14 +66,14 @@ $filter = @" (objectcategory=msDS-ManagedServiceAccount) ) ) -"@ -replace "[\s\n]", '' +"@ -replace '[\s\n]', '' $propertylist = @( - "servicePrincipalname", - "useraccountcontrol", - "samaccountname", - "msDS-AllowedToDelegateTo", - "msDS-AllowedToActOnBehalfOfOtherIdentity" + 'servicePrincipalname', + 'useraccountcontrol', + 'samaccountname', + 'msDS-AllowedToDelegateTo', + 'msDS-AllowedToActOnBehalfOfOtherIdentity' ) Get-ADObject -LDAPFilter $filter -SearchBase $DN -SearchScope Subtree -Properties $propertylist -PipelineVariable account | ForEach-Object { $isDC = ($account.useraccountcontrol -band $SERVER_TRUST_ACCOUNT) -ne 0 @@ -81,31 +81,31 @@ Get-ADObject -LDAPFilter $filter -SearchBase $DN -SearchScope Subtree -Propertie $constrainedDelegation = ($account.'msDS-AllowedToDelegateTo').count -gt 0 $isRODC = ($account.useraccountcontrol -band $PARTIAL_SECRETS_ACCOUNT) -ne 0 $resourceDelegation = $account.'msDS-AllowedToActOnBehalfOfOtherIdentity' -ne $null - - $comment = "" - if ((-not $isDC) -and $fullDelegation) { - $comment += "WARNING: full delegation to non-DC is not recommended!; " + + $comment = '' + if ((-not $isDC) -and $fullDelegation) { + $comment += 'WARNING: full delegation to non-DC is not recommended!; ' } - if ($isRODC) { - $comment += "WARNING: investigation needed if this is not a real RODC; " + if ($isRODC) { + $comment += 'WARNING: investigation needed if this is not a real RODC; ' } - if ($resourceDelegation) { - # to count it using PS, we need the object type to select the correct function... broken, but there we are. - $comment += "INFO: Account allows delegation FROM other server(s); " + if ($resourceDelegation) { + # to count it using PS, we need the object type to select the correct function... broken, but there we are. + $comment += 'INFO: Account allows delegation FROM other server(s); ' } - if ($constrainedDelegation) { - $comment += "INFO: constrained delegation service count: $(($account.'msDS-AllowedToDelegateTo').count); " + if ($constrainedDelegation) { + $comment += "INFO: constrained delegation service count: $(($account.'msDS-AllowedToDelegateTo').count); " } [PSCustomobject] @{ - samaccountname = $account.samaccountname - objectClass = $account.objectclass - uac = ('{0:x}' -f $account.useraccountcontrol) - isDC = $isDC - isRODC = $isRODC - fullDelegation = $fullDelegation + samaccountname = $account.samaccountname + objectClass = $account.objectclass + uac = ('{0:x}' -f $account.useraccountcontrol) + isDC = $isDC + isRODC = $isRODC + fullDelegation = $fullDelegation constrainedDelegation = $constrainedDelegation - resourceDelegation = $resourceDelegation - comment = $comment + resourceDelegation = $resourceDelegation + comment = $comment } -} \ No newline at end of file +} diff --git a/Active Directory/Domain Services/Add-ADSubnets.ps1 b/Active Directory/Domain Services/Add-ADSubnets.ps1 index e592a10..ef1d243 100644 --- a/Active Directory/Domain Services/Add-ADSubnets.ps1 +++ b/Active Directory/Domain Services/Add-ADSubnets.ps1 @@ -1,212 +1,200 @@ -#* FileName: Add-Subnets.ps1 -#*============================================================================= -#* Script Name: Add Subnets -#* Created: [11/31/2010] -#* Author: Jerry Martin -#* Company: The Home Depot -#* Email: Jerry_Martin@homedepot.com -#* Web: http://www.homedepot.com -#* Powershell Version: 2.0 -#* Reqrmnts: Microsoft Active Directory Module -#* Keywords: -#*============================================================================= -#* Purpose: This script was created to import a list of subnets from a CSV -#* File into Active Directory, Directory Services Site and Services Module. -#* This automates the process of creating Subnets and is intended to be used -#* in conjuction with the Add-SiteLinks, Add-Sites and Get-SitesAndServices -#* Scripts. -#*============================================================================= - -#*============================================================================= -#* Usage. -#*============================================================================= -#* The parameters for this script are -#* -InputCSV "Path to CSV File" Default is ".\Subnets.csv" -#* -LogFile "Path to Log File" Default is ".\Logfile.txt" -#* -NewLog Y/N Defaults to N, enter Y to create a new log file -#* -#* Typical Usage would look like the following... -#* .\Add-Subnets.ps1 -InputCSV .\Subnetlist2.csv -Logfile C:\Logfile.txt -#* OR -#* .\Add-Subnets.ps1 -#* Without using the Parameters the script will use the default file locations -#* for the Input CSV file and Log File, and will append to the existing log. -#*============================================================================= - -#*============================================================================= -#* Creation Resources and Code Samples -#*============================================================================= -#* HOW TO CREATE A FUNCTION TO VALIDATE THE EXISTENCE OF AN AD OBJECT (Test-XADObject) -#* http://blogs.msdn.com/b/adpowershell/archive/2009/05/05/how-to-create-a-function-to-validate-the-existence-of-an-ad-object-test-xadobject.aspx -#*============================================================================= - -#*============================================================================= -#* The Script -#*============================================================================= -#Get input from cmd line for Log file location and Input CSV file locations if -#They differ from the defaults. -Param ( - [Parameter()][string]$InputCSV='.\Subnets.csv', - [Parameter()][String]$Logfile='.\Logfile.txt', - [Parameter()][String]$NewLog="N") - -#Set the Script to continue on errors. -$ErrorActionPreference = "silentlycontinue" - -#Clear the cmd window -Clear-Host - -#Verify PowerShell Version 2 -if ($Host.Version.Major -lt 2) - {Write-Host "Wrong Version of Powershell, Exiting now..."; Start-Sleep5 ; Exit} - -#Verfiy the path to the Input CSV file -if (!(Test-Path $InputCSV)) - {Write-Host "Your Input CSV does not exist, Exiting..." ; Start-Sleep5 ; Exit} - -#Check for the existance of the Active Directory Module -If(!(Get-Module -ListAvailable | Where-Object {$_.name -eq 'ActiveDirectory'})) - {Write-Host "The Active Directory module is not installed on this system." ; Start-Sleep5 ;exit } - -#Import the Active Directory Module -Import-Module ActiveDirectory - -#List the Current Domain that the script is using. -Write-host "The current Domain Contoller is $((Get-ADDomainController).HostName)" -BackgroundColor Yellow -ForegroundColor Black - -If ($NewLog -eq 'Y') - { - Write-host "Creating a new log file" - #Rename Existing file to old. - Get-Item $Logfile | Move-Item -force -Destination { [IO.Path]::ChangeExtension( $_.Name, "old" ) } - } - -#Add the Current Date to the log file -$Date = Get-Date -Add-Content $Logfile $Date - -#Import the SubnetList CSV file. -# Please note, -# We are using the first line of the file to set the property names of the items -# imported, so the csv file should have the following as the first line. -# subnetname,sitename,subnetlocation,subnetdescription -# -# Then the contents should look something like this. -# 10.25.0.0/24,ST9990,"Store 9990","Test Subnet 0" -$SBin = Import-Csv $InputCSV - - -# Get the Subnet container -$ConfigurationDN = (Get-ADRootDSE).ConfigurationNamingContext -$SubnetDN = ("CN=Subnets,CN=Sites," + $ConfigurationDN) - -#Create the funtion to test existance of the subnets for error checking. -#See Creation Resources and Code Samples Section to be linked for more info. -function Test-XADObject() - { - [CmdletBinding(ConfirmImpact="Low")] - Param - ( - [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,HelpMessage="Identity of the AD object to verify if exists or not.")] - [Object] $Identity - ) - - trap [Exception] - { - return $false - } - $auxObject = Get-ADObject -Identity $Identity - return ($Null -ne $auxObject) - } - -#Create the funtion to test existance of the sites for error checking. -# See Creation Resources and Code Samples Section to be linked for more info. -function Test-XADSite() - { - [CmdletBinding(ConfirmImpact="Low")] - Param - ( - [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,HelpMessage="Identity of the AD object to verify if exists or not.")] - [Object] $Identity - ) - - trap [Exception] - { - return $false - } - $auxObject = Get-ADObject -Filter 'ObjectClass -eq "site"' -SearchBase $ConfigurationDN| Where-Object {$_.Name -eq $Identity} - return ($Null -ne $auxObject) - } - -#null out i -$i = $null - -#Loop through each line in the CSV file and create the subnets based on info in the CSV. -foreach ($SB in $SBin) -{ -#Incriment i for each object in the csv file -$i++ - # Create the new subnet container name - $NewSubnetDN = ("CN=" + $SB.subnetname +"," + $SubnetDN) - - - If (!(Test-XADSite -Identity $SB.Sitename)) - { - Write-Host "Site $($SB.Sitename) Does Not Exist, Please create Sites before Subnets." -BackgroundColor Red -ForegroundColor White - } - Else - { - #Test if the new subnet doesn't already exist - If (!(Test-XADObject -Identity $newSubnetDN)) - { - - #Creating the new subnet - $for = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() - $fortyp = [System.DirectoryServices.ActiveDirectory.DirectoryContexttype]"forest" - $forcntxt = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext($fortyp, $for) - - $subnet = new-object System.DirectoryServices.ActiveDirectory.ActiveDirectorySubnet($forcntxt, $SB.subnetname, $SB.sitename) - $Subnet.Location = $SB.subnetlocation - $subnet.Save() - - #Set the Subnet Description - $subnetde = $subnet.GetDirectoryEntry() - $subnetde.Description = $SB.subnetdescription - $subnetde.CommitChanges() - - #Verify that the subnet was created sucessfully and update the log - If (Test-XADObject -Identity $newSubnetDN) - { - #Update the console - Write-Host "Created & Verified Subnet: $($SB.subnetname)" - #Update the log file - Add-Content -Path $Logfile -Value "$($SB.subnetname),Created and Verified" - } - - #Log that we Attempted to create the subnet but could not verify it. - Else - { - #Update the Console +#* FileName: Add-Subnets.ps1 +#*============================================================================= +#* Script Name: Add Subnets +#* Created: [11/31/2010] +#* Author: Jerry Martin +#* Company: The Home Depot +#* Email: Jerry_Martin@homedepot.com +#* Web: http://www.homedepot.com +#* Powershell Version: 2.0 +#* Reqrmnts: Microsoft Active Directory Module +#* Keywords: +#*============================================================================= +#* Purpose: This script was created to import a list of subnets from a CSV +#* File into Active Directory, Directory Services Site and Services Module. +#* This automates the process of creating Subnets and is intended to be used +#* in conjuction with the Add-SiteLinks, Add-Sites and Get-SitesAndServices +#* Scripts. +#*============================================================================= + +#*============================================================================= +#* Usage. +#*============================================================================= +#* The parameters for this script are +#* -InputCSV "Path to CSV File" Default is ".\Subnets.csv" +#* -LogFile "Path to Log File" Default is ".\Logfile.txt" +#* -NewLog Y/N Defaults to N, enter Y to create a new log file +#* +#* Typical Usage would look like the following... +#* .\Add-Subnets.ps1 -InputCSV .\Subnetlist2.csv -Logfile C:\Logfile.txt +#* OR +#* .\Add-Subnets.ps1 +#* Without using the Parameters the script will use the default file locations +#* for the Input CSV file and Log File, and will append to the existing log. +#*============================================================================= + +#*============================================================================= +#* Creation Resources and Code Samples +#*============================================================================= +#* HOW TO CREATE A FUNCTION TO VALIDATE THE EXISTENCE OF AN AD OBJECT (Test-XADObject) +#* http://blogs.msdn.com/b/adpowershell/archive/2009/05/05/how-to-create-a-function-to-validate-the-existence-of-an-ad-object-test-xadobject.aspx +#*============================================================================= + +#*============================================================================= +#* The Script +#*============================================================================= +#Get input from cmd line for Log file location and Input CSV file locations if +#They differ from the defaults. +Param ( + [Parameter()][string]$InputCSV = '.\Subnets.csv', + [Parameter()][String]$Logfile = '.\Logfile.txt', + [Parameter()][String]$NewLog = 'N') + +#Set the Script to continue on errors. +$ErrorActionPreference = 'silentlycontinue' + +#Clear the cmd window +Clear-Host + +#Verify PowerShell Version 2 +if ($Host.Version.Major -lt 2) +{ Write-Host 'Wrong Version of Powershell, Exiting now...'; Start-Sleep5 ; Exit } + +#Verfiy the path to the Input CSV file +if (!(Test-Path $InputCSV)) +{ Write-Host 'Your Input CSV does not exist, Exiting...' ; Start-Sleep5 ; Exit } + +#Check for the existance of the Active Directory Module +If (!(Get-Module -ListAvailable | Where-Object { $_.name -eq 'ActiveDirectory' })) +{ Write-Host 'The Active Directory module is not installed on this system.' ; Start-Sleep5 ; exit } + +#Import the Active Directory Module +Import-Module ActiveDirectory + +#List the Current Domain that the script is using. +Write-Host "The current Domain Contoller is $((Get-ADDomainController).HostName)" -BackgroundColor Yellow -ForegroundColor Black + +If ($NewLog -eq 'Y') { + Write-Host 'Creating a new log file' + #Rename Existing file to old. + Get-Item $Logfile | Move-Item -Force -Destination { [IO.Path]::ChangeExtension( $_.Name, 'old' ) } +} + +#Add the Current Date to the log file +$Date = Get-Date +Add-Content $Logfile $Date + +#Import the SubnetList CSV file. +# Please note, +# We are using the first line of the file to set the property names of the items +# imported, so the csv file should have the following as the first line. +# subnetname,sitename,subnetlocation,subnetdescription +# +# Then the contents should look something like this. +# 10.25.0.0/24,ST9990,"Store 9990","Test Subnet 0" +$SBin = Import-Csv $InputCSV + + +# Get the Subnet container +$ConfigurationDN = (Get-ADRootDSE).ConfigurationNamingContext +$SubnetDN = ('CN=Subnets,CN=Sites,' + $ConfigurationDN) + +#Create the funtion to test existance of the subnets for error checking. +#See Creation Resources and Code Samples Section to be linked for more info. +function Test-XADObject() { + [CmdletBinding(ConfirmImpact = 'Low')] + Param + ( + [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, HelpMessage = 'Identity of the AD object to verify if exists or not.')] + [Object] $Identity + ) + + trap [Exception] { + return $false + } + $auxObject = Get-ADObject -Identity $Identity + return ($Null -ne $auxObject) +} + +#Create the funtion to test existance of the sites for error checking. +# See Creation Resources and Code Samples Section to be linked for more info. +function Test-XADSite() { + [CmdletBinding(ConfirmImpact = 'Low')] + Param + ( + [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, HelpMessage = 'Identity of the AD object to verify if exists or not.')] + [Object] $Identity + ) + + trap [Exception] { + return $false + } + $auxObject = Get-ADObject -Filter 'ObjectClass -eq "site"' -SearchBase $ConfigurationDN | Where-Object { $_.Name -eq $Identity } + return ($Null -ne $auxObject) +} + +#null out i +$i = $null + +#Loop through each line in the CSV file and create the subnets based on info in the CSV. +foreach ($SB in $SBin) { + #Incriment i for each object in the csv file + $i++ + # Create the new subnet container name + $NewSubnetDN = ('CN=' + $SB.subnetname + ',' + $SubnetDN) + + + If (!(Test-XADSite -Identity $SB.Sitename)) { + Write-Host "Site $($SB.Sitename) Does Not Exist, Please create Sites before Subnets." -BackgroundColor Red -ForegroundColor White + } Else { + #Test if the new subnet doesn't already exist + If (!(Test-XADObject -Identity $newSubnetDN)) { + + #Creating the new subnet + $for = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() + $fortyp = [System.DirectoryServices.ActiveDirectory.DirectoryContexttype]'forest' + $forcntxt = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext($fortyp, $for) + + $subnet = New-Object System.DirectoryServices.ActiveDirectory.ActiveDirectorySubnet($forcntxt, $SB.subnetname, $SB.sitename) + $Subnet.Location = $SB.subnetlocation + $subnet.Save() + + #Set the Subnet Description + $subnetde = $subnet.GetDirectoryEntry() + $subnetde.Description = $SB.subnetdescription + $subnetde.CommitChanges() + + #Verify that the subnet was created sucessfully and update the log + If (Test-XADObject -Identity $newSubnetDN) { + #Update the console + Write-Host "Created & Verified Subnet: $($SB.subnetname)" + #Update the log file + Add-Content -Path $Logfile -Value "$($SB.subnetname),Created and Verified" + } + + #Log that we Attempted to create the subnet but could not verify it. + Else { + #Update the Console Write-Host "Created but not Verified Subnet: $($SB.subnetname)" - #Update the log file - Add-Content -Path $Logfile -Value "$($SB.subnetname),Created but not Verified " - } - - - } - #Make note that the subnet already existed. - Else { - #Update the Console - Write-Host "Subnet $($SB.subnetname) already exists. Skipping this subnet" -BackgroundColor Red -ForegroundColor White - #Update the log file. - Add-Content -Path $Logfile -Value "$($SB.subnetname),PreExisting" - } - } -#Give a graphical representation of the percentage of work completed. -Write-Progress -activity "Adding Subnets..." -status "Percent added: " -PercentComplete (($i / $SBin.length) * 100) - -} - -#*============================================================================= -#* End of The Script + #Update the log file + Add-Content -Path $Logfile -Value "$($SB.subnetname),Created but not Verified " + } + + + } + #Make note that the subnet already existed. + Else { + #Update the Console + Write-Host "Subnet $($SB.subnetname) already exists. Skipping this subnet" -BackgroundColor Red -ForegroundColor White + #Update the log file. + Add-Content -Path $Logfile -Value "$($SB.subnetname),PreExisting" + } + } + #Give a graphical representation of the percentage of work completed. + Write-Progress -Activity 'Adding Subnets...' -Status 'Percent added: ' -PercentComplete (($i / $SBin.length) * 100) + +} + +#*============================================================================= +#* End of The Script #*============================================================================= diff --git a/Active Directory/Domain Services/Check AD Domain Permissions.ps1 b/Active Directory/Domain Services/Check AD Domain Permissions.ps1 index 092afba..4752c25 100644 --- a/Active Directory/Domain Services/Check AD Domain Permissions.ps1 +++ b/Active Directory/Domain Services/Check AD Domain Permissions.ps1 @@ -1,7 +1,7 @@ <# - The idea for this script was based a sample script provided by Trimarc at + The idea for this script was based a sample script provided by Trimarc at https://www.hub.trimarcsecurity.com/post/mitigating-exchange-permission-paths-to-domain-admins-in-active-directory. - + See also: https://www.hub.trimarcsecurity.com/post/securing-active-directory-performing-an-active-directory-security-review #> @@ -13,39 +13,39 @@ $DomainRootPermissionsReportName = 'DomainRootPermissionsReport.csv' $ADDomain = (Get-ADDomain).DnsRoot $ConfigurationContext = $((Get-ADRootDSE).ConfigurationNamingContext) $DomainTopLevelObjectDN = (Get-ADDomain $ADDomain).DistinguishedName -$GUID = "" -$DomainRootPermissionsReportPath = $DomainRootPermissionsReportDir + '\' + $ADDomain + '-' +$DomainRootPermissionsReportName +$GUID = '' +$DomainRootPermissionsReportPath = $DomainRootPermissionsReportDir + '\' + $ADDomain + '-' + $DomainRootPermissionsReportName function Get-PermissionName { - param ( - [string]$GUID - ) - # Look for the GUID in the Extended-Rights of the ControlAccessRight class - $ExtendedRightsName = ( (Get-ADObject -SearchBase "CN=Extended-Rights,$ConfigurationContext" -LDAPFilter "(&(ObjectClass=ControlAccessRight)(RightsGUID=$GUID))") ).Name - if ( $ExtendedRightsName ) { - Return $ExtendedRightsName - } - # If the GUID is not found in Extended-Rights, look for it in the SchemaNamingContext: - else { - $SchemaRightsName = ( (Get-ADObject -SearchBase (Get-ADRootDSE).SchemaNamingContext -LDAPFilter "(SchemaIDGUID=$GUID)" -Properties Name, SchemaIDGUID) ).Name - Return $SchemaRightsName - } + param ( + [string]$GUID + ) + # Look for the GUID in the Extended-Rights of the ControlAccessRight class + $ExtendedRightsName = ( (Get-ADObject -SearchBase "CN=Extended-Rights,$ConfigurationContext" -LDAPFilter "(&(ObjectClass=ControlAccessRight)(RightsGUID=$GUID))") ).Name + if ( $ExtendedRightsName ) { + Return $ExtendedRightsName + } + # If the GUID is not found in Extended-Rights, look for it in the SchemaNamingContext: + else { + $SchemaRightsName = ( (Get-ADObject -SearchBase (Get-ADRootDSE).SchemaNamingContext -LDAPFilter "(SchemaIDGUID=$GUID)" -Properties Name, SchemaIDGUID) ).Name + Return $SchemaRightsName + } } # Create the directory if it does not already exist if ( !(Test-Path $DomainRootPermissionsReportDir) ) { - New-Item -type Directory -Path $DomainRootPermissionsReportDir + New-Item -type Directory -Path $DomainRootPermissionsReportDir } # Get the details of the domain root permissions. $DomainRootPermissions = Get-ADObject -Identity $DomainTopLevelObjectDN -Properties * | Select-Object -ExpandProperty nTSecurityDescriptor | Select-Object -ExpandProperty Access # Export the permissions and details to the CSV file. -$DomainRootPermissions | Select-Object IdentityReference,ActiveDirectoryRights,AccessControlType,IsInherited,InheritanceType, ` - InheritedObjectType,ObjectFlags,InheritanceFlags,PropagationFlags,@{N="Type";E={Get-PermissionName $_.ObjectType}} ` - | Export-Csv $DomainRootPermissionsReportPath -NoTypeInfo +$DomainRootPermissions | Select-Object IdentityReference, ActiveDirectoryRights, AccessControlType, IsInherited, InheritanceType, ` + InheritedObjectType, ObjectFlags, InheritanceFlags, PropagationFlags, @{N = 'Type'; E = { Get-PermissionName $_.ObjectType } } ` +| Export-Csv $DomainRootPermissionsReportPath -NoTypeInfo # Display the essential details. -$DomainRootPermissions | Select-Object IdentityReference,ActiveDirectoryRights,AccessControlType,IsInherited | Sort-Object ActiveDirectoryRights,IdentityReference +$DomainRootPermissions | Select-Object IdentityReference, ActiveDirectoryRights, AccessControlType, IsInherited | Sort-Object ActiveDirectoryRights, IdentityReference Write-Output `n"$ADDomain Domain Permission Report saved to $DomainRootPermissionsReportPath" diff --git a/Active Directory/Domain Services/Collect-MissingADSubnets.ps1 b/Active Directory/Domain Services/Collect-MissingADSubnets.ps1 index c1d2fd2..f25aa79 100644 --- a/Active Directory/Domain Services/Collect-MissingADSubnets.ps1 +++ b/Active Directory/Domain Services/Collect-MissingADSubnets.ps1 @@ -2,30 +2,30 @@ <# .SYNOPSIS - The script is generating an export of missing AD subnets based on the netlogon log info of + The script is generating an export of missing AD subnets based on the netlogon log info of specified domain controllers. In first, the script is collecting remotely the netlogon log file from: specified domain controllers domain controllers from a specific domain domain controllers from the entire forest - + When data are collected, the script extract the NO_CLIENT_SITE entries and compute the missing subnets from them. - + The script check at the end if the missing subnets were not already created on AD by extracting the list of AD subnets. - + .NOTES Author : Alexandre Augagneur (www.alexwinner.com) File Name : Collect-MissingADSubnets.ps1 - + Update - 04/22/2014 : New field 'Computers' added in the export (list of computers concerned by each missing subnet). Update - 06/16/2013 : Fixed issues related to existing subnets. - + .EXAMPLE .\Collect-MissingADSubnets.ps1 -Path C:\Temp -Domain "corpnet.net" - + .EXAMPLE .\Collect-MissingADSubnets.ps1 -Path C:\Temp -Server "dc01.corpnet.net","dc02.corpnet.net" @@ -34,62 +34,62 @@ .EXAMPLE .\Collect-MissingADSubnets.ps1 -Path C:\Temp -IPv4Mask 16 -nbLines 250 -CollectOnly - + .EXAMPLE .\Collect-MissingADSubnets.ps1 -Path C:\Temp -ExistingData c:\CollectedData - + .PARAMETER Domain Collect data from domain controllers of a specific AD domain. If this parameter, the parameters 'Server' and 'ExistingData' are not specified, the script is collecting data of all domain controllers of the forest. .PARAMETER Server List of domain controllers to collect data - + .PARAMETER ExistingData Path of previous collected netlogon logs to treat - + .PARAMETER Path Path for the exported files - + .PARAMETER CollectOnly Collect only netlogon entries (parameter not available with parameter ExistingData - + .PARAMETER IPv4Mask The netmask in decimal to compute the missing subnets (default 24) - + .PARAMETER nbLines The number of lines to grab from a netlogon file (default 500) #> param -( - [Parameter(ParameterSetName="Domain")] - [String] $Domain, - - [Parameter(ParameterSetName="Server",Mandatory=$true)] - [String[]] $Server, - - [Parameter(ParameterSetName="Data",Mandatory=$true)] - [ValidateScript({Test-Path $_ -PathType Container})] - [String] $ExistingData, - - [Parameter(Mandatory=$true)] - [ValidateScript({Test-Path $_ -PathType Container})] - [String] $Path, - - [parameter(ParameterSetName="Domain")] - [parameter(ParameterSetName="Server")] - [Switch] - $CollectOnly, - - [Parameter()] - [ValidateRange(1,32)] - [int] $IPv4Mask = 24, - - [Parameter()] - [int] $nbLines = 500 +( + [Parameter(ParameterSetName = 'Domain')] + [String] $Domain, + + [Parameter(ParameterSetName = 'Server', Mandatory = $true)] + [String[]] $Server, + + [Parameter(ParameterSetName = 'Data', Mandatory = $true)] + [ValidateScript({ Test-Path $_ -PathType Container })] + [String] $ExistingData, + + [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path $_ -PathType Container })] + [String] $Path, + + [parameter(ParameterSetName = 'Domain')] + [parameter(ParameterSetName = 'Server')] + [Switch] + $CollectOnly, + + [Parameter()] + [ValidateRange(1, 32)] + [int] $IPv4Mask = 24, + + [Parameter()] + [int] $nbLines = 500 ) #Region Functions @@ -101,110 +101,97 @@ param #--------------------------------------------------- # Create the IPv4 object #--------------------------------------------------- -function Compute-IPv4 ( $Obj, $ObjInputAddress, $IPv4Mask ) -{ - $Obj | Add-Member -type NoteProperty -name Type -value "IPv4" - - # Compute IP length +function Compute-IPv4 ( $Obj, $ObjInputAddress, $IPv4Mask ) { + $Obj | Add-Member -type NoteProperty -Name Type -Value 'IPv4' + + # Compute IP length [int] $IntIPLength = 32 - $IPv4Mask - - # Returns the number of block-size - [int] $BlockBytes = [Math]::Floor($IntIPLength / 8) - - $NumberOfIPs = ([System.Math]::Pow(2, $IntIPLength)) -1 - - $IpStart = Compute-IPv4NetworkAddress $ObjInputAddress $BlockBytes $IPv4Mask - $Obj | Add-Member -type NoteProperty -name Subnet -value "$($IpStart)/$($IPv4Mask)" - $Obj | Add-Member -type NoteProperty -name IpStart -value $IpStart - - $ArrBytesIpStart = $IpStart.GetAddressBytes() - [array]::Reverse($ArrBytesIpStart) - $RangeStart = [system.bitconverter]::ToUInt32($ArrBytesIpStart,0) - - $IpEnd = $RangeStart + $NumberOfIPs - - If (($IpEnd.Gettype()).Name -ine "double") - { - $IpEnd = [Convert]::ToDouble($IpEnd) - } - - $IpEnd = [System.Net.IPAddress] $IpEnd - $Obj | Add-Member -type NoteProperty -name IpEnd -value $IpEnd - - $Obj | Add-Member -type NoteProperty -name RangeStart -value $RangeStart - - $ArrBytesIpEnd = $IpEnd.GetAddressBytes() - [array]::Reverse($ArrBytesIpEnd) - $Obj | Add-Member -type NoteProperty -name RangeEnd -value ([system.bitconverter]::ToUInt32($ArrBytesIpEnd,0)) - - Return $Obj + + # Returns the number of block-size + [int] $BlockBytes = [Math]::Floor($IntIPLength / 8) + + $NumberOfIPs = ([System.Math]::Pow(2, $IntIPLength)) - 1 + + $IpStart = Compute-IPv4NetworkAddress $ObjInputAddress $BlockBytes $IPv4Mask + $Obj | Add-Member -type NoteProperty -Name Subnet -Value "$($IpStart)/$($IPv4Mask)" + $Obj | Add-Member -type NoteProperty -Name IpStart -Value $IpStart + + $ArrBytesIpStart = $IpStart.GetAddressBytes() + [array]::Reverse($ArrBytesIpStart) + $RangeStart = [system.bitconverter]::ToUInt32($ArrBytesIpStart, 0) + + $IpEnd = $RangeStart + $NumberOfIPs + + If (($IpEnd.Gettype()).Name -ine 'double') { + $IpEnd = [Convert]::ToDouble($IpEnd) + } + + $IpEnd = [System.Net.IPAddress] $IpEnd + $Obj | Add-Member -type NoteProperty -Name IpEnd -Value $IpEnd + + $Obj | Add-Member -type NoteProperty -Name RangeStart -Value $RangeStart + + $ArrBytesIpEnd = $IpEnd.GetAddressBytes() + [array]::Reverse($ArrBytesIpEnd) + $Obj | Add-Member -type NoteProperty -Name RangeEnd -Value ([system.bitconverter]::ToUInt32($ArrBytesIpEnd, 0)) + + Return $Obj } #--------------------------------------------------- # Compute the network address #--------------------------------------------------- -function Compute-IPv4NetworkAddress ( $Address, $nbBytes, $IPv4Mask ) -{ - $ArrBytesAddress = $Address.GetAddressBytes() - [array]::Reverse($ArrBytesAddress) - - # Sets a Block-Size to 0 if it is a part of the network length - for ( $i=0; $i -lt $nbBytes; $i++ ) - { - $ArrBytesAddress[$i] = 0 - } - - # Returns the remaining bits of the prefix - $Remaining = $obj.Prefix % 8 - - if ( $Remaining -gt 0 ) - { - $Mask = ([Math]::Pow(2,$Remaining)-1)*([Math]::Pow(2,8-$Remaining)) - $BlockBytesValue = $ArrBytesAddress[$i] -band $Mask - $ArrBytesAddress[$i] = $BlockBytesValue - } - - [array]::Reverse($ArrBytesAddress) - $NetworkAddress = [System.Net.IPAddress] $ArrBytesAddress - - Return $NetworkAddress +function Compute-IPv4NetworkAddress ( $Address, $nbBytes, $IPv4Mask ) { + $ArrBytesAddress = $Address.GetAddressBytes() + [array]::Reverse($ArrBytesAddress) + + # Sets a Block-Size to 0 if it is a part of the network length + for ( $i = 0; $i -lt $nbBytes; $i++ ) { + $ArrBytesAddress[$i] = 0 + } + + # Returns the remaining bits of the prefix + $Remaining = $obj.Prefix % 8 + + if ( $Remaining -gt 0 ) { + $Mask = ([Math]::Pow(2, $Remaining) - 1) * ([Math]::Pow(2, 8 - $Remaining)) + $BlockBytesValue = $ArrBytesAddress[$i] -band $Mask + $ArrBytesAddress[$i] = $BlockBytesValue + } + + [array]::Reverse($ArrBytesAddress) + $NetworkAddress = [System.Net.IPAddress] $ArrBytesAddress + + Return $NetworkAddress } #--------------------------------------------------- # Connect to specified domain controller and retrieve OS Version #--------------------------------------------------- -function Retrieve-DomainController ( $hostname ) -{ - $context = new-object System.directoryServices.ActiveDirectory.DirectoryContext('DirectoryServer',$hostname) - - try - { - $OSVersion = ([System.directoryServices.ActiveDirectory.DomainController]::GetDomainController($context)).OSVersion - Return $OSVersion - } - catch - { - Write-Host "Unable to contact the domain controller." -ForegroundColor Red - Return $null - } +function Retrieve-DomainController ( $hostname ) { + $context = New-Object System.directoryServices.ActiveDirectory.DirectoryContext('DirectoryServer', $hostname) + + try { + $OSVersion = ([System.directoryServices.ActiveDirectory.DomainController]::GetDomainController($context)).OSVersion + Return $OSVersion + } catch { + Write-Host 'Unable to contact the domain controller.' -ForegroundColor Red + Return $null + } } #--------------------------------------------------- # Construct and return the netlogon.log path #--------------------------------------------------- -function Get-NetlogonPath ( $hostname ) -{ - try - { - $WMIObj = Get-WmiObject Win32_OperatingSystem -Property systemDirectory - $Path = "\\"+$hostname+"\"+((split-path $WMIObj.SystemDirectory -Parent) -replace ':','$')+"\debug\netlogon.log" - Return $Path - } - catch - { - Write-Host "Unable to retrieve netlogon.log path with WMI." -ForegroundColor Red - Return $null - } +function Get-NetlogonPath ( $hostname ) { + try { + $WMIObj = Get-WmiObject Win32_OperatingSystem -Property systemDirectory + $Path = '\\' + $hostname + '\' + ((Split-Path $WMIObj.SystemDirectory -Parent) -replace ':', '$') + '\debug\netlogon.log' + Return $Path + } catch { + Write-Host 'Unable to retrieve netlogon.log path with WMI.' -ForegroundColor Red + Return $null + } } #Endregion @@ -216,288 +203,237 @@ function Get-NetlogonPath ( $hostname ) #################################################### # Connect to Active Directory -$objRootDSE = [System.DirectoryServices.DirectoryEntry] "LDAP://rootDSE" +$objRootDSE = [System.DirectoryServices.DirectoryEntry] 'LDAP://rootDSE' $DCs = @() $LogEntries = @() # Construct the list of domain controllers to be treated by the script -if ( $PSCmdlet.ParameterSetName -eq "Server" ) -{ - $DCs = $Server -} -elseif ( $PSCmdlet.ParameterSetName -eq "Data" ) -{ - $CollectedFiles = Get-ChildItem -Path $ExistingData -Filter "*-Netlogon.log" - Write-Host "`nNumber of files found: " -NoNewline - - if ( $CollectedFiles.Count -gt 0 ) - { - Write-Host $CollectedFiles.Count -ForegroundColor Magenta - } - else - { - Write-Host 0 -ForegroundColor Red - Exit - } - - $i = 1 - - foreach ( $File in $CollectedFiles ) - { - $Content = Get-Content -Path $File.FullName -Tail $nbLines - - Write-Host "($i/$($CollectedFiles.Count)) Loading entries from: " -NoNewline - Write-host "$($File.FullName)" -ForegroundColor Green - - # Search the NO_CLIENT_SITE entries in the netlogon log file and capture the IP address of each entry (only IPv4) - Foreach ( $Line in $Content ) - { - if ( $Line -match 'NO_CLIENT_SITE:\s*(.*?)\s*(\d*\.\d*\.\d*\.\d*)' ) - { - $Obj = New-Object -TypeName PsObject - $Obj | Add-Member -type NoteProperty -name Computer -value ($matches[1]) - $Obj | Add-Member -type NoteProperty -name IpAddress -value ($matches[2]) - $LogEntries += $Obj - - } - } - - $i++ - } -} -else -{ - if ( [string]::IsNullOrEmpty($Domain) ) - { - $ArrReadHost = @('yes','y','no','n') - $Confirm = $null - - # Request confirmation to retrieve the netlogon file from all DCs on the forest - while ( $ArrReadHost -notcontains $Confirm ) - { - Write-Warning "The script is going to retrieve the netlogon log file of all domain controllers in the forest.`nDo you want to proceed? (Y/N)" - $Confirm = Read-Host - } - - if ( $Confirm -like "n*" ) - { - Exit - } - else - { - # Connect to the current forest - $ADSIForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() - - # Retrieve the list of all DCs within the forest - foreach ( $ADSIDomain in $ADSIForest.Domains ) - { - $DCs += $ADSIDomain.DomainControllers - } - } - } - else - { - # Connect to the specified domain and retrieve the list of all dcs within this domain - $DomainContext = new-object System.directoryServices.ActiveDirectory.DirectoryContext("Domain",$Domain) - $ADSIDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) - $DCs += $ADSIDomain.DomainControllers - } +if ( $PSCmdlet.ParameterSetName -eq 'Server' ) { + $DCs = $Server +} elseif ( $PSCmdlet.ParameterSetName -eq 'Data' ) { + $CollectedFiles = Get-ChildItem -Path $ExistingData -Filter '*-Netlogon.log' + Write-Host "`nNumber of files found: " -NoNewline + + if ( $CollectedFiles.Count -gt 0 ) { + Write-Host $CollectedFiles.Count -ForegroundColor Magenta + } else { + Write-Host 0 -ForegroundColor Red + Exit + } + + $i = 1 + + foreach ( $File in $CollectedFiles ) { + $Content = Get-Content -Path $File.FullName -Tail $nbLines + + Write-Host "($i/$($CollectedFiles.Count)) Loading entries from: " -NoNewline + Write-Host "$($File.FullName)" -ForegroundColor Green + + # Search the NO_CLIENT_SITE entries in the netlogon log file and capture the IP address of each entry (only IPv4) + Foreach ( $Line in $Content ) { + if ( $Line -match 'NO_CLIENT_SITE:\s*(.*?)\s*(\d*\.\d*\.\d*\.\d*)' ) { + $Obj = New-Object -TypeName PsObject + $Obj | Add-Member -type NoteProperty -Name Computer -Value ($matches[1]) + $Obj | Add-Member -type NoteProperty -Name IpAddress -Value ($matches[2]) + $LogEntries += $Obj + + } + } + + $i++ + } +} else { + if ( [string]::IsNullOrEmpty($Domain) ) { + $ArrReadHost = @('yes', 'y', 'no', 'n') + $Confirm = $null + + # Request confirmation to retrieve the netlogon file from all DCs on the forest + while ( $ArrReadHost -notcontains $Confirm ) { + Write-Warning "The script is going to retrieve the netlogon log file of all domain controllers in the forest.`nDo you want to proceed? (Y/N)" + $Confirm = Read-Host + } + + if ( $Confirm -like 'n*' ) { + Exit + } else { + # Connect to the current forest + $ADSIForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() + + # Retrieve the list of all DCs within the forest + foreach ( $ADSIDomain in $ADSIForest.Domains ) { + $DCs += $ADSIDomain.DomainControllers + } + } + } else { + # Connect to the specified domain and retrieve the list of all dcs within this domain + $DomainContext = New-Object System.directoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain) + $ADSIDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) + $DCs += $ADSIDomain.DomainControllers + } } # Treat list of domain controllers if existing data are not used -if ( $PSCmdlet.ParameterSetName -ne "Data" ) -{ - # Treatment of each domain controller - Write-Host "`nNumber of Domain Controllers to treat: " -NoNewline - - if ( $DCs.Count -gt 0 ) - { - - Write-Host $DCs.Count -ForegroundColor Magenta - - foreach ( $DC in $DCs ) - { - Write-Host "$($DC): " -NoNewline - - $DCVersion = Retrieve-DomainController $DC - - # Windows 2000 Server not supported by the script - if ( -not([string]::IsNullOrEmpty($DCVersion)) ) - { - if ( $DCVersion -like "*2000*" ) - { - Write-Host "$($DCVersion) not supported by the script." -ForegroundColor Red - } - else - { - Write-Host "Connection established." -ForegroundColor Green - Write-Host "$($DC): " -NoNewline - - # Retrieve the netlogon file path for the specified DC - $NetLogonPath = Get-NetlogonPath $DC - - if ( -not([string]::IsNullOrEmpty($NetLogonPath)) ) - { - if ( Test-Path $NetLogonPath -ErrorAction SilentlyContinue ) - { - Write-Host "Retrieving NO_CLIENT_SITE entries from logs..." -ForegroundColor Green - - $Content = Get-Content -Path $NetLogonPath -Tail $nbLines - - # Saving the netlogon content of each DC - $Content | Out-File "$($Path)\$($DC.ToString().split('.')[0])-Netlogon.log" -Force - - # Search the NO_CLIENT_SITE entries in the netlogon log file - # Capture the IP address of each entry (only IPv4) - Foreach ( $Line in $Content ) - { - if ( $Line -match 'NO_CLIENT_SITE:\s*(.*?)\s*(\d*\.\d*\.\d*\.\d*)' ) - { - $Obj = New-Object -TypeName PsObject - $Obj | Add-Member -type NoteProperty -name Computer -value ($matches[1]) - $Obj | Add-Member -type NoteProperty -name IpAddress -value ($matches[2]) - $LogEntries += $Obj - } - } - } - else - { - Write-Host "Unable to access to $($NetLogonPath): $($_.Exception.Message)" -ForegroundColor Red - } - } - } - } - } - } - else - { - Write-Host 0 -ForegroundColor Red - Exit - } +if ( $PSCmdlet.ParameterSetName -ne 'Data' ) { + # Treatment of each domain controller + Write-Host "`nNumber of Domain Controllers to treat: " -NoNewline + + if ( $DCs.Count -gt 0 ) { + + Write-Host $DCs.Count -ForegroundColor Magenta + + foreach ( $DC in $DCs ) { + Write-Host "$($DC): " -NoNewline + + $DCVersion = Retrieve-DomainController $DC + + # Windows 2000 Server not supported by the script + if ( -not([string]::IsNullOrEmpty($DCVersion)) ) { + if ( $DCVersion -like '*2000*' ) { + Write-Host "$($DCVersion) not supported by the script." -ForegroundColor Red + } else { + Write-Host 'Connection established.' -ForegroundColor Green + Write-Host "$($DC): " -NoNewline + + # Retrieve the netlogon file path for the specified DC + $NetLogonPath = Get-NetlogonPath $DC + + if ( -not([string]::IsNullOrEmpty($NetLogonPath)) ) { + if ( Test-Path $NetLogonPath -ErrorAction SilentlyContinue ) { + Write-Host 'Retrieving NO_CLIENT_SITE entries from logs...' -ForegroundColor Green + + $Content = Get-Content -Path $NetLogonPath -Tail $nbLines + + # Saving the netlogon content of each DC + $Content | Out-File "$($Path)\$($DC.ToString().split('.')[0])-Netlogon.log" -Force + + # Search the NO_CLIENT_SITE entries in the netlogon log file + # Capture the IP address of each entry (only IPv4) + Foreach ( $Line in $Content ) { + if ( $Line -match 'NO_CLIENT_SITE:\s*(.*?)\s*(\d*\.\d*\.\d*\.\d*)' ) { + $Obj = New-Object -TypeName PsObject + $Obj | Add-Member -type NoteProperty -Name Computer -Value ($matches[1]) + $Obj | Add-Member -type NoteProperty -Name IpAddress -Value ($matches[2]) + $LogEntries += $Obj + } + } + } else { + Write-Host "Unable to access to $($NetLogonPath): $($_.Exception.Message)" -ForegroundColor Red + } + } + } + } + } + } else { + Write-Host 0 -ForegroundColor Red + Exit + } } # If data collected the script start to compute the list of missing subnets -if ( ($LogEntries.Count -gt 0) -and ($CollectOnly -eq $false) ) -{ - # Remove duplicated IP addresses - $LogEntries = $LogEntries | Select * -Unique - - $ArrIPs = @() - - # Each IP is converted to a subnet based on the IPv4Mask argument (24 bits by default) - foreach ( $Entry in $LogEntries ) - { - $ObjIP = [System.Net.IPAddress] $Entry.IpAddress - - $SubnetObj = New-Object -TypeName PsObject - - if ( $ObjIP.AddressFamily -match "InterNetwork" ) - { - $SubnetObj = Compute-IPv4 $SubnetObj $ObjIP $IPv4Mask - $SubnetObj | Add-Member -MemberType NoteProperty -Name Computer -Value $Entry.Computer - $ArrIPs += $SubnetObj - } - } - - # Remove duplicated subnets - $ArrIPs = $ArrIPs | Sort-Object RangeStart | Select * -Unique - - # Create only one entry per subnet with the list of computers associated to this subnet - $Subnets = $ArrIPs | Select Type,Subnet,IpStart,IpEnd,RangeStart,RangeEnd | Sort Subnet -Unique - $TempArray = @() - - foreach ( $Subnet in $Subnets ) - { - $ArrComputers = @() - $ArrIPs | Where-Object { $_.Subnet -eq $Subnet.Subnet } | %{ $ArrComputers += $_.Computer } - $Subnet | Add-Member -MemberType NoteProperty -Name Computers -Value ($ArrComputers -join " ") - $TempArray += $Subnet - } - - $ArrIPs = $TempArray | Sort-Object RangeStart - - # Retrieve AD subnets to check if missing subnets found in the netlogon files have not been added during the interval - Write-Host "`nRetrieving AD subnets: " -NoNewline - - $Searcher = New-Object System.DirectoryServices.DirectorySearcher - $Searcher.SearchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://cn=subnets,cn=sites,"+$objRootDSE.ConfigurationNamingContext) - $Searcher.PageSize = 10000 - $Searcher.SearchScope = "Subtree" - $Searcher.Filter = "(objectClass=subnet)" - - $Properties = @("cn","location","siteobject") - $Searcher.PropertiesToLoad.AddRange(@($Properties)) - $Subnets = $Searcher.FindAll() - - $selectedProperties = $Properties | ForEach-Object {@{name="$_";expression=$ExecutionContext.InvokeCommand.NewScriptBlock("`$_['$_']")}} - [Regex] $RegexCN = "CN=(.*?),.*" - $SubnetsArray = @() - - foreach ( $Subnet in $Subnets ) - { - # Construct the subnet object - $SubnetObj = New-Object -TypeName PsObject - $SubnetObj | Add-Member -type NoteProperty -name Name -value ([string] $Subnet.Properties['cn']) - $SubnetObj | Add-Member -type NoteProperty -name Location -value ([string] $Subnet.Properties['location']) - $SubnetObj | Add-Member -type NoteProperty -name Site -value ([string] $RegexCN.Match( $Subnet.Properties['siteobject']).Groups[1].Value) - - $InputAddress = (($SubnetObj.Name).Split("/"))[0] - $ADSubnetPrefix = (($SubnetObj.Name).Split("/"))[1] - - # Construct System.Net.IPAddress - $ObjInputAddress = [System.Net.IPAddress] $InputAddress - - # Check if IP is a IPv4 (IPv6 not collected) - if ( $ObjInputAddress.AddressFamily -eq "InterNetwork" ) - { - $SubnetObj = Compute-IPv4 $SubnetObj $ObjInputAddress $ADSubnetPrefix - $SubnetsArray += $SubnetObj - } - } - - $SubnetsArray | Export-Csv "$($Path)\ADSubnets-Export.csv" -Delimiter ";" -NoTypeInformation -Force - - if ( Test-Path "$($Path)\ADSubnets-Export.csv" ) - { - Write-Host "$($Path)\ADSubnets-Export.csv" -ForegroundColor Green - } - else - { - Write-Host "Error while exporting result to file." -ForegroundColor Yellow - } - - $Subnets = $SubnetsArray | Sort-Object -Property RangeStart - - # Check if subnets are not already created - foreach ($Item in $ArrIPs) - { - $SubnetIsExisting = $Subnets | Where-Object { ($Item.RangeStart -ge $_.RangeStart) -and ($Item.RangeEnd -le $_.RangeEnd) } - - if ( ($SubnetIsExisting) -and ($ArrIPs.Count -gt 1) ) - { - [array]::Clear($ArrIPs,([array]::IndexOf($ArrIPs, $Item)),1) - } - } - - # Export Missing subnets - if ( $ArrIPs ) - { - $ArrIPs | ? Type -ne $null | Export-Csv "$($Path)\ADSubnets-MissingSubnets.csv" -Delimiter ";" -NoTypeInformation -Force - - if ( Test-Path "$($Path)\ADSubnets-MissingSubnets.csv" ) - { - Write-Host "`nList of missing subnets: " -NoNewline - Write-Host "$($Path)\ADSubnets-MissingSubnets.csv" -ForegroundColor Green - } - else - { - Write-Host "`nError while exporting missing subnets to file." -ForegroundColor Red - } - } - else - { - Write-Host "`nNo Missing subnet found. Try with a greater netmask." -ForegroundColor Yellow - } +if ( ($LogEntries.Count -gt 0) -and ($CollectOnly -eq $false) ) { + # Remove duplicated IP addresses + $LogEntries = $LogEntries | Select-Object * -Unique + + $ArrIPs = @() + + # Each IP is converted to a subnet based on the IPv4Mask argument (24 bits by default) + foreach ( $Entry in $LogEntries ) { + $ObjIP = [System.Net.IPAddress] $Entry.IpAddress + + $SubnetObj = New-Object -TypeName PsObject + + if ( $ObjIP.AddressFamily -match 'InterNetwork' ) { + $SubnetObj = Compute-IPv4 $SubnetObj $ObjIP $IPv4Mask + $SubnetObj | Add-Member -MemberType NoteProperty -Name Computer -Value $Entry.Computer + $ArrIPs += $SubnetObj + } + } + + # Remove duplicated subnets + $ArrIPs = $ArrIPs | Sort-Object RangeStart | Select-Object * -Unique + + # Create only one entry per subnet with the list of computers associated to this subnet + $Subnets = $ArrIPs | Select-Object Type, Subnet, IpStart, IpEnd, RangeStart, RangeEnd | Sort-Object Subnet -Unique + $TempArray = @() + + foreach ( $Subnet in $Subnets ) { + $ArrComputers = @() + $ArrIPs | Where-Object { $_.Subnet -eq $Subnet.Subnet } | ForEach-Object { $ArrComputers += $_.Computer } + $Subnet | Add-Member -MemberType NoteProperty -Name Computers -Value ($ArrComputers -join ' ') + $TempArray += $Subnet + } + + $ArrIPs = $TempArray | Sort-Object RangeStart + + # Retrieve AD subnets to check if missing subnets found in the netlogon files have not been added during the interval + Write-Host "`nRetrieving AD subnets: " -NoNewline + + $Searcher = New-Object System.DirectoryServices.DirectorySearcher + $Searcher.SearchRoot = New-Object System.DirectoryServices.DirectoryEntry('LDAP://cn=subnets,cn=sites,' + $objRootDSE.ConfigurationNamingContext) + $Searcher.PageSize = 10000 + $Searcher.SearchScope = 'Subtree' + $Searcher.Filter = '(objectClass=subnet)' + + $Properties = @('cn', 'location', 'siteobject') + $Searcher.PropertiesToLoad.AddRange(@($Properties)) + $Subnets = $Searcher.FindAll() + + $selectedProperties = $Properties | ForEach-Object { @{name = "$_"; expression = $ExecutionContext.InvokeCommand.NewScriptBlock("`$_['$_']") } } + [Regex] $RegexCN = 'CN=(.*?),.*' + $SubnetsArray = @() + + foreach ( $Subnet in $Subnets ) { + # Construct the subnet object + $SubnetObj = New-Object -TypeName PsObject + $SubnetObj | Add-Member -type NoteProperty -Name Name -Value ([string] $Subnet.Properties['cn']) + $SubnetObj | Add-Member -type NoteProperty -Name Location -Value ([string] $Subnet.Properties['location']) + $SubnetObj | Add-Member -type NoteProperty -Name Site -Value ([string] $RegexCN.Match( $Subnet.Properties['siteobject']).Groups[1].Value) + + $InputAddress = (($SubnetObj.Name).Split('/'))[0] + $ADSubnetPrefix = (($SubnetObj.Name).Split('/'))[1] + + # Construct System.Net.IPAddress + $ObjInputAddress = [System.Net.IPAddress] $InputAddress + + # Check if IP is a IPv4 (IPv6 not collected) + if ( $ObjInputAddress.AddressFamily -eq 'InterNetwork' ) { + $SubnetObj = Compute-IPv4 $SubnetObj $ObjInputAddress $ADSubnetPrefix + $SubnetsArray += $SubnetObj + } + } + + $SubnetsArray | Export-Csv "$($Path)\ADSubnets-Export.csv" -Delimiter ';' -NoTypeInformation -Force + + if ( Test-Path "$($Path)\ADSubnets-Export.csv" ) { + Write-Host "$($Path)\ADSubnets-Export.csv" -ForegroundColor Green + } else { + Write-Host 'Error while exporting result to file.' -ForegroundColor Yellow + } + + $Subnets = $SubnetsArray | Sort-Object -Property RangeStart + + # Check if subnets are not already created + foreach ($Item in $ArrIPs) { + $SubnetIsExisting = $Subnets | Where-Object { ($Item.RangeStart -ge $_.RangeStart) -and ($Item.RangeEnd -le $_.RangeEnd) } + + if ( ($SubnetIsExisting) -and ($ArrIPs.Count -gt 1) ) { + [array]::Clear($ArrIPs, ([array]::IndexOf($ArrIPs, $Item)), 1) + } + } + + # Export Missing subnets + if ( $ArrIPs ) { + $ArrIPs | Where-Object Type -NE $null | Export-Csv "$($Path)\ADSubnets-MissingSubnets.csv" -Delimiter ';' -NoTypeInformation -Force + + if ( Test-Path "$($Path)\ADSubnets-MissingSubnets.csv" ) { + Write-Host "`nList of missing subnets: " -NoNewline + Write-Host "$($Path)\ADSubnets-MissingSubnets.csv" -ForegroundColor Green + } else { + Write-Host "`nError while exporting missing subnets to file." -ForegroundColor Red + } + } else { + Write-Host "`nNo Missing subnet found. Try with a greater netmask." -ForegroundColor Yellow + } } #EndRegion diff --git a/Active Directory/Domain Services/DCDIAG Complete Testing.ps1 b/Active Directory/Domain Services/DCDIAG Complete Testing.ps1 index 201d7fc..4861498 100644 --- a/Active Directory/Domain Services/DCDIAG Complete Testing.ps1 +++ b/Active Directory/Domain Services/DCDIAG Complete Testing.ps1 @@ -9,7 +9,7 @@ .NOTES Requires ADDS management tools (dcdiag.exe, netdom.exe) which are installed with an ADDS role automatically or when adding the ADDS management tools feature to any computer. Minimizes dependencies by not requiring the ActiveDirectory PowerShell module. - + DCDIAG Reference: https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/cc731968(v=ws.11) AUTHOR : Sam Erde @@ -20,17 +20,16 @@ https://twitter.com/SamErde #> -$TimeStamp = Get-Date -Format "yyymmdd_hhmmss" +$TimeStamp = Get-Date -Format 'yyymmdd_hhmmss' $LogFile = "dcdiag_$TimeStamp.log" # Find the PDC FSMO role holder. try { - $PDCe = ( ( ( (& netdom query PDC) | - Select-String -Pattern "The command completed successfully." -Context 1 -SimpleMatch) -Split '\r?\n' ) | - Select-Object -Index 0).Trim() -} -catch { - Write-Output "Failed to find a PDCe." + $PDCe = ( ( ( (& netdom query PDC) | + Select-String -Pattern 'The command completed successfully.' -Context 1 -SimpleMatch) -Split '\r?\n' ) | + Select-Object -Index 0).Trim() +} catch { + Write-Output 'Failed to find a PDCe.' $PDCe = $null } @@ -38,9 +37,9 @@ catch { & dcdiag /s:$PDCe /e /v /i /f:$LogFile # Find lines that note omitted tests and add each unique test name to an array -$OmittedTests = (Get-Content -Path $LogFile | Select-String -Pattern "Test omitted" -SimpleMatch | Select-Object -Unique) -Replace(" Test omitted by user request: ","") +$OmittedTests = (Get-Content -Path $LogFile | Select-String -Pattern 'Test omitted' -SimpleMatch | Select-Object -Unique) -Replace (' Test omitted by user request: ', '') foreach ($item in $OmittedTests) { - $TestLogFile = 'dcdiag_'+$TimeStamp+'_'+$item+'.log' + $TestLogFile = 'dcdiag_' + $TimeStamp + '_' + $item + '.log' & dcdiag.exe /Test:$item /e /f:$TestLogFile } diff --git a/Active Directory/Domain Services/DNSZonesRemote.ps1 b/Active Directory/Domain Services/DNSZonesRemote.ps1 index d389b71..1acf5a4 100644 --- a/Active Directory/Domain Services/DNSZonesRemote.ps1 +++ b/Active Directory/Domain Services/DNSZonesRemote.ps1 @@ -3,7 +3,7 @@ Loop through a list of specified domain controllers, and then loop through all DNS Server zones on each domain controller to make desired changes. .DESCRIPTION -This script was written to change the Secondary Servers setting and the SecureSecondaries setting on all DNS zones on all DNS Servers (all domain controllers, +This script was written to change the Secondary Servers setting and the SecureSecondaries setting on all DNS zones on all DNS Servers (all domain controllers, in our environment.) It provides an ideal way to adjust settings for one (or all) zones across every zone server, because some settings are stored individually in each server's registry, and not completed replicated, even when the zone is AD-integrated. @@ -12,8 +12,8 @@ of zones ("$zones = Get-ChildItem ...") from the registry can be run manually on manual changes could. .NOTES -Be sure to test your changes first by using -WhatIf on the Set-ItemProperty cmdlets, and also by testing your changes manually with at least one zone. Check the -registry and the GUI after running your script, and note that changing some zone settings via the registry will require the DNS Server service to be restarted +Be sure to test your changes first by using -WhatIf on the Set-ItemProperty cmdlets, and also by testing your changes manually with at least one zone. Check the +registry and the GUI after running your script, and note that changing some zone settings via the registry will require the DNS Server service to be restarted in order for those changes to be read and take effect. #> @@ -27,26 +27,25 @@ $creds = Get-Credential foreach ($srv in $servers) { $server = $srv.Hostname $session = New-PSSession -ComputerName $server -Name $server -Credential $creds - Try { + Try { Write-Host -ForegroundColor Green "Connecting to $server... " -NoNewline - Enter-PSSession $session - } - Catch { + Enter-PSSession $session + } Catch { Write-Host -ForegroundColor DarkYellow "Failed to enter the PSSession for $server. Skipping." - Continue + Continue } Write-Output $session.State $zones = Get-ChildItem -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones\' - foreach ($zone in $zones) { - Write-Host -NoNewline -ForegroundColor Yellow `n`n "Name: " (Get-ItemProperty -PSPath $zone.PSPath).PSChildName - Write-Host -NoNewline `n "SecondaryServers: " (Get-ItemProperty -PSPath $zone.PSPath).SecondaryServers - Write-Host -NoNewline `n "SecureSecondaries: " (Get-ItemProperty -PSPath $zone.PSPath).SecureSecondaries `n + foreach ($zone in $zones) { + Write-Host -NoNewline -ForegroundColor Yellow `n`n 'Name: ' (Get-ItemProperty -PSPath $zone.PSPath).PSChildName + Write-Host -NoNewline `n 'SecondaryServers: ' (Get-ItemProperty -PSPath $zone.PSPath).SecondaryServers + Write-Host -NoNewline `n 'SecureSecondaries: ' (Get-ItemProperty -PSPath $zone.PSPath).SecureSecondaries `n - #Set-ItemProperty -PSPath $zone.PSPath -Name "SecondaryServers" -Value "" -Whatif - #Set-ItemProperty -PSPath $zone.PSPath -Name "SecureSecondaries" -Value "3" -Whatif - } + #Set-ItemProperty -PSPath $zone.PSPath -Name "SecondaryServers" -Value "" -Whatif + #Set-ItemProperty -PSPath $zone.PSPath -Name "SecureSecondaries" -Value "3" -Whatif + } #Cleanup and then show the current PSSession state. if ($session) { Exit-PSSession } diff --git a/Active Directory/Find-DeepestOU.ps1 b/Active Directory/Find-DeepestOU.ps1 index 9813db8..d70102b 100644 --- a/Active Directory/Find-DeepestOU.ps1 +++ b/Active Directory/Find-DeepestOU.ps1 @@ -17,9 +17,9 @@ function Find-DeepestOU { if ($SubOUs.Count -eq 0) { Return @{ Depth = $CurrentDeepest - OU=$OU + OU = $OU } - + foreach ($ou in $SubOUs) { Return (Find-Deepest $ou $CurrentDepth) } diff --git a/Active Directory/Get-GPOsMissingPermissions.ps1 b/Active Directory/Get-GPOsMissingPermissions.ps1 index ab511bd..f2a23a2 100644 --- a/Active Directory/Get-GPOsMissingPermissions.ps1 +++ b/Active Directory/Get-GPOsMissingPermissions.ps1 @@ -10,44 +10,42 @@ #Find Group Policies with Missing Permissions Function Get-GPOsMissingPermissions { - $GPOs = Get-GPO -all + $GPOs = Get-GPO -All # Check for GPOs missing Authenticated Users and Domain Computers $GPOsMissingPermissions = New-Object System.Collections.ArrayList foreach ($item in $GPOs) { - $GPOPermAuthUsers = Get-GPPermission -Guid $GPO.Id -All | Select-Object -ExpandProperty Trustee | Where-Object {$_.Name -eq "Authenticated Users"} - $GPOPermDomainComputers = Get-GPPermission -Guid $GPO.Id -All | Select-Object -ExpandProperty Trustee | Where-Object {$_.Name -eq "Domain Computers"} + $GPOPermAuthUsers = Get-GPPermission -Guid $GPO.Id -All | Select-Object -ExpandProperty Trustee | Where-Object { $_.Name -eq 'Authenticated Users' } + $GPOPermDomainComputers = Get-GPPermission -Guid $GPO.Id -All | Select-Object -ExpandProperty Trustee | Where-Object { $_.Name -eq 'Domain Computers' } - If (!$GPOPermAuthUsers -and !$GPOPermDomainComputers) { - $GPOsMissingPermissions.Add($item)| Out-Null + If (!$GPOPermAuthUsers -and !$GPOPermDomainComputers) { + $GPOsMissingPermissions.Add($item) | Out-Null } } If ($GPOsMissingPermissions.Count -ne 0) { - Write-Warning "The following Group Policy Objects do not grant any permissions to the 'Authenticated Users' or 'Domain Computers' groups:" + Write-Warning "The following Group Policy Objects do not grant any permissions to the 'Authenticated Users' or 'Domain Computers' groups:" foreach ($item in $GPOsMissingPermissions) { Write-Host "'$($item.DisplayName)'" } - } - Else { - Write-Host "There are no GPOs missing permissions for Authenticated Users AND Domain Computers." + } Else { + Write-Host 'There are no GPOs missing permissions for Authenticated Users AND Domain Computers.' } # Check for GPOs missing Authenticated Users $GPOsMissingAuthenticatedUsers = New-Object System.Collections.ArrayList foreach ($item in $GPOs) { - $GPOPermissionForAuthUsers = Get-GPPermission -Guid $item.Id -All | Select-Object -ExpandProperty Trustee | Where-Object {$_.Name -eq "Authenticated Users"} + $GPOPermissionForAuthUsers = Get-GPPermission -Guid $item.Id -All | Select-Object -ExpandProperty Trustee | Where-Object { $_.Name -eq 'Authenticated Users' } If (!$GPOPermissionForAuthUsers) { - $GPOsMissingAuthenticatedUsers.Add($item)| Out-Null + $GPOsMissingAuthenticatedUsers.Add($item) | Out-Null } } If ($GPOsMissingAuthenticatedUsers.Count -ne 0) { - Write-Warning "The following Group Policy Objects do not grant any permissions to the 'Authenticated Users' security principal:" + Write-Warning "The following Group Policy Objects do not grant any permissions to the 'Authenticated Users' security principal:" foreach ($item in $GPOsMissingAuthenticatedUsers) { Write-Host "'$($item.DisplayName)'" } - } - Else { - Write-Host "There are no GPOs missing permissions for Authenticated Users." + } Else { + Write-Host 'There are no GPOs missing permissions for Authenticated Users.' } } diff --git a/Active Directory/Get-OverlappingOUNames.ps1 b/Active Directory/Get-OverlappingOUNames.ps1 index 36cd285..85adfc5 100644 --- a/Active Directory/Get-OverlappingOUNames.ps1 +++ b/Active Directory/Get-OverlappingOUNames.ps1 @@ -1,18 +1,18 @@ function Get-OverlappingOUNames { [CmdletBinding()] param ( - + ) - + begin { Import-Module ActiveDirectory } - + process { $OUs = Get-ADOrganizationalUnit -Filter * - $OverlappingOUNames = $OUs | Group-Object -Property Name | Where-Object {$_.Count -gt 1} + $OverlappingOUNames = $OUs | Group-Object -Property Name | Where-Object { $_.Count -gt 1 } } - + end { $OverlappingOUNames } diff --git a/Active Directory/Set DNS Server Zone Settings via Registry.ps1 b/Active Directory/Set DNS Server Zone Settings via Registry.ps1 index 6e9dc94..fe51c38 100644 --- a/Active Directory/Set DNS Server Zone Settings via Registry.ps1 +++ b/Active Directory/Set DNS Server Zone Settings via Registry.ps1 @@ -40,26 +40,25 @@ $creds = Get-Credential foreach ($srv in $servers) { $server = $srv.Hostname $session = New-PSSession -ComputerName $server -Name $server -Credential $creds - Try { + Try { Write-Host "Connecting to $server... " -ForegroundColor Green -NoNewline - Enter-PSSession $session - } - Catch { + Enter-PSSession $session + } Catch { Write-Host "Failed to enter the PSSession for $server. Skipping." -ForegroundColor DarkYellow - Continue + Continue } Write-Output $session.State $zones = Get-ChildItem -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones\' - foreach ($zone in $zones) { - Write-Host "`n`nName: $((Get-ItemProperty -PSPath $zone.PSPath).PSChildName)" -NoNewline -ForegroundColor Yellow - Write-Host "`nSecondaryServers: $((Get-ItemProperty -PSPath $zone.PSPath).SecondaryServers)" -NoNewline - Write-Host "`nSecureSecondaries: $((Get-ItemProperty -PSPath $zone.PSPath).SecureSecondaries) `n" -NoNewline + foreach ($zone in $zones) { + Write-Host "`n`nName: $((Get-ItemProperty -PSPath $zone.PSPath).PSChildName)" -NoNewline -ForegroundColor Yellow + Write-Host "`nSecondaryServers: $((Get-ItemProperty -PSPath $zone.PSPath).SecondaryServers)" -NoNewline + Write-Host "`nSecureSecondaries: $((Get-ItemProperty -PSPath $zone.PSPath).SecureSecondaries) `n" -NoNewline - #Set-ItemProperty -PSPath $zone.PSPath -Name "SecondaryServers" -Value "" -Whatif - #Set-ItemProperty -PSPath $zone.PSPath -Name "SecureSecondaries" -Value "3" -Whatif - } + #Set-ItemProperty -PSPath $zone.PSPath -Name "SecondaryServers" -Value "" -Whatif + #Set-ItemProperty -PSPath $zone.PSPath -Name "SecureSecondaries" -Value "3" -Whatif + } #Cleanup and then show the current PSSession state. diff --git a/DDI/Resolve-IPs.ps1 b/DDI/Resolve-IPs.ps1 index 97e7bf3..d767cd3 100644 --- a/DDI/Resolve-IPs.ps1 +++ b/DDI/Resolve-IPs.ps1 @@ -1,28 +1,28 @@ <# -Description: Resolve the hostnames for a list of IP addresses. +Description: Resolve the hostnames for a list of IP addresses. -To Do: +To Do: - Add version handling so PowerShell 7 can take advantage of parallel for loops - Add error handling and logging option #> # Add IP addresses, one per line, without additional quotes -$ListOfIPs = @" -"@ -split [Environment]::NewLine +$ListOfIPs = @' + +'@ -split [Environment]::NewLine $ResultList = @() foreach ($IP in $ListOfIPs) { - $ErrorActionPreference = "silentlycontinue" + $ErrorActionPreference = 'silentlycontinue' $Result = $null - write-host "Resolving $IP" -ForegroundColor Green + Write-Host "Resolving $IP" -ForegroundColor Green $result = [System.Net.Dns]::gethostentry($IP) If ($Result) { $ResultList += "$IP," + [string]$Result.HostName - } - Else { + } Else { $ResultList += "$IP,unresolved" } } diff --git a/DDI/Validate IP Address.ps1 b/DDI/Validate IP Address.ps1 index 84e29a1..227cc36 100644 --- a/DDI/Validate IP Address.ps1 +++ b/DDI/Validate IP Address.ps1 @@ -8,20 +8,20 @@ [] Follows standard of inserting zeroes in octects where an apparent value is not specified. (Example: [IPAddress] 2.2) #> -$IP = "10.253.26.1" +$IP = '10.253.26.1' $IP -eq ([IPAddress]$IP).IPAddressToString # RESULT: True -$IP = "1" +$IP = '1' $IP -eq ([IPAddress]$IP).IPAddressToString # RESULT: False -$IP = "300.1.1.1" +$IP = '300.1.1.1' $IP -eq ([IPAddress]$IP).IPAddressToString # ERROR: Connot convert value "300.1.1.1" to type "System.Net.IPAddress". Error: "An invalid IP address was specified." -[IPAddress] "10.253.26.1" -<# OUTPUT: +[IPAddress] '10.253.26.1' +<# OUTPUT: Address : 18545930 AddressFamily : InterNetwork ScopeId : @@ -31,4 +31,4 @@ $IP -eq ([IPAddress]$IP).IPAddressToString IsIPv6Teredo : False IsIPv4MappedToIPv6 : False IPAddressToString : 10.253.26.1 -#> \ No newline at end of file +#> diff --git a/Defender/MDI/Disable-NetAdapterLso.ps1 b/Defender/MDI/Disable-NetAdapterLso.ps1 index 3783e84..513a049 100644 --- a/Defender/MDI/Disable-NetAdapterLso.ps1 +++ b/Defender/MDI/Disable-NetAdapterLso.ps1 @@ -1,20 +1,20 @@ <# .SYNOPSIS Disable Large Send Offload on all network adapters. - + .DESCRIPTION This script disabled Large Send Offload (LSO) on all network adapters. It resolves a potential issue with Microsoft Defender - for Identity (MDI) in which you might receive a health alert that states "Some netowkr traffic is not being analyzed." - + for Identity (MDI) in which you might receive a health alert that states "Some netowkr traffic is not being analyzed." + WARNING: Depending on your configuration, this might cause a brief loss of network connectivity when the adapter configuration changes. - + .NOTE Reference: https://docs.microsoft.com/en-us/defender-for-identity/troubleshooting-known-issues#vmware-virtual-machine-sensor-issue #> # Disable Large Send Offload (LSO) if it is enabled on domain controllers' virtual NICs. Write-Output "`nDisabling Large Send Offload..." -(Get-NetAdapterAdvancedProperty).Where({ $_.DisplayName -Match "^Large*" }) | Disable-NetAdapterLso -Verbose -WhatIf +(Get-NetAdapterAdvancedProperty).Where({ $_.DisplayName -Match '^Large*' }) | Disable-NetAdapterLso -Verbose -WhatIf Write-Output "`nNetwork adapters with Large Send Offload enabled: `n" -Get-NetAdapterAdvancedProperty | Where-Object { $_.DisplayName -Match "^Large*" } +Get-NetAdapterAdvancedProperty | Where-Object { $_.DisplayName -Match '^Large*' } diff --git a/Defender/MDI/Install-MDI.ps1 b/Defender/MDI/Install-MDI.ps1 index bc03c9a..e960181 100644 --- a/Defender/MDI/Install-MDI.ps1 +++ b/Defender/MDI/Install-MDI.ps1 @@ -4,7 +4,7 @@ $AccessKey = '' .\"Azure ATP sensor Setup.exe" /quiet NetFrameworkCommandLineArguments="/q" AccessKey=$AccessKey -<# Review notes and script the creation of gMSAs and/or service accounts, and give credit: +<# Review notes and script the creation of gMSAs and/or service accounts, and give credit: https://dirteam.com/sander/2022/03/23/howto-programmatically-add-a-microsoft-defender-for-identity-action-account-to-active-directory/ #> @@ -13,5 +13,4 @@ $AccessKey = '' - Extract the installation files from the zip file. Installing directly from the zip file will fail. - Run Azure ATP sensor setup.exe with elevated privileges (Run as administrator) and follow the setup wizard. - Install KB 3047154 for Windows Server 2012 R2 only. - - -#> \ No newline at end of file +#> diff --git a/Entra/Get Entra Applications.ps1 b/Entra/Get Entra Applications.ps1 index 66f07e9..3eb846b 100644 --- a/Entra/Get Entra Applications.ps1 +++ b/Entra/Get Entra Applications.ps1 @@ -1,8 +1,8 @@ # Get a list of all Entra Applications and the most recent interactive sign-in for each Connect-MgGraph -Scopes AuditLog.Read.All -Get-MgApplication -All | - ForEach-Object { - $x = Get-MgAuditLogSignIn -Filter "appId eq '$($_.AppId)'" -Top 1 -OrderBy 'createdDateTime desc' - [pscustomobject]@{Id = $_.Id; DisplayName = $_.DisplayName; LastSignIn = $x.CreatedDateTime } - } +Get-MgApplication -All | + ForEach-Object { + $x = Get-MgAuditLogSignIn -Filter "appId eq '$($_.AppId)'" -Top 1 -OrderBy 'createdDateTime desc' + [pscustomobject]@{Id = $_.Id; DisplayName = $_.DisplayName; LastSignIn = $x.CreatedDateTime } + } diff --git a/Entra/M365 Service URLs.ps1 b/Entra/M365 Service URLs.ps1 index f33a6ab..021a56c 100644 --- a/Entra/M365 Service URLs.ps1 +++ b/Entra/M365 Service URLs.ps1 @@ -9,7 +9,7 @@ Get-InstanceNames Pull a list of all Microsoft cloud instance names. They include the general worldwide instance, government, DoD, and foreign instances. - + .EXAMPLE Get-ServiceAreas @@ -28,7 +28,7 @@ .NOTES Reference Documentation: - + Office 365 IP Addresses and URL Web Service https://docs.microsoft.com/en-us/microsoft-365/enterprise/microsoft-365-ip-web-service?view=o365-worldwide @@ -37,7 +37,7 @@ #> # $Guid = New-Guid -[Guid]$global:Guid = "f31523fa-cb44-44e6-9a13-037a0643e282" +[Guid]$global:Guid = 'f31523fa-cb44-44e6-9a13-037a0643e282' function Get-InstanceNames { # Get a list of available cloud instance names (gov, foreign, worldwide, etc) using a GUID for the request. @@ -51,21 +51,20 @@ function Get-ServiceAreas { # Get all service area names for a given instance. [CmdletBinding()] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ArgumentCompleter({ - param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) - if (!($global:InstanceNames)) { - Get-InstanceNames - } - else { - Return $global:InstanceNames - } - })] + param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) + if (!($global:InstanceNames)) { + Get-InstanceNames + } else { + Return $global:InstanceNames + } + })] [string] $Instance ) - $Uri = "https://endpoints.office.com/endpoints/$Instance"+"?clientrequestid=$Guid" + $Uri = "https://endpoints.office.com/endpoints/$Instance" + "?clientrequestid=$Guid" $global:ServiceAreas = ( ( ((Invoke-WebRequest -Uri $Uri).Content) | ConvertFrom-Json ) | Select-Object serviceArea -Unique).servicearea Return $global:ServiceAreas } # End function Get-ServiceAreas @@ -74,16 +73,15 @@ function Get-ServiceUrls { # Get all service URLs for a selected instance and service area. [CmdletBinding()] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ArgumentCompleter({ - param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) - if (!($global:InstanceNames)) { - Get-InstanceNames - } - else { - Return $global:InstanceNames - } - })] + param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) + if (!($global:InstanceNames)) { + Get-InstanceNames + } else { + Return $global:InstanceNames + } + })] [string] $Instance, [ArgumentCompleter( @@ -91,14 +89,12 @@ function Get-ServiceUrls { param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) if (!($global:InstanceNames)) { Get-InstanceTypes - } - elseif (!($ServiceAreas)) { + } elseif (!($ServiceAreas)) { if (!($Instance)) { - $Instance = "Worldwide" + $Instance = 'Worldwide' } Get-ServiceAreas -InstanceName $Instance - } - else { + } else { Return $global:ServiceAreas } } @@ -107,7 +103,7 @@ function Get-ServiceUrls { $ServiceArea ) - $Uri = "https://endpoints.office.com/endpoints/$Instance"+"?clientrequestid=$Guid"+'&'+"serviceArea=$ServiceArea" + $Uri = "https://endpoints.office.com/endpoints/$Instance" + "?clientrequestid=$Guid" + '&' + "serviceArea=$ServiceArea" $global:ServiceUrls = ( ( ((Invoke-WebRequest -Uri $Uri).Content) | ConvertFrom-Json ) | Select-Object urls -Unique).urls Return $global:ServiceUrls } @@ -126,7 +122,7 @@ function Get-ServiceUrls { $Expression = "$Variablename = " + '"' + "https://endpoints.office.com/endpoints/"+"$item"+"?clientrequestid=$($Guid)" + '"' Invoke-Expression $Expression Write-Output `n"A new URI variable has been created for $item : " $Expression - } + } #> diff --git a/Exchange/Add-EmailAddressDomain.ps1 b/Exchange/Add-EmailAddressDomain.ps1 index f5a42b0..12495ce 100644 --- a/Exchange/Add-EmailAddressDomain.ps1 +++ b/Exchange/Add-EmailAddressDomain.ps1 @@ -9,16 +9,16 @@ function Add-EmailAddressDomain { .PARAMETER NewDomain The new domain name that will be added for all existing email addresses. - + .PARAMETER ReportOnly Create a CSV report showing current and new addresses but do not make any changes. - + .PARAMETER ReportFilePath Path to save the exported CSV report in. - + .PARAMETER Passthru Return the CSVData in the script output. - + .PARAMETER BatchSize Set the batch size for the number of recipients to update before waiting to start the next batch of changes. This can be used to reduce change congestion in AD replication or to minimize the number of changes that get @@ -31,17 +31,17 @@ function Add-EmailAddressDomain { For a recipient with the following email addresses: user@domain.com, userFirst.userLast@domain.com, customUser@domain.com Add-EmailAddressDomain -NewDomain 'example.com' - + The script will add the following email addresses to the recipient: user@example.com, userFirst.userLast@example.com, customUser@example.com - + .EXAMPLE For a recipient with the following email addresses: user@domain.com, userFirst.userLast@domain.com, customUser@domain.com Add-EmailAddressDomain -NewDomain 'example.com' -BatchSize 250 -Delay 15 - + The script will add the following email addresses to the recipient: user@example.com, userFirst.userLast@example.com, customUser@example.com It will update 250 recipients at a time, then wait 15 minutes before updating the next batch of 250 recipients. - + .EXAMPLE Add-EmailAddressDomain -NewDomain 'example.com' -ReportOnly -ReportFilePath '.\New Email Address Report.csv' @@ -53,7 +53,7 @@ function Add-EmailAddressDomain { $ReportData = Add-EmailAddressDomain -NewDomain 'powershealth.org' -ReportOnly -Passthru $ReportData | ConvertTo-Csv -NoTypeInformation -Delimiter ';' | Set-Clipboard - + .NOTES Version: 0.4.3 Modified: 2024-06-11 @@ -70,7 +70,7 @@ function Add-EmailAddressDomain { [ValidateNotNullOrEmpty()] [string] $NewDomain, - + # Switch to export a CSV of potential changes instead of making changes. [Parameter(ParameterSetName = 'ReportOnly')] [switch] @@ -136,7 +136,7 @@ function Add-EmailAddressDomain { $SmtpAddresses = $recipient.EmailAddresses | Where-Object { $_.PrefixString -eq 'smtp' } $CurrentAddresses = ($SmtpAddresses.addressstring | Sort-Object) #-join ', ' $NewEmailAddresses = foreach ($address in $($SmtpAddresses.addressstring)) { - $address -replace '@.*',"@$NewDomain" + $address -replace '@.*', "@$NewDomain" } $NewEmailAddresses = ($NewEmailAddresses | Sort-Object -Unique) #-join ', ' @@ -144,10 +144,10 @@ function Add-EmailAddressDomain { if ($ReportOnly) { $CSVData.Add( [PSCustomObject]@{ - Name = $($recipient.DisplayName) - Alias = $($recipient.alias) + Name = $($recipient.DisplayName) + Alias = $($recipient.alias) CurrentEmailAddresses = [string]($CurrentAddresses -join ', ') - NewEmailAddresses = [string]($NewEmailAddresses -join ', ') + NewEmailAddresses = [string]($NewEmailAddresses -join ', ') } ) | Out-Null # Continue to the next recipient in report-only mode instead of adding addresses. @@ -171,7 +171,7 @@ function Add-EmailAddressDomain { if ($SmtpAddresses -notcontains $address -and $ValidEmailAddress -eq $true) { # Add the new email address to the recipient. Write-Information "`t`t`t Add address: '$address'" -InformationAction Continue - Set-Mailbox -Identity $recipient.Identity -EmailAddresses @{Add="$address"} + Set-Mailbox -Identity $recipient.Identity -EmailAddresses @{Add = "$address" } } } # End foreach $address Write-Output "`n" diff --git a/Exchange/Get Email Addresses with a Digit Before the At Sign.ps1 b/Exchange/Get Email Addresses with a Digit Before the At Sign.ps1 index 606ba82..5afd538 100644 --- a/Exchange/Get Email Addresses with a Digit Before the At Sign.ps1 +++ b/Exchange/Get Email Addresses with a Digit Before the At Sign.ps1 @@ -14,8 +14,8 @@ $NumberAts = Get-Mailbox -ResultSize Unlimited -SortBy alias | Where-Object { $_.alias -match '\d$' -or $_.PrimarySmtpAddress -match '^.*\d@.*$' } -$NumberAts | +$NumberAts | Select-Object DisplayName, alias, EmailAddressPolicyEnabled, WindowsEmailAddress, PrimarySmtpAddress, - @{Name = 'SmtpAddresses'; Expression = { $_.emailaddresses.smtpAddress -join ', ' } } | - ConvertTo-Csv -NoTypeInformation -Delimiter ';' | + @{Name = 'SmtpAddresses'; Expression = { $_.emailaddresses.smtpAddress -join ', ' } } | + ConvertTo-Csv -NoTypeInformation -Delimiter ';' | Set-Clipboard diff --git a/Exchange/Get-AliasConflicts.ps1 b/Exchange/Get-AliasConflicts.ps1 index 7ab639c..793273d 100644 --- a/Exchange/Get-AliasConflicts.ps1 +++ b/Exchange/Get-AliasConflicts.ps1 @@ -1,5 +1,5 @@ function Get-AliasConflicts { -<# + <# .SYNOPSIS Get all conflicting recipient aliases in Exchange. .DESCRIPTION @@ -10,10 +10,10 @@ function Get-AliasConflicts { Version: 0.0.3 #> [CmdletBinding()] - [OutputType([Microsoft.Exchange.Data.Directory.Management.ReducedRecipient],[Microsoft.PowerShell.Commands.GroupInfo])] + [OutputType([Microsoft.Exchange.Data.Directory.Management.ReducedRecipient], [Microsoft.PowerShell.Commands.GroupInfo])] param ( # Optionally specify the Resultsize parameter as an integer or 'Unlimited' - [ValidatePattern('^(\d+|Unlimited)$')] + [ValidatePattern('^(\d+|Unlimited)$')] $Resultsize = 'Unlimited', # Optionally copy the results to the clipboard as a CSV @@ -23,14 +23,14 @@ function Get-AliasConflicts { Write-Information "Checking $Resultsize recipients..." $Recipients = Get-Recipient -Resultsize $Resultsize -SortBy alias - $OverlappingAliases = $Recipients | Group-Object -Property alias | Where-Object {$_.Count -gt 1} + $OverlappingAliases = $Recipients | Group-Object -Property alias | Where-Object { $_.Count -gt 1 } # Replace domainName.tld below. This replace statement is simply used to add commas between recipient IDs. $AliasConflicts = $OverlappingAliases | - Select-Object Count, - @{Name='Alias';Expression={$_.Name}}, - @{Name="ConflictedRecipients";Expression={[string]($_.Group) -Replace ' domainName.tld/',', domainName.tld/' }} - + Select-Object Count, + @{Name = 'Alias'; Expression = { $_.Name } }, + @{Name = 'ConflictedRecipients'; Expression = { [string]($_.Group) -Replace ' domainName.tld/', ', domainName.tld/' } } + if ($Clip) { $AliasConflicts | ConvertTo-Csv -NoTypeInformation -Delimiter ';' | Set-Clipboard } diff --git a/Exchange/Parse-TransportLogs.ps1 b/Exchange/Parse-TransportLogs.ps1 index 6147155..715f242 100644 --- a/Exchange/Parse-TransportLogs.ps1 +++ b/Exchange/Parse-TransportLogs.ps1 @@ -1,63 +1,63 @@ <# .SYNOPSIS - Parses Exchange SMTP Transport logs and shows a summary of how many messages are sent by each IP/hostname. + Parses Exchange SMTP Transport logs and shows a summary of how many messages are sent by each IP/hostname. Groups by message subject to help identify what applications or jobs might be sending the messages. .DESCRIPTION Find out which machines are sending SMTP messages to your Exchange receive connectors. I like to pull SMTP transport logs from all Exchange Servers to a single local folder for parsing instead - of running this against the live logs. Only reads in log entries that have the action "Queued" in the line, + of running this against the live logs. Only reads in log entries that have the action "Queued" in the line, because a single SMTP transaction can generate many lines in the logs. .NOTES - Credits : Initial concept was inspired by Chris Lehr's blog at + Credits : Initial concept was inspired by Chris Lehr's blog at http://blog.chrislehr.com/2015/07/parse-transportlogs-which-ips-on-my.html #> Set-ExecutionPolicy RemoteSigned $ExchangeCredential = Get-Credential -Message "Please enter credentials to connect to your Exchange Server. `nThis will be used to pull message subject lines from the tracking logs." -$ExchangeServer = Read-Host "Please specify an Exchange Server name." +$ExchangeServer = Read-Host 'Please specify an Exchange Server name.' $ExchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://$ExchangeServer.DOMAINNAME.org/PowerShell/ -Authentication Kerberos -Credential $ExchangeCredential Import-PSSession $ExchangeSession -DisableNameChecking $SMTPLogPath = Read-Host "`nWhat is the path of the folder containing your SMTP transport logs?" -$Days = Read-Host "How many days of logs do you want to inspect? (24 hour spans from the current system time.) Enter 0 or blank to parse all logs in the folder." +$Days = Read-Host 'How many days of logs do you want to inspect? (24 hour spans from the current system time.) Enter 0 or blank to parse all logs in the folder.' Write-Output "Reading in source SMTP log files from $SMTPLogPath and parsing for queued messages being sent." -$FilteredLogs = (Select-String $SMTPLogPath\*.log -pattern "Queued").Line - -$SMTPTransactions = ConvertFrom-Csv -InputObject $FilteredLogs -Header dt,connector,session,sequence-number,local,remote,event,data,context - $SMTPTransactions | Add-Member -MemberType NoteProperty -Name "IPAddress" -Value "" - $SMTPTransactions | Add-Member -MemberType NoteProperty -Name "Hostname" -Value "" - $SMTPTransactions | Add-Member -MemberType NoteProperty -Name "MessageID" -Value "" - $SMTPTransactions | Add-Member -MemberType NoteProperty -Name "Subject" -Value "" - $SMTPTransactions | Add-Member -MemberType NoteProperty -Name "DateTime" -Value "" - - foreach ($item in $SMTPTransactions) { - $item.DateTime = [DateTime]::Parse($item.dt) - $item.IPAddress = $item.remote -replace '(.*):(.*)','$1' - } +$FilteredLogs = (Select-String $SMTPLogPath\*.log -Pattern 'Queued').Line + +$SMTPTransactions = ConvertFrom-Csv -InputObject $FilteredLogs -Header dt, connector, session, sequence-number, local, remote, event, data, context +$SMTPTransactions | Add-Member -MemberType NoteProperty -Name 'IPAddress' -Value '' +$SMTPTransactions | Add-Member -MemberType NoteProperty -Name 'Hostname' -Value '' +$SMTPTransactions | Add-Member -MemberType NoteProperty -Name 'MessageID' -Value '' +$SMTPTransactions | Add-Member -MemberType NoteProperty -Name 'Subject' -Value '' +$SMTPTransactions | Add-Member -MemberType NoteProperty -Name 'DateTime' -Value '' + +foreach ($item in $SMTPTransactions) { + $item.DateTime = [DateTime]::Parse($item.dt) + $item.IPAddress = $item.remote -replace '(.*):(.*)', '$1' +} # Filter logs by the # of days chosen above, or parse all messages if the input is: zero, null, or an empty string. switch ($Days) { - { @(0, $null, "") -contains $_ } { $testSet = $SMTPTransactions } - Default { $testSet = $SMTPTransactions | Where-Object { $_.DateTime -gt (Get-Date).AddDays(-$Days)} } + { @(0, $null, '') -contains $_ } { $testSet = $SMTPTransactions } + Default { $testSet = $SMTPTransactions | Where-Object { $_.DateTime -gt (Get-Date).AddDays(-$Days) } } } $testProgress = 0; $testCount = $testSet.Count foreach ($item in $TestSet) { - $testProgress++; $testPercent = [math]::Round(($testProgress/$testCount),2)*100 - Write-Progress -Activity "Parsing message $testProgress of $testCount." -Status "$testPercent% complete" -PercentComplete $testPercent - - $data = ($item.data.split("<")[1]).Split(">")[0] - $subject = (Get-MessageTrackingLog -MessageId $data).MessageSubject | Select-Object -Unique - $item.MessageID = $data - $item.Subject = $subject - $ErrorActionPreference = "SilentlyContinue" #To avoid ambiguous error output if/when a hostname is not found. - $item.Hostname = ([System.Net.DNS]::GetHostbyAddress($item.IPAddress)).Hostname - $ErrorActionPreference = "Continue" + $testProgress++; $testPercent = [math]::Round(($testProgress / $testCount), 2) * 100 + Write-Progress -Activity "Parsing message $testProgress of $testCount." -Status "$testPercent% complete" -PercentComplete $testPercent + + $data = ($item.data.split('<')[1]).Split('>')[0] + $subject = (Get-MessageTrackingLog -MessageId $data).MessageSubject | Select-Object -Unique + $item.MessageID = $data + $item.Subject = $subject + $ErrorActionPreference = 'SilentlyContinue' #To avoid ambiguous error output if/when a hostname is not found. + $item.Hostname = ([System.Net.DNS]::GetHostbyAddress($item.IPAddress)).Hostname + $ErrorActionPreference = 'Continue' } Remove-PSSession $ExchangeSession.Id Write-Output "Showing $Days days of results:" -$Summary = $TestSet | Group-Object -Property Hostname,IPAddress,Subject -NoElement | Sort-Object -Property @{Expression={$_.Count}; Descending=$true}, @{Expression={$_.Subject}; Descending=$true} -Unique +$Summary = $TestSet | Group-Object -Property Hostname, IPAddress, Subject -NoElement | Sort-Object -Property @{Expression = { $_.Count }; Descending = $true }, @{Expression = { $_.Subject }; Descending = $true } -Unique $Summary | Format-Table -AutoSize diff --git a/Exchange/Remove Disabled Users from Distribution Groups.ps1 b/Exchange/Remove Disabled Users from Distribution Groups.ps1 index 96152be..d0f4ac1 100644 --- a/Exchange/Remove Disabled Users from Distribution Groups.ps1 +++ b/Exchange/Remove Disabled Users from Distribution Groups.ps1 @@ -2,8 +2,8 @@ $Groups = Get-DistributionGroup -ResultSize Unlimited foreach ($group in $Groups) { Write-Information "$group" - Get-DistributionGroupMember $group | - Where-Object {$_.RecipientType -like '*User*' -and $_.ResourceType -eq $null} | - Get-User | Where-Object {$_.UserAccountControl -match 'AccountDisabled'} | + Get-DistributionGroupMember $group | + Where-Object { $_.RecipientType -like '*User*' -and $_.ResourceType -eq $null } | + Get-User | Where-Object { $_.UserAccountControl -match 'AccountDisabled' } | Remove-DistributionGroupMember $group -Confirm:$false } diff --git a/Exchange/Set-ExchangeURLs.ps1 b/Exchange/Set-ExchangeURLs.ps1 index e0c05de..531f2ac 100644 --- a/Exchange/Set-ExchangeURLs.ps1 +++ b/Exchange/Set-ExchangeURLs.ps1 @@ -6,7 +6,7 @@ Start-Transcript -$ServerFqdn = @('','','') +$ServerFqdn = @('', '', '') $SmtpNameSpace = 'host.domain.tld' $BaseUri = "https://$SmtpNameSpace" @@ -20,40 +20,40 @@ foreach ($item in $ServerFqdn) { Write-Host -ForegroundColor Cyan "Updating server: $item ($ServerShort) with $BaseUri." Write-Host -ForegroundColor Green "$ServerShort : OWA" - Get-OwaVirtualDirectory -Server $item | - Set-OwaVirtualDirectory -InternalUrl "$BaseUri/owa" -ExternalUrl "$BaseUri/owa" - + Get-OwaVirtualDirectory -Server $item | + Set-OwaVirtualDirectory -InternalUrl "$BaseUri/owa" -ExternalUrl "$BaseUri/owa" + Write-Host -ForegroundColor Green "$ServerShort : ECP" - Get-EcpVirtualDirectory -Server $item | - Set-EcpVirtualDirectory -InternalUrl "$BaseUri/ecp" -ExternalUrl "$BaseUri/ecp" - + Get-EcpVirtualDirectory -Server $item | + Set-EcpVirtualDirectory -InternalUrl "$BaseUri/ecp" -ExternalUrl "$BaseUri/ecp" + Write-Host -ForegroundColor Green "$ServerShort : MAPI" - Get-MapiVirtualDirectory -Server $item | - Set-MapiVirtualDirectory -InternalUrl "$BaseUri/mapi" -ExternalUrl "$BaseUri/mapi" - + Get-MapiVirtualDirectory -Server $item | + Set-MapiVirtualDirectory -InternalUrl "$BaseUri/mapi" -ExternalUrl "$BaseUri/mapi" + Write-Host -ForegroundColor Green "$ServerShort : OAB" - Get-OabVirtualDirectory -Server $item | - Set-OabVirtualDirectory -InternalUrl "$BaseUri/OAB" -ExternalUrl "$BaseUri/OAB" - + Get-OabVirtualDirectory -Server $item | + Set-OabVirtualDirectory -InternalUrl "$BaseUri/OAB" -ExternalUrl "$BaseUri/OAB" + Write-Host -ForegroundColor Green "$ServerShort : EWS" - Get-WebServicesVirtualDirectory -Server $item | - Set-WebServicesVirtualDirectory -InternalUrl "$BaseUri/EWS/Exchange.asmx" -ExternalUrl "$BaseUri/EWS/Exchange.asmx" - + Get-WebServicesVirtualDirectory -Server $item | + Set-WebServicesVirtualDirectory -InternalUrl "$BaseUri/EWS/Exchange.asmx" -ExternalUrl "$BaseUri/EWS/Exchange.asmx" + Write-Host -ForegroundColor Green "$ServerShort : EAS" - Get-ActiveSyncVirtualDirectory -Server $item | - Set-ActiveSyncVirtualDirectory -InternalUrl "$BaseUri/Microsoft-Server-ActiveSync" -ExternalUrl "$BaseUri/Microsoft-Server-ActiveSync" - + Get-ActiveSyncVirtualDirectory -Server $item | + Set-ActiveSyncVirtualDirectory -InternalUrl "$BaseUri/Microsoft-Server-ActiveSync" -ExternalUrl "$BaseUri/Microsoft-Server-ActiveSync" + Write-Host -ForegroundColor Green "$ServerShort : OAW" - Get-OutlookAnywhere -Server $ServerShort | - Set-OutlookAnywhere -ExternalHostname $SmtpNameSpace -InternalHostname $SmtpNameSpace -ExternalClientsRequireSsl:$true -InternalClientsRequireSsl:$true -ExternalClientAuthenticationMethod 'Negotiate' - + Get-OutlookAnywhere -Server $ServerShort | + Set-OutlookAnywhere -ExternalHostname $SmtpNameSpace -InternalHostname $SmtpNameSpace -ExternalClientsRequireSsl:$true -InternalClientsRequireSsl:$true -ExternalClientAuthenticationMethod 'Negotiate' + Write-Host -ForegroundColor Green "$ServerShort : PS" - Get-PowerShellVirtualDirectory -Server $item | - Set-PowerShellVirtualDirectory -InternalUrl "http://$item/PowerShell" -ExternalUrl $null + Get-PowerShellVirtualDirectory -Server $item | + Set-PowerShellVirtualDirectory -InternalUrl "http://$item/PowerShell" -ExternalUrl $null Write-Host -ForegroundColor Green "$ServerShort : AutoDiscover" - (Get-ClientAccessService).Where({$_.Name -eq $ServerShort}) | - Set-ClientAccessService -AutodiscoverServiceInternalUri "https://autodiscover.domain.tld/Autodiscover/Autodiscover.xml" + (Get-ClientAccessService).Where({ $_.Name -eq $ServerShort }) | + Set-ClientAccessService -AutodiscoverServiceInternalUri 'https://autodiscover.domain.tld/Autodiscover/Autodiscover.xml' } Stop-Transcript @@ -64,14 +64,14 @@ foreach ($item in $ServerFqdn) { $ServerShort = $ServerFqdn.Split('.')[0] Write-Host -ForegroundColor Cyan "Checking server: $item ($ServerShort)." - Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : OWA" ; Get-OwaVirtualDirectory -Server $item | Format-List InternalUrl,ExternalUrl #-AutoSize -Wrap - Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : ECP" ; Get-EcpVirtualDirectory -Server $item | Format-List InternalUrl,ExternalUrl #-AutoSize -Wrap - Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : MAPI" ; Get-MapiVirtualDirectory -Server $item | Format-List InternalUrl,ExternalUrl #-AutoSize -Wrap - Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : OAB" ; Get-OabVirtualDirectory -Server $item | Format-List InternalUrl,ExternalUrl #-AutoSize -Wrap - Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : EWS" ; Get-WebServicesVirtualDirectory -Server $item | Format-List InternalUrl,ExternalUrl #-AutoSize -Wrap - Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : ActiveSync" ; Get-ActiveSyncVirtualDirectory -Server $item | Format-List InternalUrl,ExternalUrl #-AutoSize -Wrap - Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : PowerShell" ; Get-PowerShellVirtualDirectory -Server $item | Format-List InternalUrl,ExternalUrl #-AutoSize -Wrap - Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : CAS" ; Get-ClientAccessService -Identity $item | Format-List Name,AutoDiscoverServiceInternalUri #-AutoSize -Wrap - Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : AutoDiscover" ; Get-AutoDiscoverVirtualDirectory -Server $ServerShort | Format-List Name,InternalUrl,ExternalUrl #-AutoSize -Wrap - Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : Outlook Anywhere" ; Get-OutlookAnywhere -Server $ServerShort | Format-List InternalHostName,ExternalHostname #-AutoSize -Wrap + Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : OWA" ; Get-OwaVirtualDirectory -Server $item | Format-List InternalUrl, ExternalUrl #-AutoSize -Wrap + Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : ECP" ; Get-EcpVirtualDirectory -Server $item | Format-List InternalUrl, ExternalUrl #-AutoSize -Wrap + Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : MAPI" ; Get-MapiVirtualDirectory -Server $item | Format-List InternalUrl, ExternalUrl #-AutoSize -Wrap + Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : OAB" ; Get-OabVirtualDirectory -Server $item | Format-List InternalUrl, ExternalUrl #-AutoSize -Wrap + Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : EWS" ; Get-WebServicesVirtualDirectory -Server $item | Format-List InternalUrl, ExternalUrl #-AutoSize -Wrap + Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : ActiveSync" ; Get-ActiveSyncVirtualDirectory -Server $item | Format-List InternalUrl, ExternalUrl #-AutoSize -Wrap + Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : PowerShell" ; Get-PowerShellVirtualDirectory -Server $item | Format-List InternalUrl, ExternalUrl #-AutoSize -Wrap + Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : CAS" ; Get-ClientAccessService -Identity $item | Format-List Name, AutoDiscoverServiceInternalUri #-AutoSize -Wrap + Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : AutoDiscover" ; Get-AutoDiscoverVirtualDirectory -Server $ServerShort | Format-List Name, InternalUrl, ExternalUrl #-AutoSize -Wrap + Write-Host -ForegroundColor Yellow -NoNewline "$ServerShort : Outlook Anywhere" ; Get-OutlookAnywhere -Server $ServerShort | Format-List InternalHostName, ExternalHostname #-AutoSize -Wrap } diff --git a/General/Get-PatchTuesday.ps1 b/General/Get-PatchTuesday.ps1 index d9a9338..c5caa8b 100644 --- a/General/Get-PatchTuesday.ps1 +++ b/General/Get-PatchTuesday.ps1 @@ -1,10 +1,10 @@ Function Get-PatchTuesday { -<# + <# .SYNOPSIS Name: Get-PatchTuesday.ps1 Return the date of Patch Tuesday for a given month, and provide functions for our management and monitoring products to be able to mute their alerts when a given device is scheduled for patching. - + .DESCRIPTION Get the date of Patch Tuesday and the current internal patch week number. @@ -21,7 +21,7 @@ Function Get-PatchTuesday { Get the date of the current month's Patch Tuesday. Get-PatchTuesday -.EXAMPLE +.EXAMPLE Get the date of the previous month's Patch Tuesday: Get-PatchTuesday -CheckMonth Previous @@ -31,99 +31,96 @@ Function Get-PatchTuesday { #> -[CmdletBinding()] -param( - [Parameter(Mandatory=$False)] - [ValidateSet("Previous","Next","Current")] - [string]$CheckMonth, + [CmdletBinding()] + param( + [Parameter(Mandatory = $False)] + [ValidateSet('Previous', 'Next', 'Current')] + [string]$CheckMonth, - [Parameter(Mandatory=$False)] - [AllowEmptyString()] - [ValidateSet("CurrentWeek","AllWeeks")] - [string]$Output + [Parameter(Mandatory = $False)] + [AllowEmptyString()] + [ValidateSet('CurrentWeek', 'AllWeeks')] + [string]$Output ) -$Today = (Get-Date 00:00:00) -$ThisDay = $Today.DayOfWeek -[datetime]$FirstDay = (Get-Date 00:00:00 -Day 1) -[datetime]$NextFirstDay = (Get-Date 00:00:00 -Day 1).AddMonths(1) -[datetime]$LastFirstDay = (Get-Date 00:00:00 -Day 1).AddMonths(-1) - -Switch ($CheckMonth) { - "Previous" { $FirstDay = $FirstDay.AddMonths(-1) } - "Next" { $FirstDay = $FirstDay.AddMonths(1) } - "Current" { } # No change. - Default { } # No change. -} - -# Loop through the first 16 days of the specified month and select the second instance (index [1]) of a Tuesday. -[datetime]$PatchTuesday = (( 0..16 | ForEach-Object { $FirstDay.AddDays($_) } | Where-Object {$_.DayOfWeek -like "Tuesday"} )[1]) -[datetime]$NextPatchTuesday = (( 0..16 | ForEach-Object { $NextFirstDay.AddDays($_) } | Where-Object {$_.DayOfWeek -like "Tuesday"} )[1]) -[datetime]$LastPatchTuesday = (( 0..16 | ForEach-Object { $LastFirstDay.AddDays($_) } | Where-Object {$_.DayOfWeek -like "Tuesday"} )[1]) - -## Begin listing patch dates for the current and previous month. -# Begin listing the month's Saturday and Sunday patch groups and set their start times at 21:00. -$PatchWeeks = [ordered]@{ - "Week 1 Sunday" = $PatchTuesday.AddDays(5).AddHours(21); - "Week 2 Sunday" = $PatchTuesday.AddDays(12).AddHours(21); - "Week 3 Saturday" = $PatchTuesday.AddDays(18).AddHours(21); - "Week 3 Sunday" = $PatchTuesday.AddDays(19).AddHours(21); - "Week 4 Saturday" = $PatchTuesday.AddDays(25).AddHours(21); - "Week 4 Sunday" = $PatchTuesday.AddDays(26).AddHours(21); - # Week 5 will get evaulated next. - "Week 5 Catchup" = $null -} + $Today = (Get-Date 00:00:00) + $ThisDay = $Today.DayOfWeek + [datetime]$FirstDay = (Get-Date 00:00:00 -Day 1) + [datetime]$NextFirstDay = (Get-Date 00:00:00 -Day 1).AddMonths(1) + [datetime]$LastFirstDay = (Get-Date 00:00:00 -Day 1).AddMonths(-1) + + Switch ($CheckMonth) { + 'Previous' { $FirstDay = $FirstDay.AddMonths(-1) } + 'Next' { $FirstDay = $FirstDay.AddMonths(1) } + 'Current' { } # No change. + Default { } # No change. + } + + # Loop through the first 16 days of the specified month and select the second instance (index [1]) of a Tuesday. + [datetime]$PatchTuesday = (( 0..16 | ForEach-Object { $FirstDay.AddDays($_) } | Where-Object { $_.DayOfWeek -like 'Tuesday' } )[1]) + [datetime]$NextPatchTuesday = (( 0..16 | ForEach-Object { $NextFirstDay.AddDays($_) } | Where-Object { $_.DayOfWeek -like 'Tuesday' } )[1]) + [datetime]$LastPatchTuesday = (( 0..16 | ForEach-Object { $LastFirstDay.AddDays($_) } | Where-Object { $_.DayOfWeek -like 'Tuesday' } )[1]) + + ## Begin listing patch dates for the current and previous month. + # Begin listing the month's Saturday and Sunday patch groups and set their start times at 21:00. + $PatchWeeks = [ordered]@{ + 'Week 1 Sunday' = $PatchTuesday.AddDays(5).AddHours(21) + 'Week 2 Sunday' = $PatchTuesday.AddDays(12).AddHours(21) + 'Week 3 Saturday' = $PatchTuesday.AddDays(18).AddHours(21) + 'Week 3 Sunday' = $PatchTuesday.AddDays(19).AddHours(21) + 'Week 4 Saturday' = $PatchTuesday.AddDays(25).AddHours(21) + 'Week 4 Sunday' = $PatchTuesday.AddDays(26).AddHours(21) + # Week 5 will get evaulated next. + 'Week 5 Catchup' = $null + } # Check to see if a possible 5th patch week date comes before the next Patch Tuesday date. - if ( $PatchTuesday.AddDays(33) -lt $NextPatchTuesday ) { - $PatchWeeks.'Week 5 Catchup' = $PatchTuesday.AddDays(33) + if ( $PatchTuesday.AddDays(33) -lt $NextPatchTuesday ) { + $PatchWeeks.'Week 5 Catchup' = $PatchTuesday.AddDays(33) + } else { + $PatchWeeks.'Week 5 Catchup' = $null + } + # End listing of the month's Saturday and Sunday patch groups. + # Begin listing the previous month's Saturday and Sunday patch groups and set their start times at 21:00. + $PreviousPatchWeeks = [ordered]@{ + 'Week 1 Sunday' = $LastPatchTuesday.AddDays(5).AddHours(21) + 'Week 2 Sunday' = $LastPatchTuesday.AddDays(12).AddHours(21) + 'Week 3 Saturday' = $LastPatchTuesday.AddDays(18).AddHours(21) + 'Week 3 Sunday' = $LastPatchTuesday.AddDays(19).AddHours(21) + 'Week 4 Saturday' = $LastPatchTuesday.AddDays(25).AddHours(21) + 'Week 4 Sunday' = $LastPatchTuesday.AddDays(26).AddHours(21) + # Week 5 will get evaulated next. + 'Week 5 Catchup' = $null } - else { - $PatchWeeks.'Week 5 Catchup' = $null + # Check to see if a possible 5th patch week date comes before the next Patch Tuesday date. + if ( $LastPatchTuesday.AddDays(33) -lt $PatchTuesday ) { + $PreviousPatchWeeks.'Week 5 Catchup' = $LastPatchTuesday.AddDays(33) + } else { + $PreviousPatchWeeks.'Week 5 Catchup' = $null } -# End listing of the month's Saturday and Sunday patch groups. -# Begin listing the previous month's Saturday and Sunday patch groups and set their start times at 21:00. -$PreviousPatchWeeks = [ordered]@{ - "Week 1 Sunday" = $LastPatchTuesday.AddDays(5).AddHours(21); - "Week 2 Sunday" = $LastPatchTuesday.AddDays(12).AddHours(21); - "Week 3 Saturday" = $LastPatchTuesday.AddDays(18).AddHours(21); - "Week 3 Sunday" = $LastPatchTuesday.AddDays(19).AddHours(21); - "Week 4 Saturday" = $LastPatchTuesday.AddDays(25).AddHours(21); - "Week 4 Sunday" = $LastPatchTuesday.AddDays(26).AddHours(21); - # Week 5 will get evaulated next. - "Week 5 Catchup" = $null -} -# Check to see if a possible 5th patch week date comes before the next Patch Tuesday date. -if ( $LastPatchTuesday.AddDays(33) -lt $PatchTuesday ) { - $PreviousPatchWeeks.'Week 5 Catchup' = $LastPatchTuesday.AddDays(33) -} -else { - $PreviousPatchWeeks.'Week 5 Catchup' = $null -} -# End listing of the previous month's Saturday and Sunday patch groups. -## End listing of patch dates for the current and previous month. - -# List the upcoming patch weeks and find the next patch date. -$UpcomingPatchWeeks = foreach ($key in ($PatchWeeks.GetEnumerator() | Where-Object { $_.Value -gt (Get-Date 21:00:00).AddHours(7) } )) {$key } - $UpcomingPatchWeeks += @{"Next Week 1 Sunday" = $NextPatchTuesday.AddDays(5)} -$NextPatchWeek = $UpcomingPatchWeeks[0] - -# Find the previous patch week date. - # If the current date is less than the Week 1 patch date, then we need to find the last patch week date of the previous month. -# Find the current patch week date. - # Find the value between the previous patch week date and the next patch week date, but consider the last week to be current until 4 AM of the next Monday. - -### Provide output as requested by the script's Output parameter, if specified. -# Don't forget to RETURN what was asked for so other scripts can use it. -If ($Output) { - Switch ($Output) { - $Output { $PatchWeeks.$Output } #Provide whichever patch week is requested. - "Current" { Write-Output ($PatchWeeks.GetEnumerator() | Where-Object {$_.Value -eq $NextPatchDate}).Name } - "All" { $PatchWeeks } - Default { $PatchWeeks } #Show all patch dates. + # End listing of the previous month's Saturday and Sunday patch groups. + ## End listing of patch dates for the current and previous month. + + # List the upcoming patch weeks and find the next patch date. + $UpcomingPatchWeeks = foreach ($key in ($PatchWeeks.GetEnumerator() | Where-Object { $_.Value -gt (Get-Date 21:00:00).AddHours(7) } )) { $key } + $UpcomingPatchWeeks += @{'Next Week 1 Sunday' = $NextPatchTuesday.AddDays(5) } + $NextPatchWeek = $UpcomingPatchWeeks[0] + + # Find the previous patch week date. + # If the current date is less than the Week 1 patch date, then we need to find the last patch week date of the previous month. + # Find the current patch week date. + # Find the value between the previous patch week date and the next patch week date, but consider the last week to be current until 4 AM of the next Monday. + + ### Provide output as requested by the script's Output parameter, if specified. + # Don't forget to RETURN what was asked for so other scripts can use it. + If ($Output) { + Switch ($Output) { + $Output { $PatchWeeks.$Output } #Provide whichever patch week is requested. + 'Current' { Write-Output ($PatchWeeks.GetEnumerator() | Where-Object { $_.Value -eq $NextPatchDate }).Name } + 'All' { $PatchWeeks } + Default { $PatchWeeks } #Show all patch dates. + } + } Else { + Write-Output "`nThis month's patch Tuesday is on $PatchTuesday and the patch schedule is: " $PatchWeeks } -} -Else { - Write-Output "`nThis month's patch Tuesday is on $PatchTuesday and the patch schedule is: " $PatchWeeks -} -} # End of Script \ No newline at end of file +} # End of Script diff --git a/General/New-Function.ps1 b/General/New-Function.ps1 index c854b24..2d062d1 100644 --- a/General/New-Function.ps1 +++ b/General/New-Function.ps1 @@ -10,7 +10,7 @@ function New-Function { .PARAMETER Name The name of the new function to create. It is recommended to use ApprovedVerb-Noun for names. - + .PARAMETER Synopsis A synopsis of the new function. @@ -90,13 +90,13 @@ function New-Function { } if ($PSBoundParameters.ContainsKey('Name') -and -not $SkipValidation -and - $Name -match '\w-\w' -and $Name.Split('-')[0] -notin (Get-Verb).Verb ) { + $Name -match '\w-\w' -and $Name.Split('-')[0] -notin (Get-Verb).Verb ) { Write-Warning "It looks like you are not using an approved verb: `"$($Name.Split('-')[0]).`" Please run `"Get-Verb`" to see a list of approved verbs." } # Set the script path and filename. Use current directory if no path specified. if (Test-Path -Path $Path -PathType Container) { - $ScriptPath = [System.IO.Path]::Combine($Path,"$Name.ps1") + $ScriptPath = [System.IO.Path]::Combine($Path, "$Name.ps1") } else { $ScriptPath = ".\$Name.ps1" } @@ -136,11 +136,11 @@ function New-Function { begin { } # end begin block - + process { } # end process block - + end { } # end end block diff --git a/Microsoft 365/Intune/Uninstall-AppsViaIntune.ps1 b/Microsoft 365/Intune/Uninstall-AppsViaIntune.ps1 index cd2b80d..494052a 100644 --- a/Microsoft 365/Intune/Uninstall-AppsViaIntune.ps1 +++ b/Microsoft 365/Intune/Uninstall-AppsViaIntune.ps1 @@ -8,7 +8,7 @@ $MsiUninstall = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall | Get-ItemProperty | Where-Object { $_.DisplayName -like 'PDF Architect*' } | Select-Object -Property DisplayName, Uninstallstring #Empty Array to store uninstall string for Main module, OCR modules and Edit Module Msi files -$MsiUninstallStr = @() +$MsiUninstallStr = @() #Create msiexec PDF Architect Modules Uninstall Strigs $MsiUninstall.UninstallString | ForEach-Object { diff --git a/Windows/Disable-Poshv2.ps1 b/Windows/Disable-Poshv2.ps1 index a0f37d0..1357615 100644 --- a/Windows/Disable-Poshv2.ps1 +++ b/Windows/Disable-Poshv2.ps1 @@ -6,33 +6,33 @@ $Logfile = ".\Disable-Poshv2 Retries $TimeStamp.log" $LogfileFailures = ".\Disable-Poshv2 Failures $TimeStamp.log" $ServerFailures = @() -$exclusions = @("","","") +$exclusions = @('', '', '') #Filtering out production servers as well as any SQL, Exchange, SharePoint (SPS), Telephony, rinf* servers, and any servers in the "Non Windows" OU. $servers = (Get-ADComputer -Filter 'OperatingSystem -like "Windows Server*" -and Enabled -eq "True" -and Name -notlike "*sps*"' ` - -SearchBase "DC=DOMAIN,DC=COM" -SearchScope Subtree).Name | Where-Object {$_ -notin $Exclusions} | Sort-Object - Write-Information "$servers.Count servers found." + -SearchBase 'DC=DOMAIN,DC=COM' -SearchScope Subtree).Name | Where-Object { $_ -notin $Exclusions } | Sort-Object +Write-Information "$servers.Count servers found." foreach ($server in $servers) { if (Test-WSMan -ComputerName $server -ErrorAction Ignore) { try { #If PowerShell version is higher than 2 and PowerShell-V2 InstallState is not 'Removed', then remove it through a remote session. - Invoke-Command -ComputerName $server -ErrorAction Stop -ScriptBlock { - if ( (Get-WindowsFeature PowerShell-V2).InstallState -ne "Removed" -and ($PSVersionTable.PSVersion.Major) -gt 2 ) { - Remove-WindowsFeature PowerShell-V2 -Confirm:$false | Format-Table @{Name="Server";Expression={& hostname.exe}},Success,ExitCode,RestartNeeded,FeatureResult - } + Invoke-Command -ComputerName $server -ErrorAction Stop -ScriptBlock { + if ( (Get-WindowsFeature PowerShell-V2).InstallState -ne 'Removed' -and ($PSVersionTable.PSVersion.Major) -gt 2 ) { + Remove-WindowsFeature PowerShell-V2 -Confirm:$false | Format-Table @{Name = 'Server'; Expression = { & hostname.exe } }, Success, ExitCode, RestartNeeded, FeatureResult + } } } #try #The preceding if statement that tests WSMan connectivity will most likely preclude this catch statement from being encounterd. catch { Write-Warning "Trying to enable PS Remoting on $server." & psexec \\$server -s powershell.exe Enable-PSRemoting -Force > $null - + #If PowerShell version is higher than 2 and PowerShell-V2 InstallState is not 'Removed', then remove it through a remote session. - Invoke-Command -ComputerName $server -ErrorAction Continue -ScriptBlock { - if ( (Get-WindowsFeature PowerShell-V2).InstallState -ne "Removed" -and ($PSVersionTable.PSVersion.Major) -gt 2 ) { - Remove-WindowsFeature PowerShell-V2 -Confirm:$false | Format-Table @{Name="Server";Expression={& hostname.exe}},Success,ExitCode,RestartNeeded,FeatureResult - } + Invoke-Command -ComputerName $server -ErrorAction Continue -ScriptBlock { + if ( (Get-WindowsFeature PowerShell-V2).InstallState -ne 'Removed' -and ($PSVersionTable.PSVersion.Major) -gt 2 ) { + Remove-WindowsFeature PowerShell-V2 -Confirm:$false | Format-Table @{Name = 'Server'; Expression = { & hostname.exe } }, Success, ExitCode, RestartNeeded, FeatureResult + } } $error[0] | Out-File $Logfile -Append } #catch diff --git a/Windows/Get-SharesAndPermissions.ps1 b/Windows/Get-SharesAndPermissions.ps1 index d199911..2f910a0 100644 --- a/Windows/Get-SharesAndPermissions.ps1 +++ b/Windows/Get-SharesAndPermissions.ps1 @@ -1,6 +1,6 @@ Function Get-SharePermissions { - <# - .Synopsis + <# + .Synopsis This function retrieves share permissions from a remote computer. .Description This function returns user name and domain of users and permission @@ -9,7 +9,7 @@ .Example Get-SharePermissions -ComputerName WindowsServer -ShareName Test Returns permissions for \\WindowsServer\Test - .Example + .Example Get-SharePermissions -ComputerName WindowsServer -ShareName Test -Credential (Get-Credential) .Parameter ComputerName The remote system to connect to @@ -17,10 +17,10 @@ The name of the share to retrieve permissions for. .Parameter Credential The alternate credentials used to connect if the logged on user does not have access - .Notes - NAME: Example- - AUTHOR: Richard Perkins - LASTEDIT: 04/11/2016 15:20:52 + .Notes + NAME: Example- + AUTHOR: Richard Perkins + LASTEDIT: 04/11/2016 15:20:52 KEYWORDS: Share, Permissions #> @@ -31,22 +31,21 @@ [parameter(Mandatory = $True)] [string]$ShareName, - + [PSCredential]$Credential ) $ACL = $Null $ShareSec = Get-CimInstance -ClassName Win32_LogicalShareSecuritySetting -ComputerName $ComputerName -Credential $Credential | Where-Object { $_.Name -eq $ShareName } $SecurityDescriptor = $ShareSec.GetSecurityDescriptor().Descriptor - + Try { $SecurityDescriptor.DACL | ForEach-Object { $UserName = $_.Trustee.Name If ($Null -ne $_.Trustee.Domain) { $UserName = "$($_.Trustee.Domain)\$UserName" } If ($Null -eq $_.Trustee.Name) { $UserName = $_.Trustee.SIDString } - #[Array]$ACL += New-Object Security.AccessControl.FileSystemAccessRule($UserName, $_.AccessMask, $_.AceType) + #[Array]$ACL += New-Object Security.AccessControl.FileSystemAccessRule($UserName, $_.AccessMask, $_.AceType) } - } - Catch { Write-Host "Unable to obtain permissions for $share" } + } Catch { Write-Host "Unable to obtain permissions for $share" } Return $ACL } #End Function Get-SharePermissions @@ -72,7 +71,7 @@ $ShareList = @() $Shares | Where-Object { $_.Type -eq 0 } | ForEach-Object { $ShareDrive = ($_.Path | Split-Path -Qualifier) + '\' $AdminUncPath = Join-Path -Path (Join-Path -Path $('\\' + $ComputerName) -ChildPath $(($AdminShares | Where-Object { $_.Path -eq $(($ShareDrive | Split-Path -Qualifier) + '\') }).Name)) -ChildPath (Split-Path $_.Path -NoQualifier) - + $Share = $_ | Select-Object -Property @{Name = 'AdminUncPath'; Expression = { $AdminUncPath } }, @{Name = 'AllowMaximum'; Expression = { $_.AllowMaximum } }, @{Name = 'Caption'; Expression = { $_.Caption } }, @{Name = 'ComputerName'; Expression = { $_.PSComputerName } }, @{Name = 'Description'; Expression = { $_.Description } }, diff --git a/Windows/IISLogsCleanup.ps1 b/Windows/IISLogsCleanup.ps1 index 270fdf5..c2c2466 100644 --- a/Windows/IISLogsCleanup.ps1 +++ b/Windows/IISLogsCleanup.ps1 @@ -2,7 +2,7 @@ .SYNOPSIS IISLogsCleanup.ps1 - IIS Log File Cleanup Script -.DESCRIPTION +.DESCRIPTION A PowerShell script to compress and archive IIS log files. This script will check the folder that you specify, and any files older @@ -96,12 +96,12 @@ V1.02, 25/08/2015, Fixed typo in a variable [CmdletBinding()] param ( - [Parameter( Mandatory=$true)] - [string]$Logpath, + [Parameter( Mandatory = $true)] + [string]$Logpath, - [Parameter( Mandatory=$false)] + [Parameter( Mandatory = $false)] [string]$ArchivePath - ) +) #------------------------------------------------- @@ -121,14 +121,14 @@ $firstdayofpreviousmonth = (Get-Date -Year $currentyear -Month $currentmonth -Da #$myDir = Split-Path -Parent $MyInvocation.MyCommand.Path #$output = "$myDir\IISLogsCleanup.log" $output = "$LogPath\IISLogsCleanup-$(Get-Date -f yyyyMMddHHmm).log" -$logpathfoldername = $logpath.Split("\")[-1] +$logpathfoldername = $logpath.Split('\')[-1] #................................... # Logfile Strings #................................... -$logstring0 = "=====================================" -$logstring1 = " IIS Log File Cleanup Script" +$logstring0 = '=====================================' +$logstring1 = ' IIS Log File Cleanup Script' #------------------------------------------------- @@ -136,52 +136,43 @@ $logstring1 = " IIS Log File Cleanup Script" #------------------------------------------------- #This function is used to write the log file for the script -Function Write-Logfile() -{ - param( $logentry ) - $timestamp = Get-Date -DisplayHint Time - "$timestamp $logentry" | Out-File $output -Append +Function Write-Logfile() { + param( $logentry ) + $timestamp = Get-Date -DisplayHint Time + "$timestamp $logentry" | Out-File $output -Append } # This function is to test the completion of the async CopyHere method # Function provided by Alain Arnould -function IsFileLocked( [string]$path) -{ +function IsFileLocked( [string]$path) { If ([string]::IsNullOrEmpty($path) -eq $true) { - Throw "The path must be specified." + Throw 'The path must be specified.' } [bool] $fileExists = Test-Path $path If ($fileExists -eq $false) { - Throw "File does not exist (" + $path + ")" + Throw 'File does not exist (' + $path + ')' } [bool] $isFileLocked = $true $file = $null - Try - { + Try { $file = [IO.File]::Open($path, - [IO.FileMode]::Open, - [IO.FileAccess]::Read, - [IO.FileShare]::None) + [IO.FileMode]::Open, + [IO.FileAccess]::Read, + [IO.FileShare]::None) $isFileLocked = $false - } - Catch [IO.IOException] - { - If ($_.Exception.Message.EndsWith("it is being used by another process.") -eq $false) - { + } Catch [IO.IOException] { + If ($_.Exception.Message.EndsWith('it is being used by another process.') -eq $false) { # Throw $_.Exception [bool] $isFileLocked = $true } - } - Finally - { - If ($null -ne $file) - { + } Finally { + If ($null -ne $file) { $file.Close() } } @@ -205,8 +196,7 @@ Write-Logfile $logstring0w #Check whether IIS Logs path exists, exit if it does not -if ((Test-Path $Logpath) -ne $true) -{ +if ((Test-Path $Logpath) -ne $true) { $tmpstring = "Log path $logpath not found" Write-Warning $tmpstring Write-Logfile $tmpstring @@ -227,14 +217,11 @@ Write-Host $tmpstring Write-Logfile $tmpstring #Fetch list of log files older than 1st day of previous month -$logstoremove = Get-ChildItem -Path "$($Logpath)\*.*" -Include *.log | Where-Object {$_.CreationTime -lt $firstdayofpreviousmonth -and $_.PSIsContainer -eq $false} +$logstoremove = Get-ChildItem -Path "$($Logpath)\*.*" -Include *.log | Where-Object { $_.CreationTime -lt $firstdayofpreviousmonth -and $_.PSIsContainer -eq $false } -if ($null -eq $($logstoremove.Count)) -{ +if ($null -eq $($logstoremove.Count)) { $logcount = 0 -} -else -{ +} else { $logcount = $($logstoremove.Count) } @@ -246,10 +233,9 @@ Write-Logfile $tmpstring $hashtable = @{} #Add each logfile to hashtable -foreach ($logfile in $logstoremove) -{ - $zipdate = $logfile.LastWriteTime.ToString("yyyy-MM") - $hashtable.Add($($logfile.FullName),"$zipdate") +foreach ($logfile in $logstoremove) { + $zipdate = $logfile.LastWriteTime.ToString('yyyy-MM') + $hashtable.Add($($logfile.FullName), "$zipdate") } #Calculate unique yyyy-MM dates from logfiles in hashtable @@ -257,89 +243,75 @@ $hashtable = $hashtable.GetEnumerator() | Sort-Object Value $dates = @($hashtable | Group-Object -Property:Value | Select-Object Name) #For each yyyy-MM date add those logfiles to a zip file -foreach ($date in $dates) -{ +foreach ($date in $dates) { $zipfilename = "$Logpath\$computername-$logpathfoldername-$($date.Name).zip" - if(-not (test-path($zipfilename))) - { - set-content $zipfilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18)) - (Get-ChildItem $zipfilename).IsReadOnly = $false + if (-not (Test-Path($zipfilename))) { + Set-Content $zipfilename ('PK' + [char]5 + [char]6 + ("$([char]0)" * 18)) + (Get-ChildItem $zipfilename).IsReadOnly = $false } - $shellApplication = new-object -com shell.application + $shellApplication = New-Object -com shell.application $zipPackage = $shellApplication.NameSpace($zipfilename) - $zipfiles = $hashtable | Where-Object {$_.Value -eq "$($date.Name)"} + $zipfiles = $hashtable | Where-Object { $_.Value -eq "$($date.Name)" } $tmpstring = "Zip file name is $zipfilename and will contain $($zipfiles.Count) files" Write-Host $tmpstring Write-Logfile $tmpstring - foreach($file in $zipfiles) - { + foreach ($file in $zipfiles) { $fn = $file.key.ToString() - + $tmpstring = "Adding $fn to $zipfilename" Write-Host $tmpstring Write-Logfile $tmpstring - $zipPackage.CopyHere($fn,16) + $zipPackage.CopyHere($fn, 16) #This sleep interval helps avoids file lock/conflict issues. May need to increase if larger #log files are taking longer to add to the zip file. - do - { - Start-sleep -s $sleepinterval + do { + Start-Sleep -s $sleepinterval } while (IsFileLocked($zipfilename)) } #Compare count of log files on disk to count of log files in zip file $zippedcount = ($zipPackage.Items()).Count - + $tmpstring = "Zipped count: $zippedcount" Write-Host $tmpstring Write-Logfile $tmpstring - + $tmpstring = "Files: $($zipfiles.Count)" Write-Host $tmpstring Write-Logfile $tmpstring #If counts match it is safe to delete the log files from disk - if ($zippedcount -eq $($zipfiles.Count)) - { - $tmpstring = "Zipped file count matches log file count, safe to delete log files" + if ($zippedcount -eq $($zipfiles.Count)) { + $tmpstring = 'Zipped file count matches log file count, safe to delete log files' Write-Host $tmpstring Write-Logfile $tmpstring - foreach($file in $zipfiles) - { + foreach ($file in $zipfiles) { $fn = $file.key.ToString() Remove-Item $fn } #If archive path was specified move zip file to archive path - if ($ArchivePath) - { + if ($ArchivePath) { #Check whether archive path is accessible - if ((Test-Path $ArchivePath) -ne $true) - { + if ((Test-Path $ArchivePath) -ne $true) { $tmpstring = "Log path $archivepath not found or inaccessible" Write-Warning $tmpstring Write-Logfile $tmpstring - } - else - { + } else { #Check if subfolder of archive path exists - if ((Test-Path $ArchivePath\$computername) -ne $true) - { - try - { + if ((Test-Path $ArchivePath\$computername) -ne $true) { + try { #Create subfolder based on server name New-Item -Path $ArchivePath\$computername -ItemType Directory -ErrorAction STOP - } - catch - { + } catch { #Subfolder creation failed $tmpstring = "Unable to create $computername subfolder in $archivepath" Write-Host $tmpstring @@ -351,15 +323,11 @@ foreach ($date in $dates) } } - if ((Test-Path $ArchivePath\$computername\$logpathfoldername) -ne $true) - { - try - { + if ((Test-Path $ArchivePath\$computername\$logpathfoldername) -ne $true) { + try { #create subfolder based on log path folder name New-Item -Path $ArchivePath\$computername\$logpathfoldername -ItemType Directory -ErrorAction STOP - } - catch - { + } catch { #Subfolder creation failed $tmpstring = "Unable to create $logpathfoldername subfolder in $archivepath\$computername" Write-Host $tmpstring @@ -372,30 +340,25 @@ foreach ($date in $dates) } #Now move the zip file to the archive path - try - { + try { #Move the zip file Move-Item $zipfilename -Destination $ArchivePath\$computername\$logpathfoldername -ErrorAction STOP $tmpstring = "$zipfilename was moved to $archivepath\$computername\$logpathfoldername" Write-Host $tmpstring Write-Logfile $tmpstring - } - catch - { + } catch { #Move failed, log the error $tmpstring = "Unable to move $zipfilename to $ArchivePath\$computername\$logpathfoldername" Write-Host $tmpstring Write-Logfile $tmpstring Write-Warning $_.Exception.Message Write-Logfile $_.Exception.Message - } + } } } - } - else - { - $tmpstring = "Zipped file count does not match log file count, not safe to delete log files" + } else { + $tmpstring = 'Zipped file count does not match log file count, not safe to delete log files' Write-Host $tmpstring Write-Logfile $tmpstring } @@ -404,7 +367,7 @@ foreach ($date in $dates) #Finished -$tmpstring = "Finished" +$tmpstring = 'Finished' Write-Host $tmpstring Write-Logfile $tmpstring diff --git a/Windows/Test-PendingReboot.ps1 b/Windows/Test-PendingReboot.ps1 index 5c21838..d624d10 100644 --- a/Windows/Test-PendingReboot.ps1 +++ b/Windows/Test-PendingReboot.ps1 @@ -8,23 +8,23 @@ .COMPANYNAME Adam the Automator, LLC -.COPYRIGHT +.COPYRIGHT .DESCRIPTION This script tests various registry values to see if the local computer is pending a reboot. -.TAGS +.TAGS -.LICENSEURI +.LICENSEURI -.PROJECTURI +.PROJECTURI -.ICONURI +.ICONURI -.EXTERNALMODULEDEPENDENCIES +.EXTERNALMODULEDEPENDENCIES -.REQUIREDSCRIPTS +.REQUIREDSCRIPTS -.EXTERNALSCRIPTDEPENDENCIES +.EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES @@ -34,7 +34,7 @@ Inspiration from: https://gallery.technet.microsoft.com/scriptcenter/Get-PendingReboot-Query-bdb79542 .EXAMPLE PS> Test-PendingReboot -ComputerName localhost - + This example checks various registry values to see if the local computer is pending a reboot. #> [CmdletBinding()] @@ -42,7 +42,7 @@ param( # ComputerName is optional. If not specified, localhost is used. [ValidateNotNullOrEmpty()] [string[]]$ComputerName, - + [Parameter()] [ValidateNotNullOrEmpty()] [pscredential]$Credential @@ -65,7 +65,7 @@ $scriptBlock = { [ValidateNotNullOrEmpty()] [string]$Key ) - + $ErrorActionPreference = 'Stop' if (Get-Item -Path $Key -ErrorAction Ignore) { @@ -86,7 +86,7 @@ $scriptBlock = { [ValidateNotNullOrEmpty()] [string]$Value ) - + $ErrorActionPreference = 'Stop' if (Get-ItemProperty -Path $Key -Name $Value -ErrorAction Ignore) { @@ -107,7 +107,7 @@ $scriptBlock = { [ValidateNotNullOrEmpty()] [string]$Value ) - + $ErrorActionPreference = 'Stop' if (($regVal = Get-ItemProperty -Path $Key -Name $Value -ErrorAction Ignore) -and $regVal.($Value)) { @@ -125,13 +125,13 @@ $scriptBlock = { { Test-RegistryKey -Key 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\PostRebootReporting' } { Test-RegistryValueNotNull -Key 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Value 'PendingFileRenameOperations' } { Test-RegistryValueNotNull -Key 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Value 'PendingFileRenameOperations2' } - { + { # Added test to check first if key exists, using "ErrorAction ignore" will incorrectly return $true - 'HKLM:\SOFTWARE\Microsoft\Updates' | Where-Object { test-path $_ -PathType Container } | ForEach-Object { - if(Test-Path "$_\UpdateExeVolatile" ){ - (Get-ItemProperty -Path $_ -Name 'UpdateExeVolatile' | Select-Object -ExpandProperty UpdateExeVolatile) -ne 0 - }else{ - $false + 'HKLM:\SOFTWARE\Microsoft\Updates' | Where-Object { Test-Path $_ -PathType Container } | ForEach-Object { + if (Test-Path "$_\UpdateExeVolatile" ) { + (Get-ItemProperty -Path $_ -Name 'UpdateExeVolatile' | Select-Object -ExpandProperty UpdateExeVolatile) -ne 0 + } else { + $false } } } @@ -142,12 +142,12 @@ $scriptBlock = { { # Added test to check first if keys exists, if not each group will return $Null # May need to evaluate what it means if one or both of these keys do not exist - ( 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName' | Where-Object { test-path $_ } | % { (Get-ItemProperty -Path $_ ).ComputerName } ) -ne - ( 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName' | Where-Object { Test-Path $_ } | % { (Get-ItemProperty -Path $_ ).ComputerName } ) + ( 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName' | Where-Object { Test-Path $_ } | ForEach-Object { (Get-ItemProperty -Path $_ ).ComputerName } ) -ne + ( 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName' | Where-Object { Test-Path $_ } | ForEach-Object { (Get-ItemProperty -Path $_ ).ComputerName } ) } { # Added test to check first if key exists - 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending' | Where-Object { + 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending' | Where-Object { (Test-Path $_) -and (Get-ChildItem -Path $_) } | ForEach-Object { $true } } ) @@ -161,10 +161,10 @@ $scriptBlock = { } } -# if ComputerName was not specified, then use localhost +# if ComputerName was not specified, then use localhost # to ensure that we don't create a Session. if ($null -eq $ComputerName) { - $ComputerName = "localhost" + $ComputerName = 'localhost' } foreach ($computer in $ComputerName) { @@ -181,14 +181,13 @@ foreach ($computer in $ComputerName) { IsPendingReboot = $false } - if ($computer -in ".", "localhost", $env:COMPUTERNAME ) { + if ($computer -in '.', 'localhost', $env:COMPUTERNAME ) { if (-not ($output.IsPendingReboot = Invoke-Command -ScriptBlock $scriptBlock)) { $output.IsPendingReboot = $false } - } - else { + } else { $psRemotingSession = New-PSSession @connParams - + if (-not ($output.IsPendingReboot = Invoke-Command -Session $psRemotingSession -ScriptBlock $scriptBlock)) { $output.IsPendingReboot = $false }