{"id":8087,"date":"2021-01-26T11:59:22","date_gmt":"2021-01-26T16:59:22","guid":{"rendered":"https:\/\/jdhitsolutions.com\/blog\/?p=8087"},"modified":"2021-01-26T11:59:45","modified_gmt":"2021-01-26T16:59:45","slug":"an-active-directory-change-report-from-powershell","status":"publish","type":"post","link":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/","title":{"rendered":"An Active Directory Change Report from PowerShell"},"content":{"rendered":"\n<div class=\"wp-block-image is-style-default\"><figure class=\"alignleft size-large is-resized\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/ValidityPeriod01.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/ValidityPeriod01.png\" alt=\"\" class=\"wp-image-8088\" width=\"201\" height=\"133\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/ValidityPeriod01.png 401w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/ValidityPeriod01-300x199.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/ValidityPeriod01-350x230.png 350w\" sizes=\"auto, (max-width: 201px) 100vw, 201px\" \/><\/a><\/figure><\/div>\n\n\n\n<p>A few days ago<a href=\"https:\/\/jdhitsolutions.com\/blog\/powershell\/8067\/building-an-active-directory-watcher-with-cim-and-powershell\/\" target=\"_blank\" rel=\"noreferrer noopener\"> I posted some PowerShell code <\/a>that you could use to be alerted when things changed in Active Directory. The code used PowerShell and CIM events to notify you, for example, when a new user account is created. This can be helpful when you need alerting.  But perhaps you only need reporting. What has changed in Active Directory since a given date and time, such as in the last 24 hours?  And wouldn't it be nice to have a pretty report? Let me help. Here's how I approached the prob lem using PowerShell and the ActiveDirectory module.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">WhenChanged<\/h2>\n\n\n\n<p>Objects in Active Directory have a <em>WhenChanged <\/em>property. Many of the ActiveDirectory commands have a filter parameter. This parameter is very flexible and you definitely want to read command help for it. The reason for using the filter is that objects are filtered at the source which is much faster. This would NOT be the recommended way to filter:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">$since = (Get-Date).AddMinutes(-90)\nGet-ADuser -filter * -properties WhenChanged | Where { $_.WhenChanged -ge $since }<\/code><\/pre>\n\n\n\n<p>It will work, but think about it. You are telling PowerShell, \"Get me all user objects from the domain, send them to my computer, and then filter where the WhenChanged property is greater than the last 90 minutes.\" In a small domain, you won't notice. But if you have large domain, there is a definite difference. <\/p>\n\n\n\n<p>Instead, create a filter like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">Get-ADuser -filter {WhenChanged -ge $since} -Properties WhenChanged |\n Select-Object DistinguishedName,Name,WhenChanged<\/code><\/pre>\n\n\n\n<p>The tricky part is that you need to create this particular filter as a scriptblock.  I haven't done any AD scripting in a while and it took some time to get this worked out. I re-read cmdlet help and that got me headed in the right direction. Always read the help.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/changed-aduser.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"319\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/changed-aduser-1024x319.png\" alt=\"\" class=\"wp-image-8089\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/changed-aduser-1024x319.png 1024w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/changed-aduser-300x93.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/changed-aduser-768x239.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/changed-aduser-1536x478.png 1536w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/changed-aduser-850x265.png 850w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/changed-aduser.png 1548w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>Here are my results. Of course, I have no idea what changed but I'm not concerned about that. One thing to keep in mind, is that you may have changed objects where the only change is due to replication and a USN update. I haven't found a good way to filter out those types of changes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Get-ADObject<\/h2>\n\n\n\n<p>For my report, I only care about users, groups, computers and OUs. While I could use the specific Get-* cmdlet, it is probably just as easy to use Get-ADObject and create a good filter.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">$filter\u00a0=\u00a0{(objectclass\u00a0-eq\u00a0'user'\u00a0-or\u00a0objectclass\u00a0-eq\u00a0'group'\u00a0-or\u00a0objectclass\u00a0-eq\u00a0'organizationalunit'\u00a0)\u00a0-AND\u00a0(WhenChanged\u00a0-gt\u00a0$since\u00a0)}<\/code><\/pre>\n\n\n\n<p>Here's one way I might use this filter.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">Get-ADObject -Filter $filter -Properties WhenCreated,WhenChanged | \n Sort-Object -Property ObjectClass,WhenChanged |\n Format-Table -GroupBy objectClass -Property DistinguishedName,WhenCreated,WhenChanged<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"441\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged-1024x441.png\" alt=\"\" class=\"wp-image-8090\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged-1024x441.png 1024w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged-300x129.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged-768x331.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged-1536x662.png 1536w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged-850x366.png 850w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged.png 1776w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>One thing to point out is that if the WhenCreated is greater or equal to my cutoff date, that is most likely a new object.<\/p>\n\n\n\n<p>Another reason for using Get-ADObject is that it will also show deleted objects. However, you need to enable the Active Directory Recycle Bin. But if you have, here's revised code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">Get-ADObject -Filter $filter -Properties WhenCreated,WhenChanged -IncludeDeletedObjects | \n Select-Object *,@{Name=\"IsNew\";Expression = { if ($_.WhenCreated -ge $since) { $True} else {$False}}},\n @{Name=\"IsDeleted\";Expression = {$_.distinguishedname -match \"Deleted Objects\" }} |\n Sort-Object -Property ObjectClass,WhenChanged |\n Format-List -GroupBy objectClass -Property DistinguishedName,WhenCreated,WhenChanged,IsNew,IsDeleted<\/code><\/pre>\n\n\n\n<p>I'm defining some additional properties to indicate new or deleted objects.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged2.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"329\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged2-1024x329.png\" alt=\"\" class=\"wp-image-8091\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged2-1024x329.png 1024w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged2-300x96.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged2-768x246.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged2-1536x493.png 1536w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged2-850x273.png 850w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/whenchanged2.png 1839w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>There is a property called IsDeleted which you can also use, although it will only exist on deleted objects. My code defines it for all objects.<\/p>\n\n\n\n<p>Once you have the objects and properties there's no end to how you can format or use the results.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating an HTML Report<\/h2>\n\n\n\n<p>I love creating reports and my favorite tool is ConvertTo-Html. In my reporting scripts, I tend to create stand-alone files with embedded style. This is a good approach if you plan on emailing files. My script is really a control script that wraps around the AD, ConvertTo-HTML, and Out-File cmdlets. I'm not writing a function to send objects to the pipeline. I'm running an orchestrated set of PowerShell commands to produce a desired result. This is a control script. <\/p>\n\n\n\n<p>These types of scripts can also have parameters. Very often, I'm passing the script parameters to the underlying cmdlets, often using PSBoundParameters and splatting. Although lately I've been using a private version $PSDefaultParameterValues, which is what I'm using in this script. Which I might as well show you now.<\/p>\n\n\n\n<pre title=\"ADChangeReport.ps1\" class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\"><em>#requires\u00a0-module\u00a0ActiveDirectory<\/em>\n\n<em>#Reporting\u00a0on\u00a0deleted\u00a0items\u00a0requires\u00a0the\u00a0Active\u00a0Directory\u00a0Recycle\u00a0Bin\u00a0feature<\/em>\n[cmdletbinding()]\nParam(\n\u00a0\u00a0\u00a0\u00a0[Parameter(<em>Position<\/em>\u00a0=\u00a00,<em>HelpMessage<\/em>\u00a0=\u00a0\"Enter\u00a0a\u00a0last\u00a0modified\u00a0datetime\u00a0for\u00a0AD\u00a0objects.\u00a0The\u00a0default\u00a0is\u00a0the\u00a0last\u00a08\u00a0hours.\")]\n\u00a0\u00a0\u00a0\u00a0[ValidateNotNullOrEmpty()]\n\u00a0\u00a0\u00a0\u00a0[datetime]$Since\u00a0=\u00a0((Get-Date).AddHours(-8)),\n\u00a0\u00a0\u00a0\u00a0[Parameter(<em>HelpMessage<\/em>\u00a0=\u00a0\"What\u00a0is\u00a0the\u00a0report\u00a0title?\")]\n\u00a0\u00a0\u00a0\u00a0[string]$ReportTitle\u00a0=\u00a0\"Active\u00a0Directory\u00a0Change\u00a0Report\",\n\u00a0\u00a0\u00a0\u00a0[Parameter(<em>HelpMessage<\/em>\u00a0=\u00a0\"Add\u00a0a\u00a0second\u00a0grouping\u00a0based\u00a0on\u00a0the\u00a0object's\u00a0container\u00a0or\u00a0OU.\")]\n\u00a0\u00a0\u00a0\u00a0[switch]$ByContainer,\n\u00a0\u00a0\u00a0\u00a0[Parameter(<em>HelpMessage<\/em>\u00a0=\u00a0\"Specify\u00a0the\u00a0path\u00a0for\u00a0the\u00a0output\u00a0file.\")]\n\u00a0\u00a0\u00a0\u00a0[ValidateNotNullOrEmpty()]\n\u00a0\u00a0\u00a0\u00a0[string]$Path\u00a0=\u00a0\".\\ADChangeReport.html\",\n\u00a0\u00a0\u00a0\u00a0[Parameter(<em>HelpMessage<\/em>\u00a0=\u00a0\"Specifies\u00a0the\u00a0Active\u00a0Directory\u00a0Domain\u00a0Services\u00a0domain\u00a0controller\u00a0to\u00a0query.\u00a0The\u00a0default\u00a0is\u00a0your\u00a0Logon\u00a0server.\")]\n\u00a0\u00a0\u00a0\u00a0[string]$Server\u00a0=\u00a0$env:LOGONSERVER.SubString(2),\n\u00a0\u00a0\u00a0\u00a0[Parameter(<em>HelpMessage<\/em>\u00a0=\u00a0\"Specify\u00a0an\u00a0alternate\u00a0credential\u00a0for\u00a0authentication.\")]\n\u00a0\u00a0\u00a0\u00a0[pscredential]$Credential,\n\u00a0\u00a0\u00a0\u00a0[ValidateSet(\"Negotiate\",\"Basic\")]\n\u00a0\u00a0\u00a0\u00a0[string]$AuthType\n)\n\n<em>#region\u00a0helper\u00a0functions<\/em>\n\n<em>#a\u00a0private\u00a0helper\u00a0function\u00a0to\u00a0convert\u00a0the\u00a0objects\u00a0to\u00a0html\u00a0fragments<\/em>\nFunction\u00a0_convertObjects\u00a0{\n\u00a0\u00a0\u00a0\u00a0Param([object[]]$Objects)\n\u00a0\u00a0\u00a0\u00a0<em>#convert\u00a0each\u00a0table\u00a0to\u00a0an\u00a0XML\u00a0fragment\u00a0so\u00a0I\u00a0can\u00a0insert\u00a0a\u00a0class\u00a0attribute<\/em>\n\u00a0\u00a0\u00a0\u00a0[xml]$frag\u00a0=\u00a0$objects\u00a0|\u00a0Sort-Object\u00a0-property\u00a0WhenChanged\u00a0|\n\u00a0\u00a0\u00a0\u00a0Select-Object\u00a0-Property\u00a0DistinguishedName,Name,WhenCreated,WhenChanged,IsDeleted\u00a0|\n\u00a0\u00a0\u00a0\u00a0ConvertTo-Html\u00a0-Fragment\n\n\u00a0\u00a0\u00a0\u00a0for\u00a0($i\u00a0=\u00a01;\u00a0$i\u00a0-lt\u00a0$frag.table.tr.count;$i++)\u00a0{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if\u00a0(($frag.table.tr[$i].td[2]\u00a0-as\u00a0[datetime])\u00a0-ge\u00a0$since)\u00a0{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<em>#highlight\u00a0new\u00a0objects\u00a0in\u00a0green<\/em>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$class\u00a0=\u00a0$frag.CreateAttribute(\"class\")\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$class.value=\"new\"\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[void]$frag.table.tr[$i].Attributes.append($class)\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\u00a0<em>#if\u00a0new<\/em>\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<em>#insert\u00a0the\u00a0alert\u00a0attribute\u00a0if\u00a0the\u00a0object\u00a0has\u00a0been\u00a0deleted.<\/em>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if\u00a0($frag.table.tr[$i].td[-1]\u00a0-eq\u00a0'True')\u00a0{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<em>#highlight\u00a0deleted\u00a0objects\u00a0in\u00a0red<\/em>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$class\u00a0=\u00a0$frag.CreateAttribute(\"class\")\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$class.value=\"alert\"\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0[void]$frag.table.tr[$i].Attributes.append($class)\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\u00a0<em>#if\u00a0deleted<\/em>\n\u00a0\u00a0\u00a0\u00a0}\u00a0<em>#for<\/em>\n\n\u00a0\u00a0\u00a0\u00a0<em>#write\u00a0the\u00a0innerXML\u00a0(ie\u00a0HTML\u00a0code)\u00a0as\u00a0the\u00a0function\u00a0output<\/em>\n\u00a0\u00a0\u00a0\u00a0$frag.InnerXml\n}\n\n<em>#\u00a0private\u00a0helper\u00a0function\u00a0to\u00a0insert\u00a0javascript\u00a0code\u00a0into\u00a0my\u00a0html<\/em>\nfunction\u00a0_insertToggle\u00a0{\n\u00a0\u00a0\u00a0\u00a0[cmdletbinding()]\n\u00a0\u00a0\u00a0\u00a0<em>#The\u00a0text\u00a0to\u00a0display,\u00a0the\u00a0name\u00a0of\u00a0the\u00a0div,\u00a0the\u00a0data\u00a0to\u00a0collapse,\u00a0and\u00a0the\u00a0heading\u00a0style<\/em>\n\u00a0\u00a0\u00a0\u00a0<em>#the\u00a0div\u00a0Id\u00a0needs\u00a0to\u00a0be\u00a0simple\u00a0text<\/em>\n\u00a0\u00a0\u00a0\u00a0Param([string]$Text,\u00a0[string]$div,\u00a0[object[]]$Data,\u00a0[string]$Heading\u00a0=\u00a0\"H2\",\u00a0[switch]$NoConvert)\n\n\u00a0\u00a0\u00a0\u00a0$out\u00a0=\u00a0[System.Collections.Generic.list[string]]::New()\n\u00a0\u00a0\u00a0\u00a0if\u00a0(-Not\u00a0$div)\u00a0{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$div\u00a0=\u00a0$Text.Replace(\"\u00a0\",\u00a0\"_\")\n\u00a0\u00a0\u00a0\u00a0}\n\u00a0\u00a0\u00a0\u00a0$out.add(\"&lt;a\u00a0href='javascript:toggleDiv(\"\"$div\"\");'\u00a0title='click\u00a0to\u00a0collapse\u00a0or\u00a0expand\u00a0this\u00a0section'>&lt;$Heading>$Text&lt;\/$Heading>&lt;\/a>&lt;div\u00a0id=\"\"$div\"\">\")\n\u00a0\u00a0\u00a0\u00a0if\u00a0($NoConvert)\u00a0{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$out.Add($Data)\n\u00a0\u00a0\u00a0\u00a0}\n\u00a0\u00a0\u00a0\u00a0else\u00a0{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$out.Add($($Data\u00a0|\u00a0ConvertTo-Html\u00a0-Fragment))\n\u00a0\u00a0\u00a0\u00a0}\n\u00a0\u00a0\u00a0\u00a0$out.Add(\"&lt;\/div>\")\n\u00a0\u00a0\u00a0\u00a0$out\n}\n\n<em>#endregion<\/em>\n\n<em>#some\u00a0report\u00a0metadata<\/em>\n$reportVersion\u00a0=\u00a0\"2.1.1\"\n$thisScript\u00a0=\u00a0Convert-Path\u00a0$myinvocation.InvocationName\n\nWrite-Verbose\u00a0\"[$(Get-Date)]\u00a0Starting\u00a0$($myinvocation.MyCommand)\"\nWrite-Verbose\u00a0\"[$(Get-Date)]\u00a0Detected\u00a0these\u00a0bound\u00a0parameters\"\n$PSBoundParameters\u00a0|\u00a0Out-String\u00a0|\u00a0Write-Verbose\n\n<em>#set\u00a0some\u00a0default\u00a0parameter\u00a0values<\/em>\n$params\u00a0=\u00a0\"Credential\",\"AuthType\"\n$script:PSDefaultParameterValues\u00a0=\u00a0@{\"Get-AD*:Server\"\u00a0=\u00a0$Server}\nForEach\u00a0($param\u00a0in\u00a0$params)\u00a0{\n\u00a0\u00a0\u00a0\u00a0if\u00a0($PSBoundParameters.ContainsKey($param))\u00a0{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Write-Verbose\u00a0\"[$(Get-Date)]\u00a0Adding\u00a0'Get-AD*:$param'\u00a0to\u00a0script\u00a0PSDefaultParameterValues\"\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$script:PSDefaultParameterValues[\"Get-AD*:$param\"]\u00a0=\u00a0$PSBoundParameters.Item($param)\n\u00a0\u00a0\u00a0\u00a0}\n}\n\nWrite-Verbose\u00a0\"[$(Get-Date)]\u00a0Getting\u00a0current\u00a0Active\u00a0Directory\u00a0domain\"\n$domain\u00a0=\u00a0Get-ADDomain\n\n<em>#create\u00a0a\u00a0list\u00a0object\u00a0to\u00a0hold\u00a0all\u00a0of\u00a0the\u00a0HTML\u00a0fragments<\/em>\nWrite-Verbose\u00a0\"[$(Get-Date)]\u00a0Initializing\u00a0fragment\u00a0list\"\n$fragments\u00a0=\u00a0[System.Collections.Generic.list[string]]::New()\n$fragments.Add(\"&lt;H2>$($domain.dnsroot)&lt;\/H2>\")\n$fragments.Add(\"&lt;a\u00a0href='javascript:toggleAll();'\u00a0title='Click\u00a0to\u00a0toggle\u00a0all\u00a0sections'>+\/-&lt;\/a>\")\n\nWrite-Verbose\u00a0\"[$(Get-Date)]\u00a0Querying\u00a0$($domain.dnsroot)\"\n$filter\u00a0=\u00a0{(objectclass\u00a0-eq\u00a0'user'\u00a0-or\u00a0objectclass\u00a0-eq\u00a0'group'\u00a0-or\u00a0objectclass\u00a0-eq\u00a0'organizationalunit'\u00a0)\u00a0-AND\u00a0(WhenChanged\u00a0-gt\u00a0$since\u00a0)}\n\nWrite-Verbose\u00a0\"[$(Get-Date)]\u00a0Filtering\u00a0for\u00a0changed\u00a0objects\u00a0since\u00a0$since\"\n$items\u00a0=\u00a0Get-ADObject\u00a0-filter\u00a0$filter\u00a0-IncludeDeletedObjects\u00a0-Properties\u00a0WhenCreated,WhenChanged,IsDeleted\u00a0-OutVariable\u00a0all\u00a0|\u00a0Group-Object\u00a0-property\u00a0objectclass\n\nWrite-Verbose\u00a0\"[$(Get-Date)]\u00a0Found\u00a0$($all.count)\u00a0total\u00a0items\"\n\nif\u00a0($items.count\u00a0-gt\u00a00)\u00a0{\n\u00a0\u00a0\u00a0\u00a0foreach\u00a0($item\u00a0in\u00a0$items)\u00a0{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$category\u00a0=\u00a0\"{0}{1}\"\u00a0-f\u00a0$item.name[0].ToString().toUpper(),$item.name.Substring(1)\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Write-Verbose\u00a0\"[$(Get-Date)]\u00a0Processing\u00a0$category\u00a0[$($item.count)]\"\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if\u00a0($ByContainer)\u00a0{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Write-Verbose\u00a0\"[$(Get-Date)]\u00a0Organizing\u00a0by\u00a0container\"\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$subgroup\u00a0=\u00a0$item.group\u00a0|\u00a0Group-Object\u00a0-Property\u00a0{\u00a0$_.distinguishedname.split(',',\u00a02)[1]\u00a0}\u00a0|\u00a0Sort-Object\u00a0-Property\u00a0Name\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$fraghtml\u00a0=\u00a0[System.Collections.Generic.list[string]]::new()\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0foreach\u00a0($subitem\u00a0in\u00a0$subgroup)\u00a0{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Write-Verbose\u00a0\"[$(Get-Date)]\u00a0$($subItem.name)\"\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$fragGroup\u00a0=\u00a0_convertObjects\u00a0$subitem.group\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$divid\u00a0=\u00a0$subitem.name\u00a0-replace\u00a0\"=|,\",\"\"\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$fraghtml.Add($(_inserttoggle\u00a0-Text\u00a0\"$($subItem.name)\u00a0[$($subitem.count)]\"\u00a0-div\u00a0$divid\u00a0-Heading\u00a0\"H4\"\u00a0-Data\u00a0$fragGroup\u00a0-NoConvert))\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\u00a0<em>#foreach\u00a0subitem<\/em>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\u00a0<em>#if\u00a0by\u00a0container<\/em>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0else\u00a0{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$fragHtml\u00a0=\u00a0_convertObjects\u00a0$item.group\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$code\u00a0=\u00a0_insertToggle\u00a0-Text\u00a0\"$category\u00a0[$($item.count)]\"\u00a0-div\u00a0$category\u00a0-Heading\u00a0\"H3\"\u00a0-Data\u00a0$fragHtml\u00a0-NoConvert\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0$fragments.Add($code)\n\u00a0\u00a0\u00a0\u00a0}\u00a0<em>#foreach\u00a0item<\/em>\n\n<em>#my\u00a0embedded\u00a0CSS<\/em>\n\u00a0\u00a0\u00a0\u00a0$head\u00a0=\u00a0@\"\n&lt;Title>$ReportTitle&lt;\/Title>\n&lt;style>\nh2\u00a0{\n\u00a0\u00a0\u00a0\u00a0width:95%;\n\u00a0\u00a0\u00a0\u00a0background-color:#7BA7C7;\n\u00a0\u00a0\u00a0\u00a0font-family:Tahoma;\n\u00a0\u00a0\u00a0\u00a0font-size:12pt;\n}\nh4\u00a0{\n\u00a0\u00a0\u00a0\u00a0width:95%;\n\u00a0\u00a0\u00a0\u00a0background-color:#b5f144;\n}\nbody\u00a0{\n\u00a0\u00a0\u00a0\u00a0background-color:#FFFFFF;\n\u00a0\u00a0\u00a0\u00a0font-family:Tahoma;\n\u00a0\u00a0\u00a0\u00a0font-size:12pt;\n}\ntd,\u00a0th\u00a0{\n\u00a0\u00a0\u00a0\u00a0border:1px\u00a0solid\u00a0black;\n\u00a0\u00a0\u00a0\u00a0border-collapse:collapse;\n}\nth\u00a0{\n\u00a0\u00a0\u00a0\u00a0color:white;\n\u00a0\u00a0\u00a0\u00a0background-color:black;\n}\ntable,\u00a0tr,\u00a0td,\u00a0th\u00a0{\n\u00a0\u00a0\u00a0\u00a0padding-left:\u00a010px;\n\u00a0\u00a0\u00a0\u00a0margin:\u00a00px\n}\ntr:nth-child(odd)\u00a0{background-color:\u00a0lightgray}\ntable\u00a0{\n\u00a0\u00a0\u00a0\u00a0width:95%;\n\u00a0\u00a0\u00a0\u00a0margin-left:5px;\n\u00a0\u00a0\u00a0\u00a0margin-bottom:20px;\n}\n.alert\u00a0{\u00a0color:red;\u00a0}\n.new\u00a0{\u00a0color:green;\u00a0}\n.footer\u00a0{\u00a0font-size:10pt;\u00a0}\n.footer\u00a0tr:nth-child(odd)\u00a0{background-color:\u00a0white}\n.footer\u00a0td,tr\u00a0{\n\u00a0\u00a0\u00a0\u00a0border-collapse:collapse;\n\u00a0\u00a0\u00a0\u00a0border:none;\n}\n.footer\u00a0table\u00a0{width:15%;}\ntd.size\u00a0{\n\u00a0\u00a0\u00a0\u00a0text-align:\u00a0right;\n\u00a0\u00a0\u00a0\u00a0padding-right:\u00a025px;\n}\n&lt;\/style>\n&lt;script\u00a0type='text\/javascript'\u00a0src='https:\/\/ajax.googleapis.com\/ajax\/libs\/jquery\/1.4.4\/jquery.min.js'>\n&lt;\/script>\n&lt;script\u00a0type='text\/javascript'>\nfunction\u00a0toggleDiv(divId)\u00a0{\n`$(\"#\"+divId).toggle();\n}\nfunction\u00a0toggleAll()\u00a0{\nvar\u00a0divs\u00a0=\u00a0document.getElementsByTagName('div');\nfor\u00a0(var\u00a0i\u00a0=\u00a00;\u00a0i\u00a0&lt;\u00a0divs.length;\u00a0i++)\u00a0{\nvar\u00a0div\u00a0=\u00a0divs[i];\n`$(\"#\"+div.id).toggle();\n}\n}\n&lt;\/script>\n&lt;H1>$ReportTitle&lt;\/H1>\n\"@\n\n<em>#a\u00a0footer\u00a0for\u00a0the\u00a0report.\u00a0This\u00a0could\u00a0be\u00a0styled\u00a0with\u00a0CSS<\/em>\n\u00a0\u00a0\u00a0\u00a0$post\u00a0=\u00a0@\"\n&lt;table\u00a0class='footer'>\n\u00a0\u00a0\u00a0\u00a0&lt;tr\u00a0align\u00a0=\u00a0\"right\">&lt;td>Report\u00a0run:\u00a0&lt;i>$(Get-Date)&lt;\/i>&lt;\/td>&lt;\/tr>\n\u00a0\u00a0\u00a0\u00a0&lt;tr\u00a0align\u00a0=\u00a0\"right\">&lt;td>Report\u00a0version:\u00a0&lt;i>$ReportVersion&lt;\/i>&lt;\/td>&lt;\/tr>\n\u00a0\u00a0\u00a0\u00a0&lt;tr\u00a0align\u00a0=\u00a0\"right\">&lt;td>Source:\u00a0&lt;i>$thisScript&lt;\/i>&lt;\/td>&lt;\/tr>\n&lt;\/table>\n\"@\n\n\u00a0\u00a0\u00a0\u00a0$htmlParams\u00a0=\u00a0@{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Head\u00a0=\u00a0$head\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0precontent\u00a0=\u00a0\"Active\u00a0Directory\u00a0changes\u00a0since\u00a0$since.\u00a0Reported\u00a0from\u00a0$($Server.toUpper()).\u00a0Replication\u00a0only\u00a0changes\u00a0may\u00a0be\u00a0included.\"\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Body\u00a0=($fragments\u00a0|\u00a0Out-String)\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0PostContent\u00a0=\u00a0$post\n\u00a0\u00a0\u00a0\u00a0}\n\u00a0\u00a0\u00a0\u00a0Write-Verbose\u00a0\"[$(Get-Date)]\u00a0Creating\u00a0report\u00a0$ReportTitle\u00a0version\u00a0$reportversion\u00a0saved\u00a0to\u00a0$path\"\n\u00a0\u00a0\u00a0\u00a0ConvertTo-HTML\u00a0@htmlParams\u00a0|\u00a0Out-File\u00a0-FilePath\u00a0$Path\n\u00a0\u00a0\u00a0\u00a0Get-Item\u00a0-Path\u00a0$Path\n}\nelse\u00a0{\n\u00a0\u00a0\u00a0\u00a0Write-Warning\u00a0\"No\u00a0modified\u00a0objects\u00a0found\u00a0in\u00a0the\u00a0$($domain.dnsroot)\u00a0domain\u00a0since\u00a0$since.\"\n}\n\nWrite-Verbose\u00a0\"[$(Get-Date)]\u00a0Ending\u00a0$($myinvocation.MyCommand)\"<\/code><\/pre>\n\n\n\n<p>The script creates a series of HTML fragments which are eventually pulled together to create the final file. My script includes a few helper functions. One of them parses the HTML as XML so that I can insert a class attribute to indicate if the object is new or deleted. I have a defined style that shows deleted objects in red and new objects in green. The other function helps me insert javascript that lets me collapse sections of the file.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">c:\\scripts\\ADChangeReport.ps1 -Since (Get-Date).Addhours(-1) -Path c:\\scripts\\ad6.html<\/code><\/pre>\n\n\n\n<p>The script will default to the logon server for the current user, although you can specify a different domain controller. You can also specify alternate credentials. You can, and should, run the script from a domain member desktop.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-top.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-top-1024x576.png\" alt=\"\" class=\"wp-image-8093\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-top-1024x576.png 1024w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-top-300x169.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-top-768x432.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-top-1536x864.png 1536w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-top-850x478.png 850w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>I can click on the +\/- to toggle collapsing all regions, or click on the different categories. You can see the new group in green text.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-bottom.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-bottom-1024x576.png\" alt=\"\" class=\"wp-image-8094\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-bottom-1024x576.png 1024w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-bottom-300x169.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-bottom-768x432.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-bottom-1536x864.png 1536w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-bottom-850x478.png 850w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>In the bottom part of the report you can see the deleted user in red. I also like to include metadata information in the footer of my reports. This information helps me know where the report originated. This can be helpful if you setup a scheduled reporting system but forget to document it. One missing piece of information in this report is the computer name. I can see the path for the script file but I can't tell what computer. I'll leave that adjustment to you.<\/p>\n\n\n\n<p>The reporting script also supports organizing the results by container.  My code will group on the object's container which I get by splitting the distinguished name.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">if\u00a0($ByContainer)\u00a0{\n  Write-Verbose\u00a0\"[$(Get-Date)]\u00a0Organizing\u00a0by\u00a0container\"\n\u00a0\u00a0$subgroup\u00a0=\u00a0$item.group\u00a0|\u00a0Group-Object\u00a0-Property\u00a0{\u00a0$_.distinguishedname.split(',',\u00a02)[1]\u00a0}\u00a0|\u00a0Sort-Object\u00a0-Property\u00a0Name\n...<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-container.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-container-1024x576.png\" alt=\"\" class=\"wp-image-8095\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-container-1024x576.png 1024w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-container-300x169.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-container-768x432.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-container-1536x864.png 1536w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/adchange-container-850x478.png 850w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>This is an admittedly complicated PowerShell script so if there is anything you don't understand and have questions about, please feel free to ask in the comments. I will also point out that like the AD event monitor, just because you can use something like this doesn't mean you should. Small domains with minimal changes could get by with this script or something similar. <\/p>\n\n\n\n<p>I'd be leery though, of using this in a large enterprise with a highly dynamic Active Directory infrastructure. Although if your change window was small, it might be effective. I'm assuming that if you are running a large AD environment, your company has invested in high-quality and appropriate management and reporting tools. You can leverage PowerShell for those niche or special situations.<\/p>\n\n\n\n<p>If you can try out the reporting script, I'd love to hear your experiences. Enjoy!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A few days ago I posted some PowerShell code that you could use to be alerted when things changed in Active Directory. The code used PowerShell and CIM events to notify you, for example, when a new user account is created. This can be helpful when you need alerting. But perhaps you only need reporting&#8230;.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"New on the blog: An Active Directory Change Report from #PowerShell","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[7,4,8],"tags":[149],"class_list":["post-8087","post","type-post","status-publish","format-standard","hentry","category-active-directory","category-powershell","category-scripting","tag-activedirectory"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>An Active Directory Change Report from PowerShell &#8226; The Lonely Administrator<\/title>\n<meta name=\"description\" content=\"Here&#039;s how you can build an Active Directory change report using PowerShell. I built a script to create a fancy HTML report.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"An Active Directory Change Report from PowerShell &#8226; The Lonely Administrator\" \/>\n<meta property=\"og:description\" content=\"Here&#039;s how you can build an Active Directory change report using PowerShell. I built a script to create a fancy HTML report.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/\" \/>\n<meta property=\"og:site_name\" content=\"The Lonely Administrator\" \/>\n<meta property=\"article:published_time\" content=\"2021-01-26T16:59:22+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2021-01-26T16:59:45+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/ValidityPeriod01.png\" \/>\n<meta name=\"author\" content=\"Jeffery Hicks\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@JeffHicks\" \/>\n<meta name=\"twitter:site\" content=\"@JeffHicks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Jeffery Hicks\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/8087\\\/an-active-directory-change-report-from-powershell\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/8087\\\/an-active-directory-change-report-from-powershell\\\/\"},\"author\":{\"name\":\"Jeffery Hicks\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#\\\/schema\\\/person\\\/d0258030b41f07fd745f4078bdf5b6c9\"},\"headline\":\"An Active Directory Change Report from PowerShell\",\"datePublished\":\"2021-01-26T16:59:22+00:00\",\"dateModified\":\"2021-01-26T16:59:45+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/8087\\\/an-active-directory-change-report-from-powershell\\\/\"},\"wordCount\":1042,\"commentCount\":8,\"publisher\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#\\\/schema\\\/person\\\/d0258030b41f07fd745f4078bdf5b6c9\"},\"image\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/8087\\\/an-active-directory-change-report-from-powershell\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2021\\\/01\\\/ValidityPeriod01.png\",\"keywords\":[\"ActiveDirectory\"],\"articleSection\":[\"Active Directory\",\"PowerShell\",\"Scripting\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/8087\\\/an-active-directory-change-report-from-powershell\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/8087\\\/an-active-directory-change-report-from-powershell\\\/\",\"url\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/8087\\\/an-active-directory-change-report-from-powershell\\\/\",\"name\":\"An Active Directory Change Report from PowerShell &#8226; The Lonely Administrator\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/8087\\\/an-active-directory-change-report-from-powershell\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/8087\\\/an-active-directory-change-report-from-powershell\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2021\\\/01\\\/ValidityPeriod01.png\",\"datePublished\":\"2021-01-26T16:59:22+00:00\",\"dateModified\":\"2021-01-26T16:59:45+00:00\",\"description\":\"Here's how you can build an Active Directory change report using PowerShell. I built a script to create a fancy HTML report.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/8087\\\/an-active-directory-change-report-from-powershell\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/8087\\\/an-active-directory-change-report-from-powershell\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/8087\\\/an-active-directory-change-report-from-powershell\\\/#primaryimage\",\"url\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2021\\\/01\\\/ValidityPeriod01.png\",\"contentUrl\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2021\\\/01\\\/ValidityPeriod01.png\",\"width\":401,\"height\":266},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/powershell\\\/8087\\\/an-active-directory-change-report-from-powershell\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"PowerShell\",\"item\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/category\\\/powershell\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"An Active Directory Change Report from PowerShell\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/\",\"name\":\"The Lonely Administrator\",\"description\":\"Practical Advice for the Automating IT Pro\",\"publisher\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#\\\/schema\\\/person\\\/d0258030b41f07fd745f4078bdf5b6c9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#\\\/schema\\\/person\\\/d0258030b41f07fd745f4078bdf5b6c9\",\"name\":\"Jeffery Hicks\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg\",\"caption\":\"Jeffery Hicks\"},\"logo\":{\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"An Active Directory Change Report from PowerShell &#8226; The Lonely Administrator","description":"Here's how you can build an Active Directory change report using PowerShell. I built a script to create a fancy HTML report.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/","og_locale":"en_US","og_type":"article","og_title":"An Active Directory Change Report from PowerShell &#8226; The Lonely Administrator","og_description":"Here's how you can build an Active Directory change report using PowerShell. I built a script to create a fancy HTML report.","og_url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/","og_site_name":"The Lonely Administrator","article_published_time":"2021-01-26T16:59:22+00:00","article_modified_time":"2021-01-26T16:59:45+00:00","og_image":[{"url":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/ValidityPeriod01.png","type":"","width":"","height":""}],"author":"Jeffery Hicks","twitter_card":"summary_large_image","twitter_creator":"@JeffHicks","twitter_site":"@JeffHicks","twitter_misc":{"Written by":"Jeffery Hicks","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/#article","isPartOf":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/"},"author":{"name":"Jeffery Hicks","@id":"https:\/\/jdhitsolutions.com\/blog\/#\/schema\/person\/d0258030b41f07fd745f4078bdf5b6c9"},"headline":"An Active Directory Change Report from PowerShell","datePublished":"2021-01-26T16:59:22+00:00","dateModified":"2021-01-26T16:59:45+00:00","mainEntityOfPage":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/"},"wordCount":1042,"commentCount":8,"publisher":{"@id":"https:\/\/jdhitsolutions.com\/blog\/#\/schema\/person\/d0258030b41f07fd745f4078bdf5b6c9"},"image":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/#primaryimage"},"thumbnailUrl":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/ValidityPeriod01.png","keywords":["ActiveDirectory"],"articleSection":["Active Directory","PowerShell","Scripting"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/","url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/","name":"An Active Directory Change Report from PowerShell &#8226; The Lonely Administrator","isPartOf":{"@id":"https:\/\/jdhitsolutions.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/#primaryimage"},"image":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/#primaryimage"},"thumbnailUrl":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/ValidityPeriod01.png","datePublished":"2021-01-26T16:59:22+00:00","dateModified":"2021-01-26T16:59:45+00:00","description":"Here's how you can build an Active Directory change report using PowerShell. I built a script to create a fancy HTML report.","breadcrumb":{"@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/#primaryimage","url":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/ValidityPeriod01.png","contentUrl":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/01\/ValidityPeriod01.png","width":401,"height":266},{"@type":"BreadcrumbList","@id":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8087\/an-active-directory-change-report-from-powershell\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"PowerShell","item":"https:\/\/jdhitsolutions.com\/blog\/category\/powershell\/"},{"@type":"ListItem","position":2,"name":"An Active Directory Change Report from PowerShell"}]},{"@type":"WebSite","@id":"https:\/\/jdhitsolutions.com\/blog\/#website","url":"https:\/\/jdhitsolutions.com\/blog\/","name":"The Lonely Administrator","description":"Practical Advice for the Automating IT Pro","publisher":{"@id":"https:\/\/jdhitsolutions.com\/blog\/#\/schema\/person\/d0258030b41f07fd745f4078bdf5b6c9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/jdhitsolutions.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/jdhitsolutions.com\/blog\/#\/schema\/person\/d0258030b41f07fd745f4078bdf5b6c9","name":"Jeffery Hicks","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg","url":"https:\/\/secure.gravatar.com\/avatar\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg","caption":"Jeffery Hicks"},"logo":{"@id":"https:\/\/secure.gravatar.com\/avatar\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg"}}]}},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":148,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/148\/order-managing-active-directory-with-windows-powershell-tfm-finally\/","url_meta":{"origin":8087,"position":0},"title":"Order Managing Active Directory with Windows PowerShell: TFM &#8211; Finally!","author":"Jeffery Hicks","date":"September 22, 2008","format":false,"excerpt":"Yes, its finally true. You can finally get your hands on Managing Active Directory with Windows PowerShell: TFM. The book is being printed so you can get your copy today. You can order it today at ScriptingOutpost.com in both print and ebook format. Or if you prefer the best of\u2026","rel":"","context":"In &quot;PowerShell&quot;","block_context":{"text":"PowerShell","link":"https:\/\/jdhitsolutions.com\/blog\/category\/powershell\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":8270,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/8270\/active-directory-group-reporting\/","url_meta":{"origin":8087,"position":1},"title":"Active Directory Group Reporting","author":"Jeffery Hicks","date":"March 31, 2021","format":false,"excerpt":"I've pushed v1.2.0 of the ADReportingTools module to the PowerShell Gallery. The release adds the missing help for Get-ADComputerReport. I've also added better documentation and information in warning messages when running commands in the PowerShell ISE or VS Code. To re-iterate, the module is designed to be run from a\u2026","rel":"","context":"In &quot;Active Directory&quot;","block_context":{"text":"Active Directory","link":"https:\/\/jdhitsolutions.com\/blog\/category\/active-directory\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/03\/Collection_DomainGroup01-1.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":8185,"url":"https:\/\/jdhitsolutions.com\/blog\/active-directory\/8185\/scaling-the-active-directory-log-search-with-powershell\/","url_meta":{"origin":8087,"position":2},"title":"Scaling the Active Directory Log Search with PowerShell","author":"Jeffery Hicks","date":"February 16, 2021","format":false,"excerpt":"Recently, I posted a demonstration of how to find changes to your Active Directory using PowerShell. This process requires that you search through the Security event log on all your domain controllers. As a few people pointed out, myself included, this has the potential to not scale very well in\u2026","rel":"","context":"In &quot;Active Directory&quot;","block_context":{"text":"Active Directory","link":"https:\/\/jdhitsolutions.com\/blog\/category\/active-directory\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2021\/02\/pexels-shuxuan-cao-4432160.jpg?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":545,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/545\/get-expert-ad-and-identity-management-information\/","url_meta":{"origin":8087,"position":3},"title":"Get Expert AD and Identity Management Information","author":"Jeffery Hicks","date":"January 12, 2010","format":false,"excerpt":"Some of my readers may have realized I don't blog much about Active Directory here, despite having written a book on the subject. That's because I also blog and help moderate The Experts Community. This site, although run by Quest Software, is not a commercial site. It is intended as\u2026","rel":"","context":"In &quot;Active Directory&quot;","block_context":{"text":"Active Directory","link":"https:\/\/jdhitsolutions.com\/blog\/category\/active-directory\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":133,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/133\/techmentor-just-around-the-corner\/","url_meta":{"origin":8087,"position":4},"title":"Techmentor Just Around the Corner","author":"Jeffery Hicks","date":"March 4, 2008","format":false,"excerpt":"The registration deadline for the first Techmentor conference of the year is almost upon us. I'll be doing sessions on using Powershell to manage Active Directory, PowerShell and WMI, Logon Scripts and more. Plus, I'm always happy to hang out and chat. I always have a great time. Hope to\u2026","rel":"","context":"In &quot;PowerShell&quot;","block_context":{"text":"PowerShell","link":"https:\/\/jdhitsolutions.com\/blog\/category\/powershell\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1036,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/1036\/join-me-in-orlando\/","url_meta":{"origin":8087,"position":5},"title":"Join Me in Orlando","author":"Jeffery Hicks","date":"December 30, 2010","format":false,"excerpt":"I will be presenting 3 sessions at Techmentor Orlando 2011. The conference runs March 14-18, 2011 at the Disney Yacht Club. My sessions are all on Wednesday March 16. In addition to all the other fabulous material at the conference I will be presenting the following: PowerShell Scripting Best Practices\u2026","rel":"","context":"In &quot;Active Directory&quot;","block_context":{"text":"Active Directory","link":"https:\/\/jdhitsolutions.com\/blog\/category\/active-directory\/"},"img":{"alt_text":"Disney Yacht Club","src":"https:\/\/i0.wp.com\/techmentorevents.com\/design\/ecg\/techmentorevents\/home\/img\/portal_2011spring.gif?resize=350%2C200","width":350,"height":200},"classes":[]}],"_links":{"self":[{"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/posts\/8087","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/comments?post=8087"}],"version-history":[{"count":0,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/posts\/8087\/revisions"}],"wp:attachment":[{"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/media?parent=8087"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/categories?post=8087"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/tags?post=8087"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}