Monday, October 15, 2018

Custom private registry key

I've long been a proponent of creating a "custom" registry presence on all PC's and servers to allow the storage of critical information.  Much of what would normally be needed from systems is already accessible via existing keys, but there are always thing specific to each company that are nice to be able to keep track of.

I used to just create these manually but for domain wide deployment that's not practical.

This PowerShell script will create and/or validate that your custom private registry key exists on all systems specified whether that be a full domain or a list.

Many settings are externalized to make the script generic, however the list of key values is not.  You should edit the script to correct the list to suit your needs.

An HTML email report is generated to show the results.  The report is coded so that it's short and "relatively" clean.

Here is an example of the output email from a small test run:

As always the most current version of the script can be found at the PowerShell Gallery here:
https://www.powershellgallery.com/packages/Private-RegKey

I'll list the code below but keep in mind that this version may not be the most up to date.

Param (
  [Switch]$Debug = $false,
  [Switch]$Console = $false
)
<#======================================================================================
         File Name : Private-RegKey.ps1
   Original Author : Kenneth C. Mazie  (kcmjr AT kcmjr DOT com)
                   : 
       Description : Sets custom registry keys on all domain PCs.  This version only creates and populates with "-" as a default.
                   : If an existing value is found no changes are made.  It is expected that other processes and/or
                   : scripts will populate the keys with appropriate values.  This just makes sure the key and value
                   : structure is in place.   Creates a simplified email report with results.
                   : 
             Notes : Normal operation is with no command line options.  
                   : Optional argument: -Debug $true (defaults to false) 
                   :                    -DebugTarget name (only applies if in debug)
                   :                    -Console $true (enables local console output)
                   : 
          Warnings : None
                   :   
             Legal : Public Domain. Modify and redistribute freely. No rights reserved.
                   : SCRIPT PROVIDED "AS IS" WITHOUT WARRANTIES OR GUARANTEES OF 
                   : ANY KIND. USE AT YOUR OWN RISK. NO TECHNICAL SUPPORT PROVIDED.
                   : That being said, please report any bugs you find!!
                   :
           Credits : Code snippets and/or ideas came from many sources including but 
                   :   not limited to the following:
                   : 
    Last Update by : Kenneth C. Mazie 
   Version History : v1.00 - 12-06-13 - Original 
    Change History : v1.10 - 02-08-16 - Added "auto logon" key   
                   : v1.20 - 04-13-16 - Added authorized local admins key
                   : v2.00 - 06-27-17 - Complete rewrite using invoke-command instead of .net or WMI.
                   : v2.10 - 01-11-18 - Fixed issue with credentials.
                   : v2.20 - 10-04-18 - Added new keys.  Changed name & added values for PS Gallery.
                   : v3.00 - 10-10-18 - Added a list of keys checked to report.  Complete rewrite of
                   :                    registry pull to speed up domain wide checks.
#                  :
#=======================================================================================#>
<#PSScriptInfo
.VERSION 3.00
.GUID cc8fe6bd-2831-453e-bc16-bd5b3763471d
.AUTHOR Kenneth C. Mazie (kcmjr AT kcmjr.com)
.DESCRIPTION 
Sets custom registry keys on all domain PCs.  This version only creates and populates with "-" as a default.
If an existing value is found no changes are made.  It is expected that other processes and/or
scripts will populate the keys with appropriate values.  This just makes sure the key and value
structure is in place.  Creates a simplified email report with results.  An option to include updates
is available.
#>

clear-host
$ErrorActionPreference = "SilentlyContinue"

#--[ Functions ]-------------------------------------------------------------------------
Function LoadModules {
        Import-Module ActiveDirectory
}

Function LoadConfig { #--[ Read and load configuration file ]-----------------------------------------
    If (!(Test-Path $Script:ConfigFile)){       #--[ Error out if configuration file doesn't exist ]--
        $Script:EmailBody = "---------------------------------------------`n" 
        $Script:EmailBody += "--[ MISSING CONFIG FILE.  Script aborted. ]--`n" 
        $Script:EmailBody += "---------------------------------------------" 
      SendEmail
        Write-Host $EmailBody -ForegroundColor Red
      break
    }Else{
    [xml]$Script:Configuration = Get-Content $Script:ConfigFile 
    $Script:DebugTarget = $Script:Configuration.Settings.General.DebugTarget
        $Script:RegistryPath = $Script:Configuration.Settings.General.RegistryPath
        $Script:Domain = $Script:Configuration.Settings.General.Domain
        $Script:DebugEmail = $Script:Configuration.Settings.Email.Debug 
        $Script:CompanyName = $Script:Configuration.Settings.Email.CompanyName 
    $Script:eMailTo = $Script:Configuration.Settings.Email.To
    $Script:eMailFrom = $Script:Configuration.Settings.Email.From
    $Script:eMailHTML = $Script:Configuration.Settings.Email.HTML
    $Script:eMailSubject = $Script:Configuration.Settings.Email.Subject
    $Script:SmtpServer = $Script:Configuration.Settings.Email.SmtpServer
        $Script:UserName = $Script:Configuration.Settings.Credentials.Username
    $Script:EncryptedPW = $Script:Configuration.Settings.Credentials.Password
    $Script:Base64String = $Script:Configuration.Settings.Credentials.Key   
        $ByteArray = [System.Convert]::FromBase64String($Base64String)
        $Script:Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $UserName, ($EncryptedPW | ConvertTo-SecureString -Key $ByteArray)
        #$Script:Password = $Credential.GetNetworkCredential().Password       #--[ Warning, exposes encrypted password ]--
    }
}

Function SendEmail {
    If ($Script:Debug){ $Script:eMailTo = $Script:DebugEmail }
$msg = new-object System.Net.Mail.MailMessage
$msg.From = $Script:eMailFrom
$msg.To.Add("$Script:eMailTo")
$msg.Subject = $Script:eMailSubject
$msg.IsBodyHtml = $Script:eMailHTML
$msg.Body = $Script:EmailBody 
$ErrorActionPreference = "silentlycontinue"
$smtp = new-object System.Net.Mail.SmtpClient($Script:SmtpServer)
    $smtp.Send($msg)
    If ($Console){Write-Host "--- Email Sent ---" -ForegroundColor White } 
}

#==[ Main Body ]================================================================
$DayOfWeek = (get-date).DayOfWeek
$StartTime = [datetime]::Now
#$domain = (Get-ADDomain).DNSroot      #--[ Optional.  Pulled from config file. ]--
$Script:Message = ""
$Target = ""
$Local = $False
$ScriptName = ($MyInvocation.MyCommand.Name).split(".")[0] 
$Script:LogFile = "$PSScriptRoot\$ScriptName-{0:MM-dd-yyyy_HHmmss}.html" -f (Get-Date)  
$Script:ConfigFile = "$PSScriptRoot\$ScriptName.xml"  

LoadConfig 
LoadModules

#--[ For testing only ]------------
#$Debug = $true
#$Console = $true
#----------------------------------

If ($Script:Debug){
If ($Console){Write-Host  "===================== DEBUG MODE ENABLED =============================" -ForegroundColor Red }
#TargetList = Get-ADComputer -Filter {Server -eq $Script:DebugTarget} -Property * | select name | sort name
    $TargetList = Get-ADComputer -Properties * -Filter * | where {$_.name -like $Script:DebugTarget} 
    If ($TargetList.count -lt 1){
        $Script:EmailBody = "-- Debug Mode Enabled --<br><br>"
        $Msg = "`nThe registry key check script found NO valid systems to scan.  `nDouble check your system list specification in the config file."
        If ($Console){Write-Host $Msg -ForegroundColor Red }
        $Script:EmailBody += $Msg
        $Script:EmailBody += "<br>The current specification = "+$Script:DebugTarget
        SendEmail
        break
    }
}Else{
#$TargetList = Get-ADComputer -Filter * | ForEach-Object {$_.Name}
$TargetList = Get-ADComputer -Filter {OperatingSystem -Like "*Windows*"} -Property * | select name | sort name
}

$FontFamily = "Consolas"   #--[ Use to change the font ]--

$Script:FontDarkCyan = '<span style="display:inline;font-family:'+$FontFamily+';size:7pt;color:#008B8B;margin-top:0px;margin-bottom:0px;">'
$Script:FontBlack = '<span style="display:inline;font-family:'+$FontFamily+';size:7pt;color:#000000;margin-top:0px;margin-bottom:0px;">'
$Script:FontRed = '<span style="display:inline;font-family:'+$FontFamily+';size:7pt;color:#ff0000;margin-top:0px;margin-bottom:0px;">'
$Script:FontDarkRed = '<span style="display:inline;font-family:'+$FontFamily+';size:7pt;color:#990000;margin-top:0px;margin-bottom:0px;">'
$Script:FontGreen = '<span style="display:inline;font-family:'+$FontFamily+';size:7pt;color:#00ff00;margin-top:0px;margin-bottom:0px;">'
$Script:FontDarkGreen = '<span style="display:inline;font-family:'+$FontFamily+';size:7pt;color:#009900;margin-top:0px;margin-bottom:0px;">'
$Script:FontDimGray = '<span style="display:inline;font-family:'+$FontFamily+';size:7pt;color:#696969;margin-top:0px;margin-bottom:0px;">'
$Script:FontYellow = '<span style="display:inline;font-family:'+$FontFamily+';size:7pt;color:#ffff00;margin-top:0px;margin-bottom:0px;">'

$Script:EmailBody = $Script:FontBlack +"Below is a record of the status of the "+ $Script:CompanyName +" custom registry key.  Entries are coded to reduce report size.<br>"
$Script:EmailBody += "<br><font size=2 color=gray>Coding key:<br>"
$Script:EmailBody += "B = Base RegKey. (Red means it was created, Green means it was detected)<br>O = OK.  Value exists and there is some data in it."
$Script:EmailBody += "  No new updates found.<br>C = Created missing value with default data of ""-"".<br>U = Found an existing value and an update.  Updated with new data." 
$Script:EmailBody += "<br>See end of email for list of keys in the order that they are inspected.<br></font>"

$NewValue = ""
$ValueList = @(             #--[ Adjust this list as suits your environment ]--
    "Approved Local Admins",
    "Approved RDP Access",
"Asset Tag",
"Assigned User",
"Auto Logon",
"Backup Schedule",
    "Build Date",
    "Build Validation",
    "Clean",
"Custom 1",
"Custom 2",
    "Custom 3",    
"Group",
"IE Version",
"Java Protected",
"Location",
"Product Key",
    "Purge",
    "RSAT User",
"Security Posture",
    "Serial Number",
    "XML Parser Version",
    "Z-Notes"
)

ForEach ($Target in $TargetList){
    $Failure = $false
$Target = ($Target.name).ToUpper()

If ($Console){
Write-Host "`n`n==[ "  -ForegroundColor Cyan -NoNewline 
Write-Host "Target: $Target " -NoNewline 
Write-Host "]===============================================`n" -ForegroundColor Cyan 
}
$Script:EmailBody = $Script:EmailBody + $Script:FontDarkCyan+'<br>'+$Target+'</span>' 
    $Local = $False
    If ($Target -eq $Env:ComputerName){
        If ($Console){Write-Host "Local PC - No Ping test..." -ForegroundColor Green }
        $Script:EmailBody = $Script:EmailBody + $Script:FontDarkGreen+'<font color="black">--</font>LocalPC<font color="black">--</font></span>' 
        $Local = $true
        Try{
            $Result = Test-Path -Path $Script:RegistryPath -ErrorAction "stop"            
        }Catch{
            $ErrorMsg =$_.Exception.Message
            #$ErrorMsg       #--[ Optional ]--
        }
    }Else{
    If(Test-Connection -ComputerName $Target -count 1 -BufferSize 16 -ErrorAction SilentlyContinue ){
    If ($Console){Write-Host "Ping test OK..." -ForegroundColor Green }
    $Script:EmailBody = $Script:EmailBody + $Script:FontDarkGreen+'<font color="black">--</font>PingOK<font color="black">--</font></span>' 
            Try{
                $Result = Invoke-command -ComputerName $Target -Authentication default -Credential $Credential -ScriptBlock { 
                    test-path -Path $Using:RegistryPath -ErrorAction "stop"
                }
            }Catch{
                $ErrorMsg =$_.Exception.Message
                #$ErrorMsg       #--[ Optional ]--
            }
        }Else{
    If ($Console){Write-Host "Ping test FAILED..." -ForegroundColor Red }
            $Script:EmailBody = $Script:EmailBody +'<font color="black";font-family:Consolas;>--</font>'+$Script:FontDarkRed+'Unable to ping target.<font color="black">--</font></span>'
            $Failure = $true
        }
    }    

    If (!($Failure)){
        If ($Result){
            If ($Script:Console){Write-Host "Base Key Detected" -ForegroundColor Green }
            $Script:EmailBody += $Script:FontDarkGreen+'B</span>'
        }Else{
            If ($Script:Console){Write-Host "Creating Base Key" -ForegroundColor Red}
            $Script:EmailBody += $Script:FontDarkRed+'B</span>'
            If($Local){
                Try{
                    New-Item -Path $Script:RegistryPath -Force | Out-Null
                }Catch{
                    $Failed = $True    
                    $ErrorMsg =$_.Exception.Message
                    #$ErrorMsg       #--[ Optional ]--            
                }
            }Else{
                Try{
                    $Result = Invoke-command -ComputerName $Target -Authentication default -Credential $Credential -ScriptBlock { 
                    New-Item -Path $Using:RegistryPath -Force | Out-Null
                    }
                }Catch{
                    $Failed = $True
                    $ErrorMsg =$_.Exception.Message
                    #$ErrorMsg       #--[ Optional ]--
                }
            }
            If ($Failed){
                $Script:EmailBody = $Script:EmailBody + $Script:FontDarkRed+'<font color="black";font-family:Consolas>--</font>Base Key FAIL<font color="black">--</font></span>'
                If ($Console){Write-Host "-- Base Key Detect/Create FAILED" -ForegroundColor Red }
                $Script:EmailBody += $Script:FontDarkRed+'B</span>'
            }
        }
        $Script:EmailBody += $Script:FontBlack+'--'

        #--[ Test for values ]----------------------------------------------
        Try{
            If ($Local){
                $Result = Get-ItemProperty -Path $Script:RegistryPath -ErrorAction "stop"   
            }Else{    
                $Result = Invoke-command -ComputerName $Target -Authentication default -Credential $Credential -ScriptBlock { 
                    Get-ItemProperty -Path $Using:RegistryPath -ErrorAction "stop"
                }
            }
        }Catch{
            $ErrorMsg =$_.Exception.Message
            #$ErrorMsg       #--[ Optional ]--
        }

        If ($Console){Write-Host "Checking Sub-Keys " -ForegroundColor Yellow -NoNewline}     
    ForEach ($Value in $ValueList){
            If ([string]::IsNullOrEmpty($Result.$Value)){
                If ($Console){Write-Host "*" -NoNewline -ForegroundColor Red }                       #--[ value does not exist, creating it ]--
                $Script:EmailBody = $Script:EmailBody + $Script:FontDarkRed+'C</span>'
                $NewResult = Invoke-command -ComputerName $Target -Authentication default -Credential $Credential -ScriptBlock { 
                    New-ItemProperty -Path $Using:RegistryPath -Name $Using:Value -Value "-" -PropertyType STRING -Force | Out-Null
                }
            }Else{
                If ($Script:Update){
                    If ($Console){Write-Host "*" -NoNewline -ForegroundColor Yellow}                 #--[ value already exists but needs update ]--
                    $Script:EmailBody = $Script:EmailBody + $Script:FontDarkCyan+'U</span>'
                    $NewResult = Invoke-command -ComputerName $Target -Authentication default -Credential $Credential -ScriptBlock { 
                        New-ItemProperty -Path $Using:RegistryPath -Name $Using:ValueName -Value $Using:NewValue -PropertyType STRING -Force | Out-Null
                    }
                }Else{    
                    If ($Console){Write-Host "*" -NoNewline -ForegroundColor Green}                  #--[ value already exists ]--
                    $Script:EmailBody = $Script:EmailBody + $Script:FontDarkGreen+'O</span>'
                }
            }
        }
    }
}

If ($Console){Write-Host "`n`n--- Run Completed ---" -ForegroundColor Cyan } 
$Script:EmailBody = $Script:EmailBody + $Script:FontRed+'<br><br>---Run Completed---' 

$Script:EmailBody = $Script:EmailBody +'<br><br><font color="gray";font-family:Consolas; size=2>The following keys are being checked/written according to the codes above:'
ForEach ($Value in $ValueList){
    $Script:EmailBody = $Script:EmailBody +'<br><font color="gray";size=2;font-family:Consolas>'+$Value+'</font></span>'
}

SendEmail

<#==[ XML Configuration file example.  Must reside in same folder as the script and be named like "scriptname.xml" ]=======================

<!-- Settings & Configuration File -->
<Settings>
<General>
<ReportName>Weekly Registry Key Refresh</ReportName>
<DebugTarget>*PC85*</DebugTarget>
<RegistryPath>HKLM:\Software\MyCompany</RegistryPath>
<DnsServer>dc01</DnsServer>
<Domain>MyCompany.com</Domain>
<CompanyName>MyCompany</CompanyName>
</General>
<Email>
<From>WeeklyReports@mycompany.com</From>
<To>me@mycompany.com</To>
<Debug>me@mycomapny.com</Debug>
<Subject>Weekly Registry Key Refresh</Subject>
<HTML>$true</HTML>
<SmtpServer>100.100.50.5</SmtpServer>
</Email>
<Credentials>
<UserName>mydomain\serviceaccount</UserName>
<Password>76492d1NgA0AGEAMAAwADQAZgBiAGMAYQBhAG6AHoAIAegB2AHYgB2AHYAZQAxAGIATg11IAeAegB2AHYAZQAxAGIATgBaADcAYwBtAHAAWHwAYwAzADQABaADcAYwBtAHAAWQB6AHoAIAegB2AHYAZQQA9AIAZQQA9AIAegB2AHYAZ6AHoAIAegB2AHYAZQQA9AIAegB2AHYAZGUAZgBkAGYAZAA=</Password>
<Key>kdhe8m+EhCh7HCh7HCvLOEyj2N0IObibCh7HCvLOEyj2N0IObiie8mE=</Key>
</Credentials>
</Settings>

#>

Friday, October 5, 2018

AD DNS Valiator

In a small environment AD integrated DNS is one of those things that can slip through the cracks.  As much as we want to keep it accurate and clean as long as DHCP and dynamic addressing does it's thing it usually just runs.

To keep old stale records cleaned out DNS scavenging is supposed to deal with old stale records.  It doesn't always do that so I created a script to 1: scan the records and check for a live system, and 2: delete them after two subsequent failures.

The delete option is a recent add-on.  Again, an HTML report is emailed to a set of users for review and for a record of what was done.

The script is available on the PowerShell Gallery here:  PowerShell Gallery  and called "DNSValidator".

Example output:  (the inline report is hard to view but the attachments are much cleaner)
Note: Sensitive data blocked out...




Standardized AD computer decommissioner

We seem to have a lack of , shall we say, "process engagement" here where folks don't always do their due diligence with removing computers from the environment.  To address that I created this script to automate and standardize removing systems from AD.

The idea here is that the script will be called by SharePoint.  A minimal GUI will be created on SharePoint asking for the target system and the users AD user ID.  When they click OK, or RUN or whatever the button says, it sends the two items as input variables to the script.

The script has it's own AD credential to perform the actions.  It verifies that the user is in a group preset from the XML config file.  It then checks for an AD record for the target and if found, deletes the record.  It then checks DNS for a record and again, if found, deletes it.

The resultant data set is formatted and sent in an HTML email to the users specified.  Below is an example of the resultant email.

The script is available on the PowerShell Gallery here: 
https://www.powershellgallery.com/profiles/Kcmjr