{"id":4157,"date":"2013-02-16T00:01:00","date_gmt":"2013-02-16T00:01:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2013\/02\/16\/weekend-scripter-backup-and-remove-old-adms\/"},"modified":"2013-02-16T00:01:00","modified_gmt":"2013-02-16T00:01:00","slug":"weekend-scripter-backup-and-remove-old-adms","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/weekend-scripter-backup-and-remove-old-adms\/","title":{"rendered":"Weekend Scripter: Backup and Remove Old ADMs"},"content":{"rendered":"<p><strong style=\"font-size: 12px\">Summary:<\/strong><span style=\"font-size: 12px\"> Two Microsoft PFEs talk about a couple of functions to back up and remove old ADMs by using a Windows PowerShell function.<\/span><\/p>\n<p>Microsoft Scripting Guy, Ed Wilson, is here. Welcome back to two PFEs Mark Morowczynski and Tom Moser for part two of a three-part series.<\/p>\n<p>Please read <a href=\"http:\/\/blogs.technet.com\/b\/heyscriptingguy\/archive\/2013\/02\/15\/use-powershell-to-simplify-removing-old-adm-files-from-ad-ds.aspx\" target=\"_blank\">yesterday&rsquo;s<\/a> blog to catch up if you missed reading it.<\/p>\n<p>Mark Morowczynski (@markmorow) and Tom Moser (@milt0r) are Premier Field Engineers (PFEs) out of Chicago and Metro Detroit, respectively. They, with a group of contributing platform PFEs, write on the <a href=\"http:\/\/blogs.technet.com\/b\/askpfeplat\" target=\"_blank\">AskPFEPlat<\/a> blog, covering a wide range of operating system, Active Directory, and other platform-related topics. Tom is a Red Wings fan. Mark is a Hawks fan. To date, no physical altercations have occurred due to this work conflict.<\/p>\n<p>Onward!<\/p>\n<h2>Building the paths<\/h2>\n<p>Next up, we need to take all of the information we put together about the domain and use it to build our paths. The very first thing we do, starting at line 111 in the script, is to create a string that contains the path to sysvol. This UNC path specifically points to the PDCe and <em>not<\/em> to the domain DFS namespace.<\/p>\n<p style=\"padding-left: 30px\">$sysvol = &#8220;\\\\$($PDCEmulator)\\sysvol&#8221;<\/p>\n<p style=\"padding-left: 30px\">if($NoCentralStore.IsPresent -eq $true)<\/p>\n<p style=\"padding-left: 30px\">{<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $PolicyDefinitionPath = &#8220;C:\\Windows\\PolicyDefinitions&#8221;<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">else<\/p>\n<p style=\"padding-left: 30px\">{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $PolicyDefinitionPath = &#8220;$($sysvol)\\$($DomainFQDN)\\Policies\\PolicyDefinitions&#8221;<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p>&nbsp;<\/p>\n<p style=\"padding-left: 30px\">$GPTPath = &#8220;$($sysvol)\\$($domainFQDN)\\Policies&#8221;<\/p>\n<p style=\"padding-left: 30px\">$backupTarget = $null<\/p>\n<p>Next, we check to see if <strong>&ndash;NoCentralStore<\/strong> is set by looking for <strong>$NoCentralStore.IsPresent<\/strong>. If this property is true, we point to C:\\Windows\\PolicyDefinitions for the <strong>$PolicyDefinitions<\/strong> variable. The reason we check for this is that some customers expressed that they don&rsquo;t want to use the GP Central Store. Instead, they use a terminal server for Group Policy management, and that server has local copies of all of the necessary ADMX templates. If <strong>$NoCentralStore.IsPresent<\/strong> returns anything but true, including null, we use the <strong>$sysvol<\/strong><em> <\/em>variable to build out the path to the domain&rsquo;s GP Central Store. Following that, we build the path to the domain <strong>Policies<\/strong> folder, and store it in <strong>$GPTPath<\/strong>. This path contains all of the Group Policy templates. Finally, we set <strong>$backupPath<\/strong> to <strong>$null<\/strong> as a placeholder.<\/p>\n<p>Finally, before getting to function declaration, we create an object called <strong>$ADMDateStamps<\/strong> by using the <strong>ConvertFrom-CSV<\/strong><em> <\/em>cmdlet. We pass in headers, for naming the properties, as well as the names and dates associated with all out-of-box ADMs for operating systems prior to Windows Server&nbsp;2008.<\/p>\n<p>(This is just a small part.)<\/p>\n<p style=\"padding-left: 30px\">$ADMDateStamps = ConvertFrom-Csv `<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8220;ADM,DateStamp<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.adm,2\/22\/2003<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; inetres.adm,2\/18\/2005<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; system.adm,2\/18\/2005<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wmplayer.adm,2\/18\/2005<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &hellip;<\/p>\n<p>&nbsp;The object this cmdlet creates looks like this.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/3755.wes-2-16-13-1.png\"><img decoding=\"async\" title=\"Image of command output\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/3755.wes-2-16-13-1.png\" alt=\"Image of command output\" \/><\/a><\/p>\n<p>We&rsquo;ll come back to that later on in our functions.<\/p>\n<h2>Function declarations<\/h2>\n<p>Now, we&rsquo;ll discuss where all of the work takes place. The script contains six functions. For the sake of brevity, they&rsquo;re briefly described in the following table.<\/p>\n<table class=\"GridTable4\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"187\">\n<p><strong>Function<\/strong><\/p>\n<\/td>\n<td valign=\"top\" width=\"436\">\n<p><strong>Purpose<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"187\">\n<p><strong>GetADMTemplates<\/strong><\/p>\n<\/td>\n<td valign=\"top\" width=\"436\">\n<p>Performs a recursive <strong>Get-Childitem<\/strong> for *.adm in the <strong>$GPTPath<\/strong>.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"187\">\n<p><strong>GetADMXTemplateHashTable<\/strong><\/p>\n<\/td>\n<td valign=\"top\" width=\"436\">\n<p>Gets a list of *.admx from <strong>$PolicyDefinitions<\/strong> and stores information about them in a hash table.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"187\">\n<p><strong>BackupAndRemoveADM<\/strong><\/p>\n<\/td>\n<td valign=\"top\" width=\"436\">\n<p>This function does the real work. It backs up, removes, and logs all actions.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"187\">\n<p><strong>IsValidPolicyDefinitonPath<\/strong><\/p>\n<\/td>\n<td valign=\"top\" width=\"436\">\n<p>A simple function to verify that the Policy Definition Path is valid.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"187\">\n<p><strong>CheckADMDate<\/strong><\/p>\n<\/td>\n<td valign=\"top\" width=\"436\">\n<p>A simple function to check the out-of-box ADM dates against the dates found on sysvol. Returns <strong>$false<\/strong> if the date isn&rsquo;t found.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"187\">\n<p><strong>RestoreADMFiles<\/strong><\/p>\n<\/td>\n<td valign=\"top\" width=\"436\">\n<p>When used with the <strong>$logpath<\/strong> parameter, restores all ADMs to the original location.<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>&nbsp;Some of these functions are just a few lines long. For portability and reusability, it made more sense to functionalize them (is that a word?). Since some of these are pretty brief and self-explanatory, we won&rsquo;t cover them all.<\/p>\n<h2>Function: GetADMTemplates<\/h2>\n<p>As mentioned above, this is a simple function.<\/p>\n<p style=\"padding-left: 30px\">function GetADMTemplates<\/p>\n<p style=\"padding-left: 30px\">{<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; param([string]$GPTPath)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $ADMTemplates = get-childitem $GPTPath *.adm -recurse | where { $_.extension.length -eq 4 }&nbsp;&nbsp;&nbsp;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; return $ADMTemplates<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p>The function takes in a string parameter called <strong>$GPTPath<\/strong>. Based on the paths we constructed earlier in the script, this should be the UNC path to the sysvol folder on the PDCe for the domain. Line 4 does a simple <strong>Get-Childitem<\/strong><em> <\/em>with the <strong>&ndash;Recurse<\/strong><em> <\/em>switch. We also filter for *.adm<em>. <\/em>This will return all files in sysvol that end in .adm* (including ADMX and ADML). Since we want <em>only<\/em> *.ADM, we pipe the results of the <strong>Get-Childitem<\/strong><em> <\/em>to a <strong>where-object<\/strong><em> <\/em>and specifically filter for any files with an extension of 4 characters or fewer, including the period. The result is returned to the script body by using a return statement.<\/p>\n<p><a href=\"http:\/\/gallery.technet.microsoft.com\/scriptcenter\/GetADMTemplates-function-8c65e5b7\" target=\"_blank\">Download the function<\/a> from the Script Repository.<\/p>\n<h2>Function: GetADMXTemplateHashTable<\/h2>\n<p>Hash tables in Windows PowerShell are a great way to use a dictionary-type search when checking for the existence of an object. The hash table allows us to create a key\/value pair that we can later use to quickly check to see if a specific ADMX name was found anywhere in sysvol.<\/p>\n<p style=\"padding-left: 30px\">Function GetADMXTemplateHashTable<\/p>\n<p style=\"padding-left: 30px\">{<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; param([string]$PolicyDefinitionPath)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $ADMXTemplates = @{}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $ADMXFiles = get-childitem $PolicyDefinitionPath *.admx<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; if($ADMXFiles -eq $null)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; write-host -fore &#8216;yellow&#8217; &#8220;No ADMX templates found in the central store.&#8221;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; if($ADMXFiles.count -eq $null)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ADMXTemplates.Add($ADMXFiles.Name, $ADMXFiles.Fullname)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; elseif ($ADMXFiles.count -gt 1)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ADMXFiles | foreach { $ADMXTemplates.Add($_.Name, $_.Fullname) }&nbsp;&nbsp;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; return $ADMXTemplates<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p>Above, you can see that we only require a single parameter for this script&mdash; <strong>$PolicyDefinitionPath<\/strong>. Next, we create an empty hash table using <strong>$ADMXTemplates= @{}<\/strong>.<em> <\/em>Next, we use <strong>Get-Childitem<\/strong>, recursively searching for *.admx, storing the result in a variable called <strong>$ADMXFiles<\/strong><em>.<\/em><\/p>\n<p>We need to evaluate the result of that search to find out if it found one object or many. We do this by looking for the <strong>count<\/strong> property on the returned object. A single object doesn&rsquo;t have this property, but an array of objects will have the <strong>count<\/strong> property. The second <strong>if<\/strong> statement checks for the <strong>count<\/strong> property.<\/p>\n<p>Since a property that doesn&rsquo;t exist on an object is equal to null, we just check to see if count is equal to <strong>$null<\/strong>. If it is, we got only one file back from our <strong>Get-Childitem<\/strong><em>. <\/em>If it isn&rsquo;t null, we know that count must be greater than one, so we check for that. In either scenario, we then iterate through every file (or just the one) and build a set of key\/value pairs in our hash table. The <strong>file name<em> <\/em><\/strong>is stored as <strong>key<\/strong> and the <strong>full file path<\/strong> is stored as the <strong>value.<\/strong> More on that later. At the end, we return the hash table back to the caller.<\/p>\n<p><a href=\"http:\/\/gallery.technet.microsoft.com\/scriptcenter\/Get-ADMXTemplateHashTable-c5f46aa3\" target=\"_blank\">Download the above function<\/a> from the Script Repository.<\/p>\n<h2>Function: BackupAndRemoveADM<\/h2>\n<p>As noted in the script comments, <strong>#This<\/strong> function does all of the work.<\/p>\n<p>First, the function takes in three parameters: <strong>$ADMTemplatePath<\/strong>, <strong>$BackupPath<\/strong>, and <strong>$BackupTarget<\/strong>. We&rsquo;ve already defined the purpose of those first two parameters. (Hint: They&rsquo;re script parameters.) The last one is dynamically constructed in the script body and we&rsquo;ll talk about it later.<\/p>\n<p>While this <strong>BackupAndRemoveADM<\/strong> function does all of the work, it&rsquo;s a relatively simple function.<\/p>\n<p>First, we use <strong>Test-Path<\/strong><em> <\/em>to see if the path specified in <strong>$backupPath<\/strong> actually exists. If not, we use the <strong>New-Item <\/strong>cmdlet and make sure to use the <strong>&ndash;ItemType<\/strong> parameter to specify that this should be a directory and not a file, function, alias, Active Directory object, or anything else we can create with the wonderfully generic <strong>New-Item<\/strong> cmdlet.<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; if((Test-path $backupPath) -eq $false)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; New-Item -ItemType directory $backupPath<\/p>\n<p style=\"padding-left: 30px\">&nbsp; &nbsp;&nbsp;}<\/p>\n<p>Next, we need to create the GUID-based backup path. This is where <strong>$backupTarget<\/strong> comes in to play. Down in the main body of the script, we have this:<\/p>\n<p style=\"padding-left: 30px\">$backupTarget = $BackupPath + &#8220;\\&#8221; + $ADMTemplate.FullName.split(&#8216;\\&#8217;)[6]<\/p>\n<p>That line is located in a <strong>Foreach<\/strong> loop that iterates through all of the ADMs found on sysvol. The line performs a <strong>String.Split()<\/strong> on the full name of the ADMTemplate using <strong>\\ <\/strong>as a delimiter. We pull out the 6<sup>th<\/sup> element and append to the user-specified backup path. An example of that process would look like this.<\/p>\n<table style=\"width: 642px\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"21\">\n<p>\\<\/p>\n<\/td>\n<td valign=\"top\" width=\"21\">\n<p>\\<\/p>\n<\/td>\n<td valign=\"top\" width=\"151\">\n<p>PDC.Corp.Contoso.Com\\<\/p>\n<\/td>\n<td valign=\"top\" width=\"54\">\n<p>Sysvol\\<\/p>\n<\/td>\n<td valign=\"top\" width=\"122\">\n<p>Corp.contoso.com\\<\/p>\n<\/td>\n<td valign=\"top\" width=\"61\">\n<p>Policies\\<\/p>\n<\/td>\n<td valign=\"top\" width=\"58\">\n<p>{GUID}\\<\/p>\n<\/td>\n<td valign=\"top\" width=\"48\">\n<p>ADM\\<\/p>\n<\/td>\n<td valign=\"top\" width=\"105\">\n<p>System.ADM<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"21\">\n<p>0<\/p>\n<\/td>\n<td valign=\"top\" width=\"21\">\n<p>1<\/p>\n<\/td>\n<td valign=\"top\" width=\"151\">\n<p>2<\/p>\n<\/td>\n<td valign=\"top\" width=\"54\">\n<p>3<\/p>\n<\/td>\n<td valign=\"top\" width=\"122\">\n<p>4<\/p>\n<\/td>\n<td valign=\"top\" width=\"61\">\n<p>5<\/p>\n<\/td>\n<td valign=\"top\" width=\"58\">\n<p>6<\/p>\n<\/td>\n<td valign=\"top\" width=\"48\">\n<p>7<\/p>\n<\/td>\n<td valign=\"top\" width=\"105\">\n<p>8<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>&nbsp;In the path above, the characters preceding each slash becomes an element in the array. We wanted the 6<sup>th<\/sup> element, as this is the GUID for the GPO. We append that to the specified backup path and come up with the following.<\/p>\n<p style=\"padding-left: 30px\">C:\\MyADMBackup\\{2F1B9A33-F347-4010-9492-157C08B71F54}<em><\/em><\/p>\n<p>Back in the function, we check to see if that path exists. If not, the file is created. What this ends up doing is populating the backup folder with a subfolder for every GUID found in sysvol.<\/p>\n<p>Next, we actually copy the ADM located at <strong>$ADMTemplatePath<\/strong> to the path set in <strong>$backupTarget<\/strong>. Since we want to be extra sure that the ADM was successfully backed up, we make use of the Windows PowerShell <strong>$? <\/strong>variable. Consulting <strong>Get-Help<\/strong> about_automatic_variables tells us exactly what the variable does.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/4341.wes-2-16-13-2.png\"><img decoding=\"async\" title=\"Image of command output\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/4341.wes-2-16-13-2.png\" alt=\"Image of command output\" \/><\/a><\/p>\n<p>We can use <strong>$?<\/strong> to ensure that our backup, or other commands, was actually successful before we go and remove the file.<\/p>\n<p style=\"padding-left: 30px\">copy-item $ADMTemplatePath $backupTarget&nbsp;<\/p>\n<p style=\"padding-left: 30px\">if($? -eq $true)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; remove-item $ADMTemplatePath<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; write-host -fore &#8216;yellow&#8217; &#8220;Removed $($ADMTemplatePath)&#8221;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;<\/p>\n<p>If the <strong>if<\/strong> statement evaluates to true, we remove the file, and in yellow font, we write to the console that it was removed.<\/p>\n<p>You can <a href=\"http:\/\/gallery.technet.microsoft.com\/scriptcenter\/BackupAndRemoveADM-492538bc\" target=\"_blank\">download the above function<\/a> at the Script Repository.<\/p>\n<p>Like we said, simple!<\/p>\n<p>~Tom and Mark<\/p>\n<p>Thanks again, Tom and Mark, for sharing this knowledge and your time. Be sure to join us tomorrow for the conclusion.<\/p>\n<p>&nbsp;I invite you to follow me on <a href=\"http:\/\/bit.ly\/scriptingguystwitter\" target=\"_blank\">Twitter<\/a> and <a href=\"http:\/\/bit.ly\/scriptingguysfacebook\" target=\"_blank\">Facebook<\/a>. If you have any questions, send email to me at <a href=\"mailto:scripter@microsoft.com\" target=\"_blank\">scripter@microsoft.com<\/a>, or post your questions on the <a href=\"http:\/\/bit.ly\/scriptingforum\" target=\"_blank\">Official Scripting Guys Forum<\/a>. See you tomorrow. Until then, peace.<\/p>\n<p><strong>Ed Wilson, Microsoft Scripting Guy&nbsp;<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Summary: Two Microsoft PFEs talk about a couple of functions to back up and remove old ADMs by using a Windows PowerShell function. Microsoft Scripting Guy, Ed Wilson, is here. Welcome back to two PFEs Mark Morowczynski and Tom Moser for part two of a three-part series. Please read yesterday&rsquo;s blog to catch up if [&hellip;]<\/p>\n","protected":false},"author":596,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[7,152,56,410,3,411,61,45],"class_list":["post-4157","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-active-directory","tag-group-policy","tag-guest-blogger","tag-mark-morowczynski","tag-scripting-guy","tag-tom-moser","tag-weekend-scripter","tag-windows-powershell"],"acf":[],"blog_post_summary":"<p>Summary: Two Microsoft PFEs talk about a couple of functions to back up and remove old ADMs by using a Windows PowerShell function. Microsoft Scripting Guy, Ed Wilson, is here. Welcome back to two PFEs Mark Morowczynski and Tom Moser for part two of a three-part series. Please read yesterday&rsquo;s blog to catch up if [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/4157","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/users\/596"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=4157"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/4157\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media\/87096"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media?parent=4157"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=4157"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=4157"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}