AutoCert.ps1
Param ( [switch]$Console = $false, [switch]$Debug = $false, [switch]$Status = $False, [Switch]$UseIPC = $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. : v6.00 - 05-01-18 - Major rewrite to include W2012 certifcate commandlets and fix folder permission issues. : =======================================================================================#> <#PSScriptInfo .VERSION 6.00 .GUID 39d43bd3-0d21-4060-b4ce-eafc88302f57 .AUTHOR Kenneth C. Mazie (kcmjr AT kcmjr.com) .DESCRIPTION Automatically creates VPN certificates based on expiration date of exisiting cert. #> Clear-Host $ErrorActionPreference = "silentlyContinue" If ($Console){$Script:Console = $true} If ($Debug){$Script:Debug = $true} If ($Status){$Script:Status = $true} If ($UseIPC){$Script:UseIPC = $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:Key = $Script:Configuration.Settings.Credentials.Key $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 $Script:BA = [System.Convert]::FromBase64String($Script:Key) $Script:SC = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Script:Username, ($Script:Password | ConvertTo-SecureString -Key $Script:BA) $Script:SP = $SC.GetNetworkCredential().Password } #------------------------------------------------------------------------------- [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") #--[ Assembly required for balloon tips ]-- Try{ New-EventLog -LogName Application -Source $Script:EventlogName -ErrorAction SilentlyContinue #--[ Register new eventlog type ]-- }Catch{ } #==[ 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 $Email.To.Add("$Script:DebugUser@$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) ]-- $Email.Bcc.Add("$Script:DebugUser@$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 $StatusEmail.To.Add("$Script:DebugUser@$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" 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 PurgeLocalStore { #--[ Purge local store ]------------------------------------------------------------- MessageLog "Both" "-- Purging any old certificates from the local store --" "3" Try{ Get-ChildItem cert:\currentuser\my | ?{ $_.Subject -eq "CN=$Script:TargetUser"} | %{ $xcert = $_ $xstore = New-Object System.Security.Cryptography.X509Certificates.X509Store "my", "currentuser" $xstore.Open("ReadWrite") $xstore.Remove($xcert) $xstore.Close() Sleep -Seconds 1 } }catch{ MessageLog "Both" $_.Exception.Message "2" MessageLog "Both" $_.Exception.ItemName "2" } } Function PurgeTemp { #--[ Clean out temp folder ]----------------------------------------------------------- MessageLog "Both" "-- Purging work files from local temp folder as needed --" "3" If (Test-Path "$Script:TempDir\*.inf"){Remove-Item "$Script:TempDir\*.inf" -ErrorAction silentlycontinue -Force:$true} If (Test-Path "$Script:TempDir\*.req"){Remove-Item "$Script:TempDir\*.req" -ErrorAction silentlycontinue -Force:$true} If (Test-Path "$Script:TempDir\*.cer"){Remove-Item "$Script:TempDir\*.cer" -ErrorAction silentlycontinue -Force:$true} If (Test-Path "$Script:TempDir\*.p7b"){Remove-Item "$Script:TempDir\*.p7b" -ErrorAction silentlycontinue -Force:$true} If (Test-Path "$Script:TempDir\*.pfx"){Remove-Item "$Script:TempDir\*.pfx" -ErrorAction silentlycontinue -Force:$true} If (Test-Path "$Script:TempDir\*.rsp"){Remove-Item "$Script:TempDir\*.rsp" -ErrorAction silentlycontinue -Force:$true} If (Test-Path "$Script:TempDir\*.docx"){Remove-Item "$Script:TempDir\*.docx" -ErrorAction silentlycontinue -Force:$true} } Function GenerateNewRequest { #--[ Generate new request file ]------------------------------------------------ MessageLog "Both" "-- Generating new certificate request file --" "3" add-content $Script:TempDir\usercert.inf "[NewRequest]`r Subject = `"CN=$Script:TargetUser`"`r Exportable = TRUE`r RequestType = CMC`r [RequestAttributes]`r CertificateTemplate = `"$Script:TemplateName`"`r SAN = `"Email=$Email`"" #--[ Compile the request file ]-------------------------------------------- MessageLog "Both" "-- Compiling certificate request --" "3" Try{ $Result = C:\Windows\system32\certreq.exe -new $Script:TempDir\usercert.inf $Script:TempDir\usercert.req }Catch{ $Script:DebugSubject = $ErrorMessage MessageLog "Both" "Build Cert : $_.Exception.Message" "7" StatusEmail break;break;break } } Function SubmitNewRequest { #--[ Send request to authority ]-------------------------------------------- MessageLog "Both" "-- Submitting certificate request to CA --" "3" Try{ #Get-Certificate -Template $Script:TemplateName -DnsName bankofstockton.com -Url http://pki/certsrv/ -Credential $SC -CertStoreLocation cert:\CurrentUser\My $Result = C:\Windows\system32\certreq.exe -submit -config "$Script:CertAuthName" $Script:TempDir\usercert.req $Script:TempDir\usercert.cer }Catch{ $Script:DebugSubject = $ErrorMessage MessageLog "Both" "Request Cert : $_.Exception.Message" "7" StatusEmail break;break;break } $ID = $Result.Split(" ")[1] MessageLog "Both" "-- Request ID --$ID" "3" } Function InstallCertificate { #--[ Install certificate on local machine ]--------------------------------- MessageLog "Both" "-- Importing certificate to local store --" "3" Try{ $result = Import-Certificate -FilePath $Script:TempDir\usercert.cer -CertStoreLocation Cert:\CurrentUser\My #C:\Windows\system32\certreq.exe -accept $Script:TempDir\usercert.cer $Script:Installed = $true }Catch{ $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName $Script:DebugSubject = $ErrorMessage MessageLog "Both" "Install cert on local : $ErrorMessage" "7" StatusEmail } } Function ExportCertificate { #--[ Export certificate from local store ]---------------------------------------- MessageLog "Both" "-- Exporting certificate to work folder --" "3" 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=$Script:CertAuthName*"} | Foreach-Object {[IO.file]::WriteAllBytes("$Script\TempDir\$Script:TargetUser.pfx", ($_.Export('PFX', $Script:VPNPassword)))} #Find VPN cert and exclude internal User cert $SecPwd = (ConvertTo-SecureString -String $Script:VPNPassword -Force -AsPlainText) $Certificate = Get-ChildItem -Path Cert:\currentuser\My | Where-Object {$_.Subject -like "*$Script:TargetUser*"} $CertificateExport = $Script:CertStore + '\' + $Script:TargetUser + '.pfx' $Result = Export-PfxCertificate -Cert $Certificate.PSPath -FilePath $CertificateExport -Password $SecPwd }Catch{ $Script:DebugSubject = $ErrorMessage MessageLog "Both" "Export Cert : $_.Exception.Message" "7" StatusEmail break;break;break } MessageLog "Both" "-- Detecting exported PFX certificate file --" "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" StatusEmail break;break;break } } } Function GenerateCert { $ErrorActionPreference = "stop" $Script:CertAuthName = $Script:CaName + "\"+$Script:CaName PurgeLocalStore PurgeTemp GenerateNewRequest SubmitNewRequest InstallCertificate ExportCertificate MessageLog "Both" "-- Preparing certificate files for mailing --" "3" #--[ 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 MessageLog "Both" "-- Emailing user --" "3" 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 ===============`n" "5" MessageLog "Both" "Script executed from server : $Env:ComputerName on $Script:DateTime" "7" MessageLog "Both" "Certificate store : $Script:CertStore" "7" #--[ Determine OS version ]------------------------------------ $OSver = (Get-WmiObject Win32_OperatingSystem).Version [int]$OS = $OSver -replace ".{6}$" If ($OS -lt [int]6.2) { MessageLog "Both" "`n-- This script requires Windows 8.1 or Server 2012 due to certificate commandlets in use. --" "2" Exit }Else{ MessageLog "Both" "Windows OS version verified : $OSver" "7" } #--[ OPTIONAL: Make an IPC connection to the CertStore ]-- If ($Script:UseIPC){ net use \\$Script:CertStore\ipc$ /user:$Script:UN $SP | Out-Null #--[ Use if connection issues occur ]-- } $Email = "$Script:TargetUser@$Script:EmailDomain" Try{ #--[ Test for the ability to write to the cert store ]-- 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{ #--[ Test for the ability to write to the temp work folder ]-- Add-Content -Value "X" -Path $Script:TempDir'\-ScriptFlag-.txt' -Force:$true -ErrorAction Stop -Confirm:$false $X = Get-Content -Path $Script:TempDir'\-ScriptFlag-.txt' -ErrorAction Stop Remove-Item -Path $Script:TempDir'\-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{ If ($Script:Debug){ $GroupMembers = Get-ADUser -Identity $Script:DebugUser -Properties * -Credential $Script:SC }Else{ $GroupMembers = Get-ADGroupMember -Identity $Script:VPNUsers -Credential $Script:SC | 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 * -Credential $Script:SC 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" #} #============================================================================================================================== #--[ This section may be omitted if exclusively using the new PS cert commandlets ]-- 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 } #--[ Detect illegal users ]---------------------------------------------------------------- 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 a "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 = 14 # 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) PurgeTemp #--[ Clean up files left in temp folder ]-------------------------- } }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 } #--[ Disconnect from CertStore ]----------------------- If ($Script:UseIPC){ net use \\$Script:TargetSystem\ipc$ /d | Out-Null } 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> #> |