AutoCert.ps1

Param (
  [bool]$Console = $false,
  [bool]$Debug = $false,
  [bool]$Status = $False
)

<#======================================================================================
         File Name : AutoCert.ps1
   Original Author : Kenneth C. Mazie (kcmjr AT kcmjr.com)
                   :
       Description : Automatically processes VPN certificates on a weekly schedule.
                   :
             Notes : Normal operation is with no command line options. Requires certutil.exe. exists on system execting script.
                   : Create files in cert store named "username.update" to trigger the update section manually.
                   : Run via a scheduled task to run as needed.
                   :
         Arguments : Note - Any combination of arguments may be used at once.
                   : "-console $true" option to display runtime info on the console.
                   : "-debug $true" includes a status email to debug user(s) only. Use for troubleshooting
                   : NOTE - As currently set a "debugging" email goes out BCC to the debug user(s) each
                   : time a user email is sent. See "sendemail" function...
                   : "-status $true" sends status results to the status user(s) only. Use for managers, etc who insist on reports.
                   :
          Warnings : See end of file for configuration file settings and example !!!
                   :
             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.
                   :
           Credits : Code snippets and/or ideas came from many sources including but
                   : not limited to the following:
                   :
   Original Author : Unknown via the Internet. Modded by Tyler Applebaum for use in production environemnt.
    Last Update by : Kenneth C. Mazie
   Version History : v1.00 - 06-01-14 - Original
    Change History : v2.00 - 07-13-15 - Major rewrite.
                   : v2.10 - 08-13-15 - Added option to send update notices.
                   : v2.10 - 04-18-16 - Added bypass for admin accounts
                   : v3.00 - 05-26-16 - Added option to create stand alone certs
                   : v3.10 - 08-10-16 - fixed bug that caused script to abort on every run
                   : v3.20 - 08-15-16 - Added option to include requestor in email if force is used.
                   : v3.30 - 08-19-16 - Added eventlog tracking and improved debug messaging
                   : v4.00 - 08-29-16 - Branched to AutoCert variant. Added try/catch to snag errors.
                   : v4.10 - 09-20-16 - Numerous coding changes and bug fixes. Mostly in output.
                   : v5.00 - 01-13-17 - Fixed for use with DFS share. Numerous changes. Fixed status email.
                   : v5.10 - 02-08-17 - fixed notice email attachments throwing error and stopping email from sending.
                   : adjusted message text. changed so reply to is no longer the user. Added BCC.
                   : disabled force option. Added user first name to email.
                   : v5.10 - 02-16-17 - fixed issue with detecting console color.
                   : v5.20 - 03-29-17 - added error checking to generation function.
                   : v5.30 - 04-14-17 - Added better handling of stsus users.
                   : v5.40 - 04-20-17 - Added error checks to make sure temp folder works for service account running script.
                   : Expanded list of bypass patterns.
                   : v5.50 - 04-21-17 - Retooled email system. Adjusted logging. Removed un-needed code.
                   : v5.60 - 06-20-17 - Commented out line 635 for cleaning up variables.
                   : v5.61 - 03-02-18 - Minor notation tweak for PS Gallery upload. Moved more variables out to config file.
                   : v5.62 - 03-02-18 - Corrected description.
                   :
=======================================================================================#>

<#PSScriptInfo
.VERSION 5.62
.GUID 39d43bd3-0d21-4060-b4ce-eafc88302f57
.AUTHOR Kenneth C. Mazie (kcmjr AT kcmjr.com)
.DESCRIPTION
 Automatically processes VPN certificates on a weekly schedule.
#>
 


Clear-Host 
$ErrorActionPreference = "Stop" #ilentlyContinue"

If($Console){$Script:Console = $true}
If($Debug){$Script:Debug = $true}
If($Status){ $Script:Status = $true}

$Computer = $Env:ComputerName
$Script:ScriptName = ($MyInvocation.MyCommand.Name).split(".")[0] 
$Script:LogFile = $PSScriptRoot+"\"+$ScriptName+"_{0:MM-dd-yyyy_HHmmss}.log" -f (Get-Date)
$Script:ConfigFile = "$PSScriptRoot\$ScriptName.xml" 

$Script:ConsoleEmail = $true               #--[ Use this to enable sending of a debug email any time an operation is performed. ]--
$Script:ConsoleEmailOK = $false            #--[ Used as a flag by the script to determine whether to send a debug email. Don't change this ]--

$Script:Datetime = Get-Date -Format "MM-dd-yyyy_HH:mm"
$Script:EmailMsg = ""
$ErrorMessage = ""
$FailedItem = ""
$Script:Installed = $False
$Script:Counter = 0
$Script:DebugLogMsg = @()
$Script:EventLogMsg = ""
$Script:TempDir = "$env:temp"              #--[ *** Temporary local folder *** ]--
$IllegalUser = $False
$Script:Attach = $false
$Script:NotifyUser = $False
$DarkConsole = ((Get-Host).UI.RawUI.BackgroundColor -like "*Dark*")   #--[ Detect console color and adjust accordingly ]------------------------------
$ScriptColor = @{
1 = "Green"
2 = "Red"
3 = "Yellow"
4 = "Blue"
5 = "Cyan"
6 = "Magenta"
7 = "Gray"
8 = "White"
9 = "Black"
11 = "DarkGreen"
12 = "DarkRed"
13 = "DarkCyan"
14 = "DarkBlue"
15 = "DarkCyan"
16 = "DarkMagenta"
17 = "DarkGray"
18 = "Gray"
19 = "Black"
}

#--[ Read and load configuration file ]-----------------------------------------
If (!(Test-Path "$PSScriptRoot\$ScriptName.xml")){                            #--[ Error out if configuration file doesn't exist ]--
      Write-host "MISSING CONFIG FILE. Script aborted." -ForegroundColor red
      break
}Else{
    [xml]$Script:Configuration = Get-Content "$PSScriptRoot\$ScriptName.xml"  #--[ Load configuration ]--
    $Script:CompanyName = $Script:Configuration.Settings.General.CompanyName
    $Script:CompanyInitials = $Script:Configuration.Settings.General.CompanyInitials   #--[ Used to prefix files and notations ]--
    $Script:SupportPhone = $Script:Configuration.Settings.General.SupportPhone
    $Script:Title = $Script:Configuration.Settings.General.ReportTitle     
    $Script:DebugUser = $Script:Configuration.Settings.Email.DebugUser
    $Script:StatusUser = $Script:Configuration.Settings.Email.StatusUser
    $Script:DebugSubject = $Script:Configuration.Settings.Email.DebugSubject 
    #$Script:EmailTo = $Script:Configuration.Settings.Email.To #--[ Determined by script ]--
    $Script:EmailHTML = $Script:Configuration.Settings.Email.HTML
    $Script:EmailSubject = $Script:Configuration.Settings.Email.Subject
    $Script:EmailFrom = $Script:Configuration.Settings.Email.From
    $Script:EmailDomain = $Script:Configuration.Settings.Email.Domain
    $Script:SmtpServer = $Script:Configuration.Settings.Email.SmtpServer
    $Script:UserName = $Script:Configuration.Settings.Credentials.Username
    $Script:Password = $Script:Configuration.Settings.Credentials.Password
    $Script:VPNPassword = $Script:Configuration.Settings.Credentials.VPNPassword
    $Script:VPNUsers = $Script:Configuration.Settings.General.VPNGroup
    $Script:EventlogName = $Script:Configuration.Settings.General.EventlogName
    $Script:EventlogID = $Script:Configuration.Settings.General.EventlogID
    $Script:EventlogType = $Script:Configuration.Settings.General.EventlogType
    $Script:CertStore = $Script:Configuration.Settings.General.CertStore 
    $Script:Attach1 = $Script:Configuration.Settings.Email.Attachments.Attach1
    $Script:Attach2 = $Script:Configuration.Settings.Email.Attachments.Attach2
    $Script:Attach3 = $Script:Configuration.Settings.Email.Attachments.Attach3
    $Script:BadPattern = $Script:Configuration.Settings.General.BadPattern      #--[ Anything matching this pattern is bypassed. Use for admin accounts ]--
    $Script:TemplateName = $Script:Configuration.Settings.General.TemplateName
    $Script:CAName = $Script:Configuration.Settings.General.CaName
}    

#-------------------------------------------------------------------------------
#$Credential = New-Object PSCredential($UserName, (ConvertTo-SecureString $Password.SubString(64) -k ($Password.SubString(0,64) -split "(?<=\G[0-9a-f]{2})(?=.)" | % { [Convert]::ToByte($_,16) })))
#$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $UserName, ($Password | ConvertTo-SecureString)

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")    #--[ Assembly required for balloon tips ]--
New-EventLog -LogName Application -Source $Script:EventlogName -ErrorAction SilentlyContinue                  #--[ Register new eventlog type ]--

#==[ Functions ]================================================================
Function MessageLog ($Mode, $MsgText, [Int]$MsgColor){    #--[ Compiles and writes log out to the local eventlog and for email ]--
    $Script:EventLogMsg += "$MsgText`n"
    If ($Script:Console -and ($mode -ne "DebugLogOnly")){   #--[ Only used in certain places. Don't alter this or you will break the status email ]--
        If ($DarkConsole){
            $Color = $ScriptColor[$MsgColor]
        }Else{
            $Color = $ScriptColor[($MsgColor+10)]
        }    
        If ($MsgText -eq "HR"){
            Write-host "`n===============================================================`n" -ForegroundColor $Color
        }Else{
            write-host $MsgText -ForegroundColor $Color
        }
    }
    
    If ($Mode -ne "ConsoleOnly"){    #--[ Only used in certain places. Don't alter this or you will break the status email ]--
        $Color = $ScriptColor[($MsgColor + 10)]
        If ($MsgText -eq "HR"){$MsgText = "<br>===============================================================<br>"}
        $Script:DebugLogMsg += "<font color="+$Color+">"+"$MsgText</font><br>"
    }
}

Function SendEmail {
    $Smtp = new-object Net.Mail.SmtpClient($Script:SmtpServer)
    $Email = New-Object System.Net.Mail.MailMessage
    $Email.From = "$Script:EmailFrom@$Script:EmailDomain"
    $Email.Subject = $Script:EmailSubject
    $Email.Body = $Script:EmailMsg 
    If ($Script:EmailHTML){$Email.IsBodyHtml = $true}
    If ($Script:Attach){   #--[ Attachments should include "USER.pfx", "Root.p7b", "Intermediate.p7b, install doc" ]--
        Try{
            $Email.Attachments.Add($Script:EmailAttach1)
            $Email.Attachments.Add($Script:EmailAttach2)
            $Email.Attachments.Add($Script:EmailAttach3)
            $Email.Attachments.Add($Script:EmailAttach4)
        }Catch{
            $ErrorMessage = $_.Exception.Message
            $FailedItem = $_.Exception.ItemName
            $Email.Subject = $Script:EmailSubject+"- EMAIL FAILURE REPORT -"
            $Email.Body = "- ATTACHMENT FAILURE REPORT -<br>"+$_.Exception.Message+' <br> '+$_.Exception.ItemName
            ForEach ($User in $Script:DebugUser.User){
                $Email.To.Add("$User@$Script:EmailDomain")
            }    
            $Script:NotifyUser = $False
        }    
    }

    If ($Script:NotifyUser){                                                            #--[ Email target user ]--
        $Email.To.Add("$Script:TargetUser@$Script:EmailDomain")
        MessageLog "Both" "-- Target user added to email --" "3"
    }
    
    If (!($Script:Debug)){                                                              #--[ Email debug user(s) ]--
        ForEach ($User in $Script:DebugUser.User){
            $Email.Bcc.Add("$User@$Script:EmailDomain")                                 #--[ Always BCC debug user(s) as a safety check ]--
        }
        MessageLog "Both" "-- Debug user(s) added as BCC to email --" "3"
    }
    
    If ($Script:NotifyUser){      
        $Smtp.Send($Email)
        MessageLog "Both" "===== Target user notification email Sent =====`n" "1"
    }Else{
        MessageLog "Both" "===== NO TARGET USER EMAIL SENT =====`n" "3"
    }
        
    $Smtp.Dispose()
    $Email.Dispose()        
        
    $Script:NotifyUser = $False
    StatusEmail    
}

Function StatusEmail {
    If (((Get-Date -Format dddd) -eq "Monday") -or ($Script:Debug) -or ($Script:Status)){       #--[Only send status mail on mondays or when debugging ]--
        $StatusEmail = New-Object System.Net.Mail.MailMessage
        $StatusSmtp = new-object Net.Mail.SmtpClient($Script:SmtpServer)
        $StatusEmail.From = "$Script:EmailFrom@$Script:EmailDomain"
        If ($Script:EmailHTML){$StatusEmail.IsBodyHtml = $true}
        
        If ($Script:Debug){
            $StatusEmail.Subject = $Script:DebugSubject
            ForEach ($User in $Script:DebugUser.User){
                $StatusEmail.To.Add("$User@$Script:EmailDomain")                                #--[ Always copy debug user(s) as a safety check ]--
            }
            MessageLog "Both" "-- Debug user(s) added to email --" "3"
        }
        
        If (((Get-Date -Format dddd) -eq "Monday") -or ($Script:Status)){
            $StatusEmail.Subject = "VPN Certificate Status Check" # $Script:DebugSubject
            ForEach ($User in $Script:StatusUser.User){
                $StatusEmail.To.Add("$User@$Script:EmailDomain")                                #--[ Send to status user only if option was specified ]--
            }
            MessageLog "Both" "-- Status user(s) added to email --" "3"
        }

        MessageLog "Both" "===== Status and Debugging Email Sent =====`n" "1"
        $StatusEmail.Body = $Script:DebugLogMsg 
        $StatusSmtp.Send($StatusEmail)
    }Else{
        MessageLog "Both" "====== NO STATUS EMAILS SENT =====`n" "2"
    }
 
}

Function GenerateCert {
    $ErrorActionPreference = "stop"
    MessageLog "Both" '-- Generating new VPN Certificate --' "3"
    #[string]$TemplateName = $Script:TemplateName
    [string]$CertAuthName = $Script:CaName+"\"+$Script:CaName
    
    [string]$UID = $Script:TargetUser
    [string]$Email = "$Script:TargetUser@$Script:EmailDomain" 

    #--[ Generate request file ]------------------------------------------------
    MessageLog "Both" "-- Generating Certificate Request File --" "3"    

    #--[ Clean out temp folder ]--
    remove-item $Script:TempDir\usercert.inf -ErrorAction silentlycontinue -Force:$true -Confirm:$false
    remove-item $Script:TempDir\usercert.req -ErrorAction silentlycontinue -Force:$true -Confirm:$false
    remove-item $Script:TempDir\*.cer -ErrorAction silentlycontinue -Force:$true -Confirm:$false
    remove-item $Script:TempDir\*.p7b -ErrorAction silentlycontinue -Force:$true -Confirm:$false
    remove-item $Script:TempDir\*.pfx -ErrorAction silentlycontinue -Force:$true -Confirm:$false
    remove-item $Script:TempDir\*.rsp -ErrorAction silentlycontinue -Force:$true -Confirm:$false
        
    add-content $Script:TempDir\usercert.inf "[NewRequest]`r
    Subject = `"CN=$UID`"`r
    Exportable = TRUE`r
    RequestType = CMC`r
    [RequestAttributes]`r
    CertificateTemplate = `"$Script:TemplateName`"`r
    SAN = `"Email=$Email`""


    #--[ Build Cert Request ]--------------------------------------------
    MessageLog "Both" "-- Building Certificate Request --" "3"
    Try{
        C:\Windows\system32\certreq.exe -new $Script:TempDir\usercert.inf $Script:TempDir\usercert.req
    }Catch{
        $ErrorMessage = $_.Exception.Message
        $FailedItem = $_.Exception.ItemName
        $Script:DebugSubject = $ErrorMessage
        MessageLog "Both" "Build Cert : $ErrorMessage" "7"
        StatusEmail
        break;break;break        
    }
    
    #--[ Send request to authority ]--------------------------------------------
    MessageLog "Both" "-- Sending Certificate Request --" "3"
    Try{
        C:\Windows\system32\certreq.exe -submit -config "$CertAuthName" $Script:TempDir\usercert.req $Script:TempDir\$UID.cer
    }Catch{
        $ErrorMessage = $_.Exception.Message
        $FailedItem = $_.Exception.ItemName
        $Script:DebugSubject = $ErrorMessage
        MessageLog "Both" "Request Cert : $ErrorMessage" "7"
        StatusEmail
        break;break;break        
    }    

    #--[ Install certificate on local machine ]---------------------------------
    MessageLog "Both" "-- Installing Certificate --" "3"
    Try{
        C:\Windows\system32\certreq.exe -accept $Script:TempDir\$UID.cer
        $Script:Installed = $true 
    }Catch{
        $ErrorMessage = $_.Exception.Message
        $FailedItem = $_.Exception.ItemName
        $Script:DebugSubject = $ErrorMessage
        MessageLog "Both" "Install cert on local : $ErrorMessage" "7"
        StatusEmail
        break;break;break        
    }                

    #--[ Export certificate and e-mail ]----------------------------------------
    
    Try{
        #--[ If the next line fails using a variable hard code the ca search as "CN=CA1\CA1*". ]--
        #dir cert:\currentuser\my | ? { $_.hasPrivateKey -and $_.Subject -like "*$Script:TargetUser*" -and $_.PrivateKey.CspKeyContainerInfo.Exportable -and $_.Subject -notlike "*E=*" -and $_.Issuer -like "CN=CA1\CA1*"} | Foreach-Object {[system.IO.file]::WriteAllBytes("$Script:CertStore\$Script:TargetUser.pfx", ($_.Export('PFX', $Script:VPNPassword)))} #Find VPN cert and exclude internal User cert
        dir cert:\currentuser\my | ? { $_.hasPrivateKey -and $_.Subject -like "*$Script:TargetUser*" -and $_.PrivateKey.CspKeyContainerInfo.Exportable -and $_.Subject -notlike "*E=*" -and $_.Issuer -like "CN=$CertAuthName*"} | Foreach-Object {[system.IO.file]::WriteAllBytes("$Script:CertStore\$Script:TargetUser.pfx", ($_.Export('PFX', $Script:VPNPassword)))} #Find VPN cert and exclude internal User cert
    }Catch{
        $ErrorMessage = $_.Exception.Message
        $FailedItem = $_.Exception.ItemName
        $Script:DebugSubject = $ErrorMessage
        MessageLog "Both" "Export Cert : $ErrorMessage" "7"
        StatusEmail
        break;break;break
    }
        
    MessageLog "Both" "-- Exporting Certificate. Emailing user. --" "3"
        
    While (!(Test-Path "$Script:CertStore\$Script:TargetUser.pfx")){
        Sleep 2
        $Script:Counter ++
        If ($Script:Counter -ge 5){
            MessageLog "Both" "-- Failure to detect newly generated certificate in backup location." "2"
        }
    }        
    
    #--[ Copy cert files to local temp folder for email attach, otherwise path causes an error ]--
    Copy-Item -Path ($Script:CertStore+"\"+$Script:Attach1) -Destination $Script:TempDir -Force:$true -Confirm:$false 
    $Script:EmailAttach1 = "$Script:TempDir\$Script:Attach1"
    Copy-Item -Path ($Script:CertStore+"\"+$Script:Attach2) -Destination $Script:TempDir -Force:$true -Confirm:$false 
    $Script:EmailAttach2 = "$Script:TempDir\$Script:Attach2"
    Copy-Item -Path ($Script:CertStore+"\"+$Script:Attach3) -Destination $Script:TempDir -Force:$true -Confirm:$false 
    $Script:EmailAttach3 = "$Script:TempDir\$Script:Attach3"
    Copy-Item -Path ($Script:CertStore+"\"+$Script:TargetUser+".pfx") -Destination $Script:TempDir -Force:$true -Confirm:$false 
    $Script:EmailAttach4 = $Script:TempDir+"\"+$Script:TargetUser+".pfx"
    $Script:Attach = $true
        
    If (Test-Path $Script:EmailAttach4){
        $Script:EmailMsg = $Script:TargetFName.givenName+',<br><br>This is an automated email from the '+$Script:CompanyName+' VPN server.<br>
        The system has generated an updated VPN certificate for you. The required files are attached.<br><br>
        Please download and install the attached certificates on your personal computer in order to<br>
        allow you to connect to, or continue to connect to the '
+$Script:CompanyName+' VPN.<br><br>
        If four files are NOT attached to this email please contact IT Support.<br><br>
        Once the attachments are saved, right click on the <strong>'
+$Script:CompanyInitials+'-Root.p7b</strong> file and select "Install Certificate"<br>
        (Click Next, Browse and select Trusted Root Certification Authorities, click OK,<br>
        Next, Finish, click Yes on the warning). After that, right click on the <strong>'
+$Script:CompanyInitials+'-Issuing.p7b</strong><br>
        file and select "Install Certificate" (Click Next, Next, Finish).<br><br>Do the same on your personal
        '
+$Script:TargetUser+' certificate (Click Next, Next, enter the password, Next, Next, Finish).<br><br>
        The password to import your personal certificate is lowercase "'
+$Script:VPNPassWord+'" (no quotes).<br><br>
        Please retain this email for reference. This will be the only notice sent.<br><br>
        Contact Support at '
+$Script:SupportPhone+' or reply to this email if you have issues or questions.<br><br>
        '
+$Script:CompanyName+' IT Network Department'
        $Script:NotifyUser = $true
        SendEmail                                              #--[ Send the user email ]--
    }Else{
        $Script:EmailMsg = 'This is an automated email from the '+$Script:CompanyName+' VPN server.<br><br>
        There was an issue in the creation of your VPN certificate. IT Support has been<br>
        notified automatically. They will contact you shortly.'

        $Script:NotifyUser = $true
        SendEmail                                              #--[ Send the user email ]--
    }
}

#==[ End Of Functions ]=========================================================

MessageLog "Both" "=============== VPN Certificate Inpection Script Initializing ===============" "5" 
MessageLog "Both" "Script executed from server : $Env:ComputerName on $Script:DateTime" "7"
MessageLog "Both" "Certificate store : $Script:CertStore" "7"

Try{
    Add-Content -Value "X" -Path $Script:CertStore'\-ScriptFlag-.txt' -Force:$true -ErrorAction Stop  -Confirm:$false  
    $X = Get-Content -Path $Script:CertStore'\-ScriptFlag-.txt' -ErrorAction:Stop
    Remove-Item -Path $Script:CertStore'\-ScriptFlag-.txt' -Force:$true -ErrorAction Stop -Confirm:$false
}Catch{
    $ErrorMessage = $_.Exception.Message
    $FailedItem = $_.Exception.ItemName
    $Script:DebugSubject = "Script failure during CERTSTORE check. "
    MessageLog "Both" "Script failure during CERTSTORE check. " "7"
    MessageLog "Both" "Error Message : $ErrorMessage" "7"
    MessageLog "Both" "Failed Item : $FailedItem" "7"
    $Script:Debug = $true
    $Script:NotifyUser = $False
    StatusEmail
    Break;break;break
}    

Try{
    Add-Content -Value "X" -Path $Script:Temp'\-ScriptFlag-.txt' -Force:$true -ErrorAction Stop  -Confirm:$false  
    $X = Get-Content -Path $Script:Temp'\-ScriptFlag-.txt' -ErrorAction Stop
    Remove-Item -Path $Script:Temp'\-ScriptFlag-.txt' -Force:$true -ErrorAction Stop -Confirm:$false
}Catch{
    $ErrorMessage = $_.Exception.Message
    $FailedItem = $_.Exception.ItemName
    $Script:DebugSubject = "Script failure during TEMP check."
    MessageLog "Both" "Script failure during TEMP check. " "7"
    MessageLog "Both" "Error Message : $ErrorMessage" "7"
    MessageLog "Both" "Failed Item : $FailedItem" "7"
    $Script:Debug = $true
    $Script:NotifyUser = $False
    StatusEmail
    Break;break;break
}

Try{
    $GroupMembers = Get-ADGroupMember -Identity $Script:VPNUsers | Sort Name
    MessageLog "Both" "HR" "2" #--[ Inserts a hard rule ]--
    Foreach ($Member in $GroupMembers){
        $Script:EventLogMsg = @() #New-Object System.Collections.ArrayList
        $Script:TargetUser = $Member.SamAccountName.ToUpper() 
        $Script:TargetFName = Get-AdUser -Identity $Script:TargetUser -Properties *
            
        MessageLog "Both" "-- VPN Certificate Inspection is executing for target user: $Script:TargetUser --" "5"
                
        #==[ This section is for MANUAL processing. It has been disabled for this version of the script but left in for reference ]==
        #
        #$Script:TargetUser = $env:username #--[ Sets the target user to the current session username variable ]--
        #$Script:EmailTo = $env:username #--[ Set the destination email to the same as above ]--
        #
        #If($Script:Force){ #--[ Adjustments for targeted execution ]--
        # $Script:TargetUser = Read-Host -Prompt "Enter Target User ID: (- DO NOT include a domain and extension - To abort leave the User ID blank -)"
        # If (($Script:TargetUser -eq "") -or($Script:TargetUser -eq $Null)){
        # MessageLog "Both" "NOTICE: VPN Certificate Script execution terminated due to a blank user ID..." "2"
        # Write-EventLog -LogName Application -Source VPN_PowerShell -EntryType Information -EventId 12345 -Message $Script:EventLogMsg
        # break
        # }
        # $Script:EmailAlternate = Read-Host -Prompt "Enter YOUR user ID. A copy of the email will be sent to you as a failsafe."
        # $Script:EmailTo = $Script:TargetUser
        #}
        #
        #If (($env:username -eq "testuser") -or ($env:username -eq $Script:DebugUser)){ #--[ Additional adjustments for debugging ]--
        # $Script:TargetUser = "testuser"
        # $Script:EmailTo = "user1"
        #}
        #==============================================================================================================================

        If ((!(Test-Path "c:\Windows\System32\certutil.exe")) -or (!(Test-Path "c:\Windows\Syswow64\certutil.exe"))){
            MessageLog "Both" "-- Certutil was not found on system ! --" "2"
            Write-EventLog -LogName Application -Source $Script:EventlogName -EntryType $Script:EventlogType -EventId $Script:EventlogID -Message ($Script:EventLogMsg | out-string)
            $Script:Debug = $true
            $Script:NotifyUser = $False
            StatusEmail
            Break;break;break
        }

        #--[ Detection ]----------------------------------------------------------------
        ForEach ($Pattern in $Script:BadPattern.Pattern){
            if ($Script:TargetUser -like $Pattern){
                $IllegalUser = $True
            }    
        }        
                
        if (!($IllegalUser)){
            If (Test-Path "$Script:CertStore\$Script:TargetUser.update"){         
                #--[ NON-STANDARD OPERATION: Force an email to user due to some out-of-cycle update ]--
                #--[ This section normally will never fire. Manually add an "user.update" file to trigger for a user. ]--
                #--[ This flag file will preempt ALL other detection. ]--
                MessageLog "Both" "-- Detected an UPDATE flag file. Removing update flag file and sending notification email --" "3" 
                Remove-item "$Script:CertStore\$Script:TargetUser.update" -ErrorAction stop -Force -Confirm:$False
    
                $Script:EmailMsg = $Script:TargetFName.givenName+',<br><br>This is an automated courtesy email from the '+$Script:CompanyName+' VPN server.<br><br>
                Please do NOT reply to it, this is an unmonitored email address.<br><br>
                We have made changes to the encryption protocols used on the VPN that require you to update the security<br>
                certificates installed on your PC so that they match the ones used for the '
+$Script:CompanyName+' computer systems.<br><br>
                Attached to this email you will find copies of the '
+$Script:CompanyName+' updated Public Key Infrastructure root certificates.<br><br>
                Please install the files as described in the included instruction document.<br><br>
                It is not necessary to re-install your personal certificate, it is being included for completeness.<br><br>
                Please contact Support at '
+$Script:SupportPhone+' if you have issues or questions.<br><br>
                This will be the only notice sent.<br><br>
                '
+$Script:CompanyName+' IT Network Department'
                $Script:NotifyUser = $True
                SendEmail
            }Else{   #--[ NORMAL OPERATION continues here... ]--
                If (Test-Path "$Script:CertStore\$Script:TargetUser.pfx"){
                    MessageLog "Both" "-- Existing certificate found --" "1"
                    $CertDump = certutil -p $Script:VPNPassword -dump ("$Script:CertStore\$Script:TargetUser.pfx").tostring()   
                    $CertLogDump = @()
                    $CertLogDump = certutil -p $Script:VPNPassword -dump ("$Script:CertStore\$Script:TargetUser.pfx").tostring()  
                    $CertTmp1 = ""
                    $CertTmp2 = ""
                                    
                    foreach ($LineIn in $CertLogDump){
                        $CertTmp1 += ($LineIn+"`n")
                        $CertTmp2 += "<font color="+$ScriptColor[16]+">$LineIn</font><br>"  
                    }
                    MessageLog "ConsoleOnly" $CertTmp1 "6"
                    MessageLog "DebugLogOnly" $CertTmp2 "6"
            
                    #--[ NOTE: Dumpfile parsing is unreliable and gives differing results on various machines. This routine finds the first date ]--
                    $Line3 = (((($CertDump -split "`r")[3]) -split " ")[2]) -as [datetime]
                    $Line4 = (((($CertDump -split "`r")[4]) -split " ")[2]) -as [datetime]
                    $Line5 = (((($CertDump -split "`r")[5]) -split " ")[2]) -as [datetime]
                    $Line6 = (((($CertDump -split "`r")[6]) -split " ")[2]) -as [datetime]
                    If ($Line3){ 
                        $CreationDate = ((($CertDump -split "`r")[3]) -split " ")[2]    #command formatted to work on PowerShell v2
                        $ExpireDate = ((($CertDump -split "`r")[4]) -split " ")[2]      #command formatted to work on PowerShell v2
                    }elseIf ($Line4){
                        $CreationDate = ((($CertDump -split "`r")[4]) -split " ")[2]    #command formatted to work on PowerShell v3
                        $ExpireDate = ((($CertDump -split "`r")[5]) -split " ")[2]      #command formatted to work on PowerShell v3
                    }elseif ($Line5){
                        $CreationDate = ((($CertDump -split "`r")[5]) -split " ")[2]    #command formatted to work on PowerShell v4
                        $ExpireDate = ((($CertDump -split "`r")[6]) -split " ")[2]      #command formatted to work on PowerShell v4
                    }elseif ($Line6){
                        $CreationDate = ((($CertDump -split "`r")[6]) -split " ")[2]    #command formatted to work on PowerShell v5
                        $ExpireDate = ((($CertDump -split "`r")[7]) -split " ")[2]      #command formatted to work on PowerShell v5
                    }            
                    $CreationDate = get-date $CreationDate -f "MM-dd-yyyy"
            
                    MessageLog "Both" ($MsgText = '-- Cert Creation Date : '+$CreationDate) "3"   
                    $ExpireDate = get-date $ExpireDate -f "MM-dd-yyyy"
                    MessageLog "Both" ($MsgText = '-- Cert Expire Date : '+$ExpireDate) "3" 
                    $Today = Get-Date -f "MM-dd-yyyy"
                    MessageLog "Both" ($MsgText = '-- Today is : '+$Today) "3" 
                    $Remaining = NEW-TIMESPAN –Start $Today –End $ExpireDate
                    $Remaining = [math]::abs($Remaining.Days)
                    MessageLog "Both" "-- $Remaining days left until expiration --" "1"
            
                    #--[ UNCOMMENT AND CHANGE THESE TO TEST EXPIRATION TRIGGERS DURING DEBUGGING ]-------------
                        <#If ($Script:TargetUser -eq $Script:DebugUser){
                            $Remaining = 25
                            MessageLog "Both" ("-- TESTING: Days until expire adjusted to : "+$Remaining) "2"
                            }#>

                    #--[ UNCOMMENT AND CHANGE THESE TO TEST EXPIRATION TRIGGERS DURING DEBUGGING ]-------------
            
                    #--[ Determine what to do depending on number of remaining days ]----------------
                    If ($Remaining -le 15){  #--[ 15 days until certificate expires. Generate a new one and send. ]--
                        MessageLog "Both" "-- Less than 15 days is remaining. Clearing flag file. Generating new cert. Archiving old cert." "3" 
                        If (Test-Path "$Script:CertStore\$Script:TargetUser.flag"){remove-item "$Script:CertStore\$Script:TargetUser.flag" -Force -Confirm:$False }
                        [string]$Script:BackupName = $Script:TargetUser+"_$ExpireDate.pfx.old"
                        If (Test-Path "$Script:CertStore\$Script:TargetUser.pfx"){
                            If ($Script:Console){
                                Write-Host "Renaming and Relocating: " -ForegroundColor "3" -NoNewline 
                                Write-Host $Script:CertStore"\"$Script:TargetUser.pfx -ForegroundColor "5" -NoNewline
                                Write-Host " to: " -ForegroundColor "3" -NoNewline 
                                Write-Host $Script:CertStore"\Backups\"$Script:BackupName -ForegroundColor  "6" 
                            }
                            try{
                                If (Test-Path "$Script:CertStore\$Script:TargetUser.pfx"){
                                    rename-item "$Script:CertStore\$Script:TargetUser.pfx" $Script:CertStore"\"$Script:BackupName -ErrorAction stop -Force -Confirm:$False
                                    move-Item $Script:CertStore"\"$Script:BackupName -Destination $Script:CertStore"\backups\"$Script:BackupName -ErrorAction stop -Force -Confirm:$False
                                }
                            }catch{
                                MessageLog "Both" $_.Exception.Message "2" 
                                MessageLog "Both" $_.Exception.ItemName "2" 
                            }
                        }
                        GenerateCert
                    }ElseIf ($Remaining -le 30){                                  #--[ NOTIFY USER. Certificate due to expire. ]--
                        If (!(Test-Path "$Script:CertStore\$Script:TargetUser.flag")){
                            MessageLog "Both" "-- Less than 30 days is remaining. Writing flag file. Sending warning email --" "3" 
                            Add-Content -Path "$Script:CertStore\$Script:TargetUser.flag" -Value "30 day notice"
            
                            $Script:EmailMsg = $Script:TargetFName.givenName+',<br><br>This is an automated courtesy email from the '+$Script:CompanyName+' VPN server.<br><br>
                            We store a copy of your VPN certificate as a backup. This backup is automatically scanned<br>
                            every two to three days and the expiration date is checked. <br><br>
                            '
+$Script:TargetUser+' Certificate Statistics:<br>
                            Created on: '
+$CreationDate+'<br>
                            <strong>Expires on: '
+$ExpireDate+'</strong><br><br>
                            Your '
+$Script:CompanyName+' VPN certificate is due to expire within 30 days. There is <br>
                            nothing you need to do except be aware of this fact. Within approximately 15 days you will<br>
                            be emailed a new certificate with instructions on how to install it. Please watch for it.<br><br>
                            If after 3 weeks you have not received it, please contact support at '
+$Script:SupportPhone+'.<br><br>
                            If your certificate expires you will be unable to use the company VPN to connect to the network.<br><br>
                            This will be the only notice sent. Please reply to this email should you have any questions.<br><br>
                            '
+$Script:CompanyName+' IT Network Department'
                            $Script:NotifyUser = $true
                            SendEmail                                          #--[ This is just a notification. ]--
                        }Else{
                            MessageLog "Both" "-- Less than 30 days is remaining. Flag file exists. Exiting --" "3" 
                        }
                    }Else{                                                     #--[ Greater than 30 days remaining. Cleanup any old lingering flag files. ]--
                        If (Test-Path "$Script:CertStore\$Script:TargetUser.flag"){
                            Try{    
                                MessageLog "Both" "-- Removing stale flag file" "3" 
                                Remove-Item "$Script:CertStore\$Script:TargetUser.flag" -Force -Confirm:$false     
                            }Catch{
                                $ErrorMessage = $_.Exception.Message
                                $FailedItem = $_.Exception.ItemName
                            }
                            MessageLog "Both" $ErrorMessage "2" 
                            MessageLog "Both" $FailedItem "2" 
                        }
                        MessageLog "Both" "-- Greater than 30 days until certificate expiration. NOTHING TO DO --" "1"      
                    }
      
                }Else{                                                  #--[ No existing certificate found in archive. Generate a new one. ]--
                    MessageLog "Both" "-- No VPN cert was found. Generating a new one --" "3"
                    $Global:EmailBody = $Script:EmailMsg
                    GenerateCert
                }
            }
        }Else{
            MessageLog "Both" "-- Detected an invalid user account. Admin and Service accounts are not permitted to use VPN. Bypassing... --" "2"
        }

        If($Script:Installed){
            MessageLog "Both" ('-- Removing any '+$Script:TargetUser+' certificates from local store --') "3" 
            If ($Script:TargetUser -ne $Env:Username){
                dir Cert:\CurrentUser -Recurse | ? subject -match $Script:TargetUser | Remove-Item #-WhatIf
            }    
            $Script:Installed = $False
        }

        MessageLog "Both" "`n-- VPN Certificate Processing Completed --" "5" 
        MessageLog "Both" "HR" "2" #--[ Inserts a hard rule ]--
        $IllegalUser = $False
        $Script:Attach = $false    
        $Script:NotifyUser = $False
        Write-EventLog -LogName Application -Source $Script:EventlogName -EntryType $Script:EventlogType -EventId $Script:EventlogID -Message ($Script:EventLogMsg | out-string)
    }
}Catch{
    $ErrorMessage = $_.Exception.Message
    $FailedItem = $_.Exception.ItemName
    MessageLog "Both" $ErrorMessage "2" 
    MessageLog "Both" $FailedItem "2"
}

If ($Script:Console){
    Write-host "-- Debug Setting : $Script:Debug" -Foreground Yellow
    Write-host "-- Status Setting : $Script:Status" -Foreground Yellow
    Write-host "-- Attach Setting : $Script:Attach" -Foreground Yellow
}

MessageLog "Both" "`n-- VPN Script Completed --`n" "5" 
StatusEmail

[System.GC]::Collect()

<#--[ Configuration file example. The file must be named same as script (autocert.xml) and be located with the script ]--
 
<!-- Settings & Configuration File -->
<Settings>
    <General>
        <CompanyName>My Company</CompanyName>
        <CompanyInitials>IBM</CompanyInitials>
        <SupportPhone></SupportPhone>
        <ScriptName>AutoCert.ps1</ScriptName>
        <VPNGroup>vpnusers</VPNGroup> <!-- AD group for VPN access -->
        <EventlogName>VPN_PowerShell</EventlogName>
        <EventlogID>12345</EventlogID>
        <EventlogType>Information</EventlogType>
        <CertStore>\\mydomain\VPN$\VPN</CertStore> <!-- Network share where certs are stored -->
        <TemplateName>VPNUserTemplate</TemplateName> <!-- cert template name on your CA -->
        <CaName>PKICA1</CaName> <!-- name of your CA -->
        <BadPattern>
            <Pattern>*admin*</Pattern> <!-- AD accounts to exclude -->
            <Pattern>*service*</Pattern>
        </BadPattern>
    </General>
    <Email>
        <From>itnetwork</From>
        <To>This_field_not_used</To>
        <Subject>My Company VPN</Subject>
        <DebugSubject>VPN Certificate Status and Debugging Information</DebugSubject>
        <Domain>MyCompany.com</Domain>
        <HTML>$true</HTML>
        <SmtpServer>10.10.50.51</SmtpServer>
        <DebugUser>
            <User>User1</User>
        </DebugUser>
        <StatusUser>
            <User>user1</User>
            <User>user2</User>
            <User>user3</User>
            <User>user4</User>
        </StatusUser>
        <Attachments>
            <Attach1>Root.p7b</Attach1>
            <Attach2>Issuing.p7b</Attach2>
            <Attach3>SSLVPN-Install.docx</Attach3>
        </Attachments>
    </Email>
    <Credentials>
        <UserName></UserName>
        <Password></Password>
        <VPNPassword>vpnpwd</VPNPassword>
    </Credentials>
</Settings>
 
#>