Bit of an esoteric one this, but might be helpful to someone anyway. If you’ve worked with Out-Gridview before you may have also made use of the NoteProperty property to collate your results prior to output; today I ran into an issue where I knew some of my output objects had 2 NoteProperties and some had 2+x NoteProperties where x was more than 0. When output to Out-Gridview or Export-CSV it was only showing the number of properties that the first object had, irrespective of how many subsequent objects had.

For example:

User Property1 Property2 Property3
Dave 43
Steve 25 62 23
John 23 263

In this scenario, all results would be output with just the User & Property1 NoteProperty displayed because Dave’s Property2 and Property3 don’t exist. You could work around this by sorting on “Property 3” so that it’s the first object in the output and thus includes all columns like so:

User Property1 Property2 Property3
Steve 25 62 23
Dave 43
John 23 263

However, this obviously only works if you know how many properties there are and I didn’t. So, I had to find a further workaround and came up with this:

$count = @()
foreach($output in $useroutput){$count += $(($output | gm).count)-5}
$max = $($count | measure -maximum).maximum
$useroutput | Sort-Object "Property$max" | Out-GridView

This iterates through your array of objects ($useroutput), counts the properties on each (minus the 4 base methods on a System.Object and the known “User” property) and then finds the highest count and sorts the output based on that highest value, ensuring that it always outputs every column even when it’s empty for most of the results.

Obviously, even this method is only useful if your properties are titled numerically, if you’re just pulling an unknown list of properties with arbitrary names, you’re probably going to have to dig a bit deeper.



You know how it is, you don’t pay attention to the management of your domain for just 5 or 6 years and suddenly you have hundreds of GPOs with no idea what half of them do or even if they’re actually linked somewhere. For some reason, the Powershell GPO module doesn’t have a simple cmdlet or property that lets you tell if a GPO is linked or not, because that would be far too helpful, but it’s not too hard to do if you don’t mind parsing some XML.

This code is based on a much more complicated script from here, designed to let you search for individual settings within a GPO. It will accept a number of arguments, but run without any it will simply output to the console a list of all of the unlinked GPOs in the current domain.

<#
Copyright (c) 2014, Adam Beardwood
All rights reserved.
 
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: 
 
1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer. 
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution. 
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
 
#Find Unlinked Linked GPOs in a domain
#Adam Beardwood 20/05/2014
#v1.0 - Initial Release
 
param (
[Parameter(Mandatory=$false)]
[boolean] $outfile=$false,
[Parameter(Mandatory=$false)]
[string] $filename="UnlinkedGPO-$(get-date -f HHmmss).txt",
[Parameter(Mandatory=$false)]  
[string] $DomainName = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
)
 
Import-Module GroupPolicy;
 
[string] $Extension="Enabled"
 
$allGposInDomain = Get-GPO -All -Domain $DomainName | Sort DisplayName;
 
$xmlnsGpSettings = "http://www.microsoft.com/GroupPolicy/Settings";
$xmlnsSchemaInstance = "http://www.w3.org/2001/XMLSchema-instance";
$xmlnsSchema = "http://www.w3.org/2001/XMLSchema";
 
$QueryString = "gp:LinksTo";
 
$host.UI.WriteLine();
 
foreach ($Gpo in $allGposInDomain)
{				
	$xmlDoc = [xml] (Get-GPOReport -Guid $Gpo.Id -ReportType xml -Domain $Gpo.DomainName);		
	$xmlNameSpaceMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable);
 
	$xmlNameSpaceMgr.AddNamespace("", $xmlnsGpSettings);
	$xmlNameSpaceMgr.AddNamespace("gp", $xmlnsGpSettings);
	$xmlNameSpaceMgr.AddNamespace("xsi", $xmlnsSchemaInstance);
	$xmlNameSpaceMgr.AddNamespace("xsd", $xmlnsSchema);
 
	$extensionNodes = $xmlDoc.DocumentElement.SelectNodes($QueryString, $XmlNameSpaceMgr);
 
	$stringToPrint = $($Gpo.DisplayName) + " is not linked in this domain";
 
	if($extensionNodes[0] -eq $null){
		if($outfile -eq $true){
			$stringToPrint | Out-File $filename -Append
		}else{
			write-host $stringToPrint -foregroundcolor red
		}
	}
}


Firstly, download the RSAT tools for Windows 7 or Windows 8 of the appropriate bittiness.

Then install them using: wusa <RSAT Installer Filename>.msu /quiet

You can then enable the Powershell AD cmdlets (or indeed any other RSAT components) from the command line using dism.exe thus (careful, the featurenames are case-sensitive):

dism /online /enable-feature /featurename:RemoteServerAdministrationTools /featurename:RemoteServerAdministrationTools-Roles /featurename:RemoteServerAdministrationTools-Roles-AD /featurename:RemoteServerAdministrationTools-Roles-AD-Powershell

For some reason it won’t install “dependent” parent components unless you explicitly tell it to install them, which is a pain but workaroundable.



Update: with appropriate irony I managed to bollocks up the formatting myself. Sorry, should be fixed now.

Courtesy of: http://www.orcsweb.com/blog/james/powershell-ing-on-windows-server-how-to-import-certificates-using-powershell/

However, the formatting is a bit borked so I’ve reproduced it here.

$certrootstore can be either LocalMachine or CurrentUser
$certstore can be any of: AddressBook, AuthRoot, CA, Disallowed, My, Root, TrustedPeople, TrustedPublisher

The script assumes the certs are in the same location as the script, if not you’ll need to modify the paths as well as the filenames.

function Import-Certificate{
     param([String]$certPath,[String]$certRootStore,[String]$certStore)
     $pfx=new-object System.Security.Cryptography.X509Certificates.X509Certificate2
     $pfx.import($certPath)
     $store= new-object System.Security.Cryptography.X509Certificates.X509Store($certStore,$certRootStore)
     $store.open("MaxAllowed")
     $store.add($pfx)
     $store.close()
}
 
Import-Certificate "$(Split-Path $MyInvocation.MyCommand.Path)\TrustedRoot.cer" "LocalMachine" "root"
 
Import-Certificate "$(Split-Path $MyInvocation.MyCommand.Path)\TrustedIssuingAuthorty.cer" "LocalMachine" "CA"


This script will leverage the Veeam Powershell modules in conjunction with Nagios’ check_nrpe plugin (or similar passive checking plugin) to check the status of Veeam backup jobs. You will probably need to have run the Install-VeeamToolkit.ps1 script in “C:\Program Files\Veeam\Backup and Replication” on any server you’re running these checks on otherwise it will be unable to load the Veeam snapin, unless you have Powershell 3 installed and have your fingers crossed.

You will also need to ensure that your agent allows NRPE arguments and don’t strip special characters (it needs to keep the “” quotes around the job name).

If your Veeam SQL database is off-box then your agent service will need to be running under an account that has access to the DB on the remote SQL server otherwise the lookups will fail.

Check command syntax for NSClient++ is something like the below, other agents may differ:
check_veeam = cmd /c echo scripts\check_veeam.ps1 "$ARG1$"; exit($lastexitcode) | powershell.exe -noninteractive -noprofile -command -

Check command syntax for the Nagios commands.cfg is something like the below:
$USER1$/check_nrpe -H $HOSTADDRESS$ -c check_veeam -a $ARG1$

<#
Copyright (c) 2013, Adam Beardwood
All rights reserved.
 
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: 
 
1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer. 
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution. 
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
 
#Check Veeam Backups
#Adam Beardwood 10/07/2013
#Based on a script by by Tytus Kurek
#v1.0 - Initial Release
 
$error.clear()
 
$snaps = Get-PSSnapin
foreach($snap in $snaps){if($snap.name -eq "VeeamPSSnapin"){$exflag = 1}}
if($exflag -ne 1){
	Add-PSSnapin -name VeeamPSSnapin -erroraction silentlycontinue
	if($error[0] -ne $null){write-host "CRITICAL - Could not load Veeam snapin";exit 2}
}
 
$name = $args[0]
 
if($args[0] -eq $null){write-host "CRITICAL - You must provide a job name.";exit 2}
 
$job = Get-VBRJob -Name $name
$name = $job.name
 
if ($job -eq $null){
	Write-Host "UNKNOWN - Could Not Find Job: $name."
	exit 3
}
 
$status = $job.GetLastResult()
$time = $($job.findlastsession()).EndTime
if($($job.findlastsession()).State -eq "Working"){
	Write-Host "OK - Job: $name is currently in progress."
	exit 0
}elseif($status -eq "Failed"){
	Write-Host "CRITICAL - Job: $name failed at $time."
	exit 2
}elseif ($status -ne "Success"){
	Write-Host "WARNING - Job: $name completed with warnings at $time."
	exit 1
}else{
	Write-Host "OK - Job: $name completed successfully at $time."
	exit 0
}
#End

Hope you find it helpful.



Along with their anti-spam Block Lists, Spamhaus also provide a DROP list, which is a list of stolen ‘hijacked’ netblocks and netblocks controlled entirely by criminals and professional spammers. As nothing good can come out of these ranges, it’s handy to block them outright at the firewall level, if that firewall happens to be TMG or ISA you can make use of the following script to keep the DROP list up-to-date.

The IP address conversion functions in this script are all courtesy of Chris Dent of www.indented.co.uk and were a godsend. To make use of this script you will need to create your Network object in ISA/TMG and update the network name in the following code to match $ipranges = $server.NetworkConfiguration.Networks.item("Spamhaus DROP List").IpRangeSet. You will also need to set $dropfile to match the location where you will be storing the downloaded DROP List, this needs to be retained so that updates can easily do a diff on the file without having to query ISA/TMG, which is much slower. Create a blank text file matching this filename & path before running the script for the first time.

<#
Copyright (c) 2013, Adam Beardwood
All rights reserved.
 
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: 
 
1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer. 
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution. 
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
 
#Grab Spamhaus DROP List and update TMG network sets based on changes
#IP Conversion Functions Courtesy of Chris Dent - http://www.indented.co.uk/index.php/2010/01/23/powershell-subnet-math/
#Adam Beardwood 26/03/2013
#v1.0 - Initial Release
#v1.1 - Considerable improvements to error checking
 
Function ConvertTo-DottedDecimalIP {
  <#
    .Synopsis
      Returns a dotted decimal IP address from either an unsigned 32-bit integer or a dotted binary string.
    .Description
      ConvertTo-DottedDecimalIP uses a regular expression match on the input string to convert to an IP address.
    .Parameter IPAddress
      A string representation of an IP address from either UInt32 or dotted binary.
  #>
 
  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [String]$IPAddress
  )
 
  Process {
    Switch -RegEx ($IPAddress) {
      "([01]{8}\.){3}[01]{8}" {
        Return [String]::Join('.', $( $IPAddress.Split('.') | ForEach-Object { [Convert]::ToUInt32($_, 2) } ))
      }
      "\d" {
        $IPAddress = [UInt32]$IPAddress
        $DottedIP = $( For ($i = 3; $i -gt -1; $i--) {
          $Remainder = $IPAddress % [Math]::Pow(256, $i)
          ($IPAddress - $Remainder) / [Math]::Pow(256, $i)
          $IPAddress = $Remainder
         } )
 
        Return [String]::Join('.', $DottedIP)
      }
      default {
        Write-Error "Cannot convert this format"
      }
    }
  }
}
 
Function ConvertTo-DecimalIP {
  <#
    .Synopsis
      Converts a Decimal IP address into a 32-bit unsigned integer.
    .Description
      ConvertTo-DecimalIP takes a decimal IP, uses a shift-like operation on each octet and returns a single UInt32 value.
    .Parameter IPAddress
      An IP Address to convert.
  #>
 
  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [Net.IPAddress]$IPAddress
  )
 
  Process {
    $i = 3; $DecimalIP = 0;
    $IPAddress.GetAddressBytes() | ForEach-Object { $DecimalIP += $_ * [Math]::Pow(256, $i); $i-- }
 
    Return [UInt32]$DecimalIP
  }
}
 
Function Get-BroadcastAddress {
  <#
    .Synopsis
      Takes an IP address and subnet mask then calculates the broadcast address for the range.
    .Description
      Get-BroadcastAddress returns the broadcast address for a subnet by performing a bitwise AND 
      operation against the decimal forms of the IP address and inverted subnet mask. 
      Get-BroadcastAddress expects both the IP address and subnet mask in dotted decimal format.
    .Parameter IPAddress
      Any IP address within the network range.
    .Parameter SubnetMask
      The subnet mask for the network.
  #>
 
  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [Net.IPAddress]$IPAddress, 
 
    [Parameter(Mandatory = $True, Position = 1)]
    [Alias("Mask")]
    [Net.IPAddress]$SubnetMask
  )
 
  Process {
    Return ConvertTo-DottedDecimalIP $((ConvertTo-DecimalIP $IPAddress) -BOr `
      ((-BNot (ConvertTo-DecimalIP $SubnetMask)) -BAnd [UInt32]::MaxValue))
  }
}
 
#Create ISA COM Objects
$root = new-object -comObject "FPC.Root" -strict
#Get our array (well, there is only one in this case)
$server = $root.Arrays | Select-Object -first 1
 
#Get IPRangeSet for the Spamhaus DROP List network
$ipranges = $server.NetworkConfiguration.Networks.item("Spamhaus DROP List").IpRangeSet
$dropfile = "c:\users\adam\desktop\drop.txt"
 
#If local copy of DROP file is missing, remove all addresses and start clean
if($(test-path $dropfile) -eq $false){
 
	$ipranges.removeall()
 
	#Download DROP List from webserver
	$source = "http://www.spamhaus.org/drop/drop.txt"
	$destination = $dropfile
	$wc = New-Object System.Net.WebClient
	$wc.DownloadFile($source, $destination)
 
	$drop = ""
	$difference = Compare-Object $drop $(gc $dropfile)
 
}else{
 
	$drop = gc $dropfile
	if($drop -eq $null){$drop = ";"}
 
	#Download DROP List from webserver
	$source = "http://www.spamhaus.org/drop/drop.txt"
	$destination = $dropfile
	$wc = New-Object System.Net.WebClient
	$wc.DownloadFile($source, $destination)
 
	$difference = Compare-Object $drop $(gc $dropfile)
}
 
if($difference -ne $null){
 
	$difference
 
	foreach($diff in $difference){
		if ($diff.inputobject.startswith(";")){
		}else{
			$ip = $($diff.inputobject.split(" ; ")[0]).split("/")[0]
			$cidr = $($diff.inputobject.split(" ; ")[0]).split("/")[1]
			$mask = ConvertTo-DottedDecimalIP ([Convert]::ToUInt32($(("1" * $cidr).PadRight(32, "0")), 2))
			$bcast = Get-BroadcastAddress $ip $mask
 
			Switch ($diff.SideIndicator){
				"=>" {if(!$ipranges.isipinset($ip)){$ipranges.add($ip,$bcast)}else{write-host "IP Address Range $ip - $bcast already in DROP List" -fore red}} #Not in local
				"<=" {if($ipranges.isipinset($ip)){$ipranges.remove($ip,$bcast)}else{write-host "IP Address Range $ip - $bcast not present in Network Object" -fore yellow}} #Not in remote
			}
		}
	}
 
}else{
	write-host "No Changes Since Last Update" -fore green
	}
 
$ipranges.save()
 
write-host "Update Complete" -fore green


It’s very easy to use something like Win32_NetworkAdapterConfiguration to get information from a remote machine as to whether or not it’s using DHCP on an interface, but it’s much harder to establish whether or not a client is using statically assigned DNS servers, but a DHCP assigned IP address.

This is the best way I’ve found to do it so far, but please let me know if you’re aware of a better one. Note that this function will return True if any interface that’s using DHCP has statically assigned DNS servers; if you want to get more specific you’ll have to start doing some interface detection and that’s a pain given the way they’re stored in the registry.

function checkstatic ($computer) {
	$rootkey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",$computer)
	$key = $rootkey.OpenSubKey("System\CurrentControlSet\Services\TCPIP\Parameters\Interfaces")
	$subkeys = $key.getsubkeynames()
	foreach($subkey in $subkeys){
		$sub = $key.opensubkey($subkey, $true)
		$dhcp = $sub.getvalue("EnableDHCP")
		if($dhcp -eq 1){
			$nameserver = $sub.getvalue("Nameserver")
			if($nameserver -ne ""){
				return $true
			}
		}
	}
}


The following script is an amalgamation of several tools I’ve built up over the last couple of years to work with multiple DHCP scopes on servers. It has 6 basic modes of operation, as detailed below, which allow you to list all scopes on a server, enable all scopes on a server, disable all scopes on a server, change the lease time for all scopes on a server, change the offer delay for all scopes on a server or add reservations to one or more servers. You can use help .\DHCP-Tool.ps1 to get more information on the various switches, though it’s worth pointing out that leasetimes are in seconds while delaytimes are in milliseconds.

Hopefully the new Server 2012 powershell modules for DHCP solve a lot of these problems, but I haven’t had a chance to dig into them yet.

.\DHCP-Tool.ps1 -Server <String> [-show]
.\DHCP-Tool.ps1 -Server <String> [-enable]
.\DHCP-Tool.ps1 -Server <String> [-disable]
.\DHCP-Tool.ps1 -Server <String> [-lease] -leasetime <String>
.\DHCP-Tool.ps1 -Server <String> [-delay] -delaytime <Int32>
.\DHCP-Tool.ps1 -Server <String> [-reserve] -ipscope <String> -ipaddr <String> -mac <String> [-altservers <String>] [-name <String>]

################################################################################################
#This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;    #
#without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     #
#THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT      #
#WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS PROVIDE THE SOFTWARE "AS IS" WITHOUT   #
#WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE         #
#IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK   #
#AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE      #
#DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. IN NO EVENT  #
#UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, BE       #
#LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES#
#ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS    #
#OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A    #
#FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER HAS BEEN     #
#ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.                                                   #
################################################################################################
 
<#
.SYNOPSIS
    DHCP Multitool
.DESCRIPTION
    Provides a variety of functions for making changes to multiple DHCP scopes and/or multiple DHCP servers
.PARAMETER Server
    The hostname or IP address of the DHCP server to work with
.PARAMETER Show
    Action: List current scope configuration
.PARAMETER Enable
	Action: Enable all scopes on server
.PARAMETER Disable
	Action: Disable all scopes on server
.PARAMETER Lease
	Action: Alters lease time on all scopes on server
.PARAMETER LeaseTime
	DHCP Lease time in seconds
.PARAMETER Delay
	Action: Alters offer delay on all scopes on server
.PARAMETER DelayTime
	Offer Delay time in miliseconds
.PARAMETER Reserve
	Action: Add reservation to server
.PARAMETER AltServer
	Additional servers to create reservation on
.PARAMETER IPScope
	Name of scope to work with
.PARAMETER IPAddr
	IP Address to reserve
.PARAMETER MAC
	MAC Address to associated reservation with
.PARAMETER Name
	Comment to add to reservation
.EXAMPLE
    C:\>dhcp-tool.ps1 -server 127.0.0.1 -enable
	Enables all scopes on server 127.0.0.1
.EXAMPLE
    C:\>dhcp-tool.ps1 -server 127.0.0.1 -delay -delaytime 1000
	Sets an offer delay of 1000ms on all scopes on server 127.0.0.1
.NOTES
    Author: Adam Beardwood
    Date: October 12, 2012
	v1.0: Initial Release
	v1.1: Added Better Error Handling and Command Feedback
#>
 
[CmdletBinding(DefaultParametersetName="showDHCP")]
Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)][String]$Server,
 
		[Parameter(ParameterSetName='showDHCP')][switch]$show,
 
		[Parameter(ParameterSetName='enableDHCP')][switch]$enable,
 
		[Parameter(ParameterSetName='disableDHCP')][switch]$disable,
 
		[Parameter(ParameterSetName='leaseDHCP')][switch]$lease,
		[Parameter(ParameterSetName='leaseDHCP',Mandatory=$true,ValueFromPipeline=$true)][string]$leasetime,
 
		[Parameter(ParameterSetName='delayDHCP')][switch]$delay,
		[Parameter(ParameterSetName='delayDHCP',Mandatory=$true,ValueFromPipeline=$true)][ValidateRange(0,1000)][int]$delaytime,
 
		[Parameter(ParameterSetName='reserveDHCP')][switch]$reserve,
		[Parameter(ParameterSetName='reserveDHCP',Mandatory=$true)][string]$ipscope,
		[Parameter(ParameterSetName='reserveDHCP',Mandatory=$true)][string]$ipaddr,
		[Parameter(ParameterSetName='reserveDHCP',Mandatory=$true)][string]$mac,
		[Parameter(ParameterSetName='reserveDHCP',Mandatory=$false)][string]$altservers = $null,
		[Parameter(ParameterSetName='reserveDHCP',Mandatory=$false)][string]$name
)
 
function getDHCPScope($servername, $type) {
 
	$ips = @()
 
	$scopes = netsh dhcp server $servername show scope
 
	if($type -eq "show"){
		foreach($scope in $scopes){
			write-host $scope -foregroundcolor green
		}
	}else{
 
		$regex = [regex]"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"
 
		foreach($scope in $scopes){
 
			$addr = $scope | foreach {$regex.Matches($_) | foreach {$_.Captures[0].Value}}
			if($addr -ne $null){
				$ips += $addr[0]
			}
		}
 
		return $ips
	}
}
 
function enableDHCPScope($servername, $ip){
	netsh dhcp server $servername scope $ip set state 1 | out-null
}
 
function disableDHCPScope($servername, $ip){
	netsh dhcp server $servername scope $ip set state 0 | out-null
}
 
function leaseDHCPScope($servername, $ip, $leasetime){
	netsh dhcp server $servername scope $ip set optionvalue 51 DWORD $leasetime | out-null
	$leaseinfo = netsh dhcp server $servername scope $ip show optionvalue
	foreach($info in $leaseinfo){if($info -match "OptionId : 51"){write-host "Scope $ip lease length: $($($leaseinfo[$([array]::IndexOf($leaseinfo, $info))+4]).split('=')[1]) seconds" -foregroundcolor green}}
}
 
function delayDHCPScope($servername, $ip, $leasetime){
	netsh dhcp server $server scope $ip set delayoffer $delaytime | out-null
	$delayinfo = netsh dhcp server $servername scope $ip show delayoffer
	foreach($info in $delayinfo){if($info -match "DelayOffer"){write-host $info -foregroundcolor green}}
}
 
function reserveDHCPScope($servername, $altservers, $ipscope, $ipaddr, $mac, $name){
 
	$servers = @($servername)
 
	foreach($alt in $altservers){
		if(($alt -ne "") -and ($alt -ne $null)){$servers += "\\$alt"}
	}
 
	write-host "Servers: $servers"
 
	foreach($server in $servers){
 
		$result = netsh dhcp server $server scope $ipscope
		if($result -match "The command needs a valid Scope IP Address"){
			write-host "Scope Does Not Exist!"
			exit 1
		}
 
		$result = netsh dhcp server $server scope $ipscope show clients
		if($result -match $ipaddr){
			write-host "IP Address Already Reserved!"
			exit 1
		}
 
		$regex = [regex]"([0-9A-Fa-f]{2}[:-]*){5}([0-9A-Fa-f]{2})"
		$result = $mac | foreach {$regex.Matches($_) | foreach {$_.Captures[0].Value}}
		if($result -eq $null){
			write-host "Invalid MAC Address!"
			exit 1
		}
 
		netsh dhcp server $server scope $ipscope add reservedip $ipaddr $mac $name
	}
 
}
 
if($(Get-Service -ComputerName $server -Name DHCPServer -ErrorAction SilentlyContinue) -eq $null){
	write-host "The specified server does not appear to be running DHCP"
	exit 1
}
 
$servername = "\\$server"
 
switch ($PsCmdlet.ParameterSetName){
	"showDHCP"{getDHCPScope $servername "show" }
	"enableDHCP"{foreach($ip in $(getDHCPScope $servername)){enableDHCPScope $servername $ip};getDHCPScope $servername "show"}
	"disableDHCP"{foreach($ip in $(getDHCPScope $servername)){disableDHCPScope $servername $ip};getDHCPScope $servername "show"}
	"leaseDHCP"{foreach($ip in $(getDHCPScope $servername)){leaseDHCPScope $servername $ip $leasetime}}
	"delayDHCP"{foreach($ip in $(getDHCPScope $servername)){delayDHCPScope $servername $ip $delaytime}}
	"reserveDHCP"{reserveDHCPScope $servername $altservers $ipscope $ipaddr $mac $name}
}


This handy little script will pull all of the users from the specified AD group and then grab the LastLogon time from each specified DC (or you could use [DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers to get all of them in the current domain) as well as grabbing the LastLogonTimeStamp for good measure (see my previous post for the caveats on that one). You can also specify which attribute you want to sort the results on; I recommend samaccountname because it’s usually the most useful.

Obviously it’s much quicker and simpler to do this with the ActiveDirectory cmdlets, but sometimes you’re stuck working with a bunch of 2003 DCs and have to make do with ADSI.

################################################################################################
#This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;    #
#without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     #
#THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT      #
#WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS PROVIDE THE SOFTWARE "AS IS" WITHOUT   #
#WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE         #
#IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK   #
#AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE      #
#DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. IN NO EVENT  #
#UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, BE       #
#LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES#
#ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS    #
#OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A    #
#FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER HAS BEEN     #
#ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.                                                   #
################################################################################################
 
#AD Group Last Logon Checker (ADSI Edition)
#Adam Beardwood 20/08/2012
#v1.0 - Initial Release
#v1.5 - Added Datagrid & CSV output
 
$dcs = ("DC1","DC2","DC3")
$group = "CN=Domain Admins,CN=Users,DC=domain,dc=local"
$basedn = "DC=domain,DC=local"
$sorton = "samaccountname"
$useroutput = @()
 
foreach($dc in $dcs){
	$Search = New-Object DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dc/$basedn")
	$Search.Filter = "(&(memberof=$group)(objectclass=user))"
	$Search.Sort = New-Object System.DirectoryServices.SortOption($sorton,"Ascending")
	$SearchRes1 = $Search.Findall()
 
	foreach($result in $searchres1){
 
		$user = $result.Properties.samaccountname
		$Search.Filter = "(&(objectclass=user)(samaccountname=$user))"
		$SearchRes2 = $Search.Findall()
 
		New-Variable -Name "$user" -value (New-Object System.Object) -ErrorAction Silentlycontinue
		(Get-Variable "$user").value | Add-Member -type NoteProperty -name Name -value $user[0] -ErrorAction Silentlycontinue
		foreach($adc in $dcs){
			(Get-Variable "$user").value | Add-Member -type NoteProperty -name $adc -value "" -ErrorAction Silentlycontinue
		}
		(Get-Variable "$user").value| Add-Member -type NoteProperty -name LLTS -value "" -ErrorAction Silentlycontinue
 
		if($SearchRes2[0].Properties.lastlogon -ne $null){
			(Get-Variable "$user").value.$dc = ([DateTime]::FromFileTime([Int64]::Parse($SearchRes2[0].Properties.lastlogon)))	
		}
 
		if($SearchRes2[0].Properties.lastlogontimestamp -ne $null){
			(Get-Variable "$user").value.LLTS = ([DateTime]::FromFileTime([Int64]::Parse($SearchRes2[0].Properties.lastlogontimestamp)))
		}
	}
}
 
foreach($result in $searchres1){
	$user = $result.Properties.samaccountname
	$useroutput += (Get-Variable "$user").value
}
 
$useroutput | Sort-Object Name | Out-GridView -Title "Last Logon Times"
#$useroutput | Sort-Object Name | Export-CSV C:\logons.csv


The following is the Powershell equivalent of unticking the box below for the folder “C:\temp”:

NTFS Permissions Inheritance

$acl = get-acl "C:\temp"
$acl.SetAccessRuleProtection($true,$true)
$acl | set-acl

Note that SetAccessRuleProtection takes two boolean arguments; the first turns inheritance on ($False) or off ($True) and the second determines whether the previously inherited permissions are retained ($True) or removed ($False).