From a1f890e09067f3d92db5665f84358d87f34fc13e Mon Sep 17 00:00:00 2001 From: Violet Date: Sun, 12 Nov 2023 11:37:06 +0000 Subject: [PATCH 01/34] Updated BitLocker category This commit includes changes to the BitLocker category, specifically, the removal of the policy "Full disk encryption for fixed data drives". This policy would prevent OneDrive personal vault from working properly. More info available in the PR and Release change logs. --- .../Resources/Registry resources.csv | 5 ++--- Payload/Security-Baselines-X.zip | Bin 12916 -> 12910 bytes 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Resources/Registry resources.csv b/Harden-Windows-Security Module/Main files/Resources/Registry resources.csv index 1197a83ed..7d4e2acc0 100644 --- a/Harden-Windows-Security Module/Main files/Resources/Registry resources.csv +++ b/Harden-Windows-Security Module/Main files/Resources/Registry resources.csv @@ -47,9 +47,8 @@ Origin,Category,Key,Name,FriendlyName,Type,Value "Group Policy","Bitlocker",HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE,EncryptionMethodWithXtsFdv,Correct Encryption method for fixed data drives,DWORD,7 "Group Policy","Bitlocker",HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE,EncryptionMethodWithXtsRdv,Correct Encryption method for removable drives,DWORD,7 "Group Policy","Bitlocker",HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE,UseEnhancedPin,Use Enhanced PIN,DWORD,1 -"Group Policy","Bitlocker",HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE,FDVEncryptionType,Full disk encryption for fixed data drives,DWORD,1 -"Group Policy","Bitlocker",HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE,OSEncryptionType,Full disk encryption OS drive,DWORD,1 -"Group Policy","Bitlocker",HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE,RDVEncryptionType,Full disk encryption removable drives,DWORD,1 +"Group Policy","Bitlocker",HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE,OSEncryptionType,Full disk encryption for OS drive,DWORD,1 +"Group Policy","Bitlocker",HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE,RDVEncryptionType,Full disk encryption for removable drives,DWORD,1 "Group Policy","Bitlocker",HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE,RDVDiscoveryVolumeType,Prevent access to BitLocker-protected removable data drives from earlier versions of Windows,SZ, "Group Policy","Bitlocker",HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE,RDVNoBitLockerToGoReader,Do not install BitLocker To Go Reader on FAT formatted removable drives,DWORD,0 "Group Policy","Bitlocker",HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE,MinimumPIN,Configure minimum PIN length for startup,DWORD,10 diff --git a/Payload/Security-Baselines-X.zip b/Payload/Security-Baselines-X.zip index 8980d354ee4e3af9efe5986ebe1d6ddc6f5435b1..d6d9188a275c5f8f2c70eda04f699251332efb86 100644 GIT binary patch delta 861 zcmey8@-AhAJTqTcbWV62_uStr85tO!actIQ7G$iKIy3Re3e{P+9Q@wfZLDbDQr;zd z|AXG&VvUCvrr8+xJl*nV(}|N6Dvwnqv!-!{Ykt1?J;Qb1KW0_&_lH)w{+t%O-+*tm zG&pD|4V4ivVIOn+==K>e3=U`Utvwg+( z>5FKl`HJZ)o@#o({QGjV_>C=3C6?b=KdC_PaTV`Ewfvoj++USm6J+rZTbAsTBrBiZF9b{zLU5uyt*z{*{gTzUjA(Y4nX3PEsLpwZL%YW_~g}w;*)==$uY@u0J+MO&uQ|2Y?|z&F3qILIe8wZ z`Q!!aT1+({rt@Te4K1d0E+A8G@<~+*d34(hP;8qvd4fhP;}alhJlTSe2WT|cWOGdg zrd@oKCu&H7E$-BmV-gYobA1IsDj=5M)s$vBFF5(5pp6zIlL!Mm^iaGOiDH?m&}2oC zh{^MXSSRn*(qNhc;o56UgMAaLEzQ&^3{(*{`HHp{(|-^%ZL*&>*W>~n1;*EtC+Z|I zeHH~;s5AMHrr2b2T?NJqlOuFBFv2qm#Zr06$&Qk7lmF>zFii(BVpyK^KDpXJ ti|GrDo5Sch*}^cGQF(HYVF;tc3pQLc}%%eZoGcqtFac$H;kd+BRpu#|L(!?ft@YHqKq_5HHB) zB~yKc_sL7`OL>6-`hll|Fa5rpuD>Dkw4vY5c-4n7$G%EA`TkQDu?t$ixlf^DlaXw) zP_kCN!NK#_H$Pn#`Jiv3?Z#KrZ5LgMu;e^>W*zvB`5(?Lmqr-&2)l zVq~5CTQ!a`aB`Gd7GvGyqiViPsvMK8Ig}@#SLIOvstO0Y2uhg3IRZeIKIh~aoaU2f zscSJcftb#d^K`i;3u`Da<#GYJa+9^yCBPQtX-G58;hn^1GdE|5N$vbP+6D zW6JbX6wH*?HDkIaKKYHfG|;aQ+iG>C8C54M>cvj}rmMv`7s#5#^i^u|LIah_`qCi3 za4CUf6eS=7QEZo&0V|J`F`S&Luffy@ Date: Sun, 12 Nov 2023 11:41:51 +0000 Subject: [PATCH 02/34] Module version increase --- .../Main files/Harden-Windows-Security-Module.psd1 | 2 +- Harden-Windows-Security Module/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security-Module.psd1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security-Module.psd1 index 89714f6d3..c75baca37 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security-Module.psd1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security-Module.psd1 @@ -12,7 +12,7 @@ # RootModule = '' # Version number of this module. - ModuleVersion = '0.2.4' + ModuleVersion = '0.2.5' # Supported PSEditions CompatiblePSEditions = @('Core') diff --git a/Harden-Windows-Security Module/version.txt b/Harden-Windows-Security Module/version.txt index 72f9fa820..28af839c0 100644 --- a/Harden-Windows-Security Module/version.txt +++ b/Harden-Windows-Security Module/version.txt @@ -1 +1 @@ -0.2.4 \ No newline at end of file +0.2.5 \ No newline at end of file From 2e6dd858c49fc3700118ee08ae844456b6933bbc Mon Sep 17 00:00:00 2001 From: Violet Date: Sun, 12 Nov 2023 11:54:43 +0000 Subject: [PATCH 03/34] Changed total number of items to verify Since a policy was removed in the previous commit, now adjusting the total number of policies to be verified as well --- .../Main files/Confirm-SystemCompliance.psm1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 index 4419de120..298f0c209 100644 --- a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 +++ b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 @@ -66,7 +66,7 @@ function Confirm-SystemCompliance { Write-Progress -Activity 'Gathering Security Policy Information' -Status 'Processing...' -PercentComplete 15 # Total number of Compliant values not equal to N/A - [int]$global:TotalNumberOfTrueCompliantValues = 231 + [int]$global:TotalNumberOfTrueCompliantValues = 230 # Get the security group policies Secedit /export /cfg .\security_policy.inf | Out-Null @@ -1766,7 +1766,7 @@ function Confirm-SystemCompliance { # Counting the number of $True Compliant values in the Final Output Object [int]$TotalTrueCompliantValuesInOutPut = ($FinalMegaObject.'Microsoft Defender' | Where-Object { $_.Compliant -eq $True }).Count + # 49 - 4x(N/A) = 45 [int]($FinalMegaObject.ASR | Where-Object { $_.Compliant -eq $True }).Count + # 17 - [int]($FinalMegaObject.Bitlocker | Where-Object { $_.Compliant -eq $True }).Count + # 23 + Number of Non-OS drives which are dynamicly increased + [int]($FinalMegaObject.Bitlocker | Where-Object { $_.Compliant -eq $True }).Count + # 22 + Number of Non-OS drives which are dynamicly increased [int]($FinalMegaObject.TLS | Where-Object { $_.Compliant -eq $True }).Count + # 21 [int]($FinalMegaObject.LockScreen | Where-Object { $_.Compliant -eq $True }).Count + # 14 [int]($FinalMegaObject.UAC | Where-Object { $_.Compliant -eq $True }).Count + # 4 From 7ce9daef1ac032691d6346d35e78dd70a710d050 Mon Sep 17 00:00:00 2001 From: Violet Date: Sun, 12 Nov 2023 12:15:16 +0000 Subject: [PATCH 04/34] Using quiet mod for LGPO.exe on unprotect cmdlet --- .../Main files/Unprotect-WindowsSecurity.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Harden-Windows-Security Module/Main files/Unprotect-WindowsSecurity.psm1 b/Harden-Windows-Security Module/Main files/Unprotect-WindowsSecurity.psm1 index 32d87dcf0..f5db62c7d 100644 --- a/Harden-Windows-Security Module/Main files/Unprotect-WindowsSecurity.psm1 +++ b/Harden-Windows-Security Module/Main files/Unprotect-WindowsSecurity.psm1 @@ -125,7 +125,7 @@ Function Unprotect-WindowsSecurity { # unzip the LGPO file Expand-Archive -Path .\LGPO.zip -DestinationPath .\ -Force - .\'LGPO_30\LGPO.exe' /s "$psscriptroot\Resources\Default Security Policy.inf" + .\'LGPO_30\LGPO.exe' /q /s "$psscriptroot\Resources\Default Security Policy.inf" # Enable LMHOSTS lookup protocol on all network adapters again Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\NetBT\Parameters' -Name 'EnableLMHOSTS' -Value '1' -Type DWord From 4a3552a7cb04070ef7f09a538dadaf6059aad560 Mon Sep 17 00:00:00 2001 From: Violet Date: Sun, 12 Nov 2023 12:50:43 +0000 Subject: [PATCH 05/34] Improved the message text --- .../Main files/Unprotect-WindowsSecurity.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Harden-Windows-Security Module/Main files/Unprotect-WindowsSecurity.psm1 b/Harden-Windows-Security Module/Main files/Unprotect-WindowsSecurity.psm1 index f5db62c7d..9cc5b36f6 100644 --- a/Harden-Windows-Security Module/Main files/Unprotect-WindowsSecurity.psm1 +++ b/Harden-Windows-Security Module/Main files/Unprotect-WindowsSecurity.psm1 @@ -29,7 +29,7 @@ Function Unprotect-WindowsSecurity { if (!$OnlyProcessMitigations) { &$WriteOrange "`r`n" &$WriteOrange "###############################################################################################`r`n" - &$WriteMintGreen "## This Will Remove the Hardening Measures Applied by Protect-WindowsSecurity Cmdlet ##`r`n" + &$WriteMintGreen "## This Will Remove the Hardening Measures Applied by the Protect-WindowsSecurity Cmdlet ##`r`n" &$WriteOrange "###############################################################################################`r`n" # Give user a chance to exit if they accidentally ran this From ba30e2b20d0018ac5f44d7d0801a17d98c45a210 Mon Sep 17 00:00:00 2001 From: Violet Date: Sun, 12 Nov 2023 13:17:36 +0000 Subject: [PATCH 06/34] WinVerifyTrust Signature Validation uses Registry now The latest Microsoft Security Baselines 23H2 mentions to have added support for WinVerifyTrust Signature Validation policy (aka certificate padding) but they haven't implemented it properly. More info in the comments section of this Microsoft Tech Community article: https://techcommunity.microsoft.com/t5/microsoft-security-baselines/windows-11-version-23h2-security-baseline/ba-p/3967618 This is why this commit uses registry keys to apply the Certificate padding check until that problem is officially solved. --- Payload/Registry.csv | 2 ++ Payload/Security-Baselines-X.zip | Bin 12910 -> 12851 bytes README.md | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Payload/Registry.csv b/Payload/Registry.csv index 399abac15..3a0d8282d 100644 --- a/Payload/Registry.csv +++ b/Payload/Registry.csv @@ -24,6 +24,8 @@ Miscellaneous,HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\ClientStateMedium\ Miscellaneous,HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\ClientStateMedium\{0D50BFEC-CD6A-4F9A-964C-C7416E3ACB10},allowautoupdatesmetered,1,DWORD,AddOrModify,Set Microsoft Edge (Dev) to update over Metered connections Miscellaneous,HKLM:\SYSTEM\ControlSet001\Services\W32Time\TimeProviders\NtpClient,SpecialPollInterval,345600,DWORD,AddOrModify,Change Windows time sync interval from every 7 days to every 4 days (= every 345600 seconds) Miscellaneous,HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters,EncryptData,1,DWORD,AddOrModify,Enable SMB Encryption +Miscellaneous,HKLM:\Software\Microsoft\Cryptography\Wintrust\Config,EnableCertPaddingCheck,1,String,AddOrModify,WinVerifyTrust Signature Validation +Miscellaneous,HKLM:\Software\Wow6432Node\Microsoft\Cryptography\Wintrust\Config,EnableCertPaddingCheck,1,String,AddOrModify,WinVerifyTrust Signature Validation Edge,HKLM:\SOFTWARE\Policies\Microsoft\Edge\Recommended,BlockThirdPartyCookies,1,DWORD,AddOrModify,Recommends to block 3rd party cookies Edge,HKLM:\SOFTWARE\Policies\Microsoft\Edge,DnsOverHttpsMode,automatic,String,AddOrModify,Sets Edge to use system's DNS over HTTPS. This makes MDAG to work properly too Edge,HKLM:\SOFTWARE\Policies\Microsoft\Edge,DnsOverHttpsTemplates,Any,String,Delete,Removes Any DNS over HTTPS set in Edge browser by registry because it prevents MDAG from working properly. See the Readme diff --git a/Payload/Security-Baselines-X.zip b/Payload/Security-Baselines-X.zip index d6d9188a275c5f8f2c70eda04f699251332efb86..b09c201d40c40e0a134fdba4b70ce37e69fed6a5 100644 GIT binary patch delta 817 zcmaEtvN>e~D=QOk`et@k9TpI6%9#zO7x7jx3BJ$B30Jphu5x5#V0gvCz@R*NBd^rt zJ(@iASA%{14>^d)2=o8r`QNS{WH}?mdBw$M(`DPdGlQa>#5w-os|>!&yIr>*54v;t+JXnQ-q~Fo*;O;@o&WSb z@A}qN;aqZPvzWK*kw3z3Dyr^@?dg?>75E%d;Fy1UYW?ZW%9}T)9jV%;{jX?KWQ+2y z2a?{shlP*X7~2S!Z;`&8`5|A_t}fznz{B!YeM>*b-uB2?q!%&e>w!;kLhtXD$h!aK zpW68;B;wSi1%DRr;*adJ{B`%%hp-Iy`t5DScOFc;rn!4sxBVUe+g1IRx!zS48?S7t z-75a@wq;G_M5VnU{#Rd3o$jBp^}2XqZT?)tXv+)JHn>}E-FhlzYSquyxixQ(zhj@u zx7+3#OP0~mKT)s0p8sCFz4lHpRZRn{b5&OZ)43XAjC`oEHF=_@vjT5=PB_>K zC}9ET2mo1A1SdNRSxwf{;sKgD*+xr-=@m#McJg*D4aUUD541`!42VE6z)u*cJYw=0 zZ5^f~5N42$1=9u*ps3E|9Xh5=J4C@uXsp6C0h)Yk-(*;{rqbtMoL}Kzn1C`0~ zl2(&{=xQ*=OjgvJ#I!{UET}KdJ$Zv6ml7y&P`wa{;(~k9VC9iAhLcnEHJE&5pj`dQ zNxBkHUokpOUSJS2xzs?5aVL;9k%>)ya;&_@04} delta 860 zcmdm-@-AfqD=X8}sLkxGIxHaClrtMlFXF9Y5`0^n7VaV7!dt`0z~INmz@R*NBd^qC zYgL~5t5Y2P4><_5&hKQeZ`<$KEAG!yZQ$q}rOck~lk1(ya_s`s!~4IVi7r{n+2|t7 zqj`7k+_^u~zUBU|n>ClQ!j++2(Mv}#u}wr#ZldzD8w(@Wx4tOacS!s=pX71DgrA<-QItid2zMBcm1LHZ_j4PUX*(r{O|eJ`s5nx z8Jm(kE#fa_sT5bu*e|$Gquf#cG55&_CsG27*3GCt(6y#bMEIQ5o!pkC9=}eBp57c_ zAGB%8>kR&yB)847VNYA$&&vqfV{SD;CNO#ShY72yAKlqhx@ed&j+0aaDc*w>LA32m=T_ zjhehbFa#8H-vpBwL3EVx6mX1wkods_;=huHi2KO~ae(-XRMWufxYX6aw2_7wBOhu^ zPY%;`1}dFATT_PVyx`Q61u6Z>etKL=Aayz6;2?n#fpCrh zkR>TINm6C9oQ&aQEqx894k%B5@*Wu;ggqcX)yjetb{UvYwldIS`U2(UFgi~DZV=3* stT Enables **WinVerifyTrust Signature Validation**, [a security feature related to WinVerifyTrust function that handles Windows Authenticode signature verification for portable executable (PE) files.](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2013-3900) +- Rotating pink checkmark denoting registry or cmdlet Enables **WinVerifyTrust Signature Validation**, [a security feature related to WinVerifyTrust function that handles Windows Authenticode signature verification for portable executable (PE) files.](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2013-3900) - Blue Check mark denoting Group Policy Requires Additional Confirmation [Blocking Untrusted Fonts](https://learn.microsoft.com/en-us/windows/security/threat-protection/block-untrusted-fonts-in-enterprise) Rotating green checkmark denoting CSP [CSP](https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-admx-grouppolicy#fontmitigation) From a77d047d2459c50493bc6dc325bf34c7e022d520 Mon Sep 17 00:00:00 2001 From: Violet Date: Mon, 13 Nov 2023 09:26:37 +0000 Subject: [PATCH 07/34] Improved Unprotect-WindowsSecurity cmdlet For restoration of security group policies, the cmdlet now only restores settings that were changed by Protect-WindowsSecurity excluding the ones applied by the Microsoft Security Baselines. This allows for a more surgical and careful restoration of the settings and will prevent any accidental changes to the settings that would otherwise need to be constantly kept in check to make sure they are correct. --- .../Resources/Default Security Policy.inf | Bin 13666 -> 2390 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Resources/Default Security Policy.inf b/Harden-Windows-Security Module/Main files/Resources/Default Security Policy.inf index a8f1c09e6eb46b09d83bb4643933ad168c809494..1e8d97f748fc147bb594c482e90997b38b3bd06a 100644 GIT binary patch literal 2390 zcmb_eO>g2b5WVMDM5sp&t-7l{aYz%EMFJW%?RF*1!HtJ7YOu4m6X4(PI0+x>f@l$! zK*pKpc{6Xup1w#-m4KPo{^@PNc++HKEG+#XO~II%H&W@lD(wRop(>^Id;Z<6_XGxP zyK-P|Em5htMJ1bWr%#Zfv3leG=6MMOIS#^K!%3edYHn9t0~=$im7zEGic+YRVPUB? zNc$VKAs`m398(0tn&8+_^3-C5cJqu&pzsn%P48Rx3eXW6{+2@+M1*&D1;8<1M^Ir3 zF*jzVwD`#}2N77xa$&C(wUU-1FLJ(dMF8<)Ko7;0QnCkkuoJ!n0=~UMZlXQ%h#Pwx zz1HJa5g~bKl_y|X0@|h3uo0JIF5N3=7IRJUSrQKGOIJyfMyW|D31*VpQh$j3V)Q5Y zTcW-czU9GUjndLnRVZA{4Hv&Q`#XjWH^TxU>J`wDMq=yKb5MuRa9MKss`R_Db&DRR z>vM!I?pb1NsHKT2$^HQryh5b|u|!EEzF$F1M>lYiBK-R+;M4d*!7bD=hcq~Miuz<3 zwl%L62{9HV9;aF3#-4_Z1$nMkAo^>XZZWqD!^S*apoHF!Zaewf!Pr8%HrS$4VnvzS z$HI+XbN>5gZ$Bssnys}M$mwKt{mJvT1>2##&*&-HZ6iC!_X4U-)6kQ$cbclBSWk$4 zMas)nc8uWTD2VG$1AT0{J(2$B-?Z-6Tw1Wx5pPrGektUk6tF)3^)qN{BcAVQoU6zO z2j;qjwwxnn=&QaF%z2sDv%7(4PyerFCI&sv@$K~XyIN9!OC?7t^QNs5XP(*3_RbC6 zEy7^%c+;UzF(7U5sO(O+=01|YaDQi(?Gq3aKO|1u5Bvsn!-gi44XUT)oF6d0* X8R!G+fKHZ#SFks!^#p8C;NJTm5|E9u literal 13666 zcmdU$OH&(16oq@8s{98k?Q@^q{P*v?!I0^4|wt%U!5-;_fy6STodIU#)lCo?g%Wh=*$NajEt%-FI1! zc{4OM{z$W=?tNjNGLpIwUAtR1RD0%6wXQs_a{ISx!@60=gQPHU|CCnJ)U%HJp!Ykq z`&sMiI%c^Oo-`Z#MYif+)i=>Ab;Vw@+80|jH0D_2r$TX~(G7PPyQVb__5Ho1-PIaR zT41+6te`kv=+EAR>@ST!P^+GA*Y|Ur>b)+RUU=#DCU!P2yM47CdtNXP{jc=%n|iZ1 zZ}l1cuO;`}Y@U4Yy5ExvwK!G#kHTHvi+t;*_M)brkwzV9XRiI|i8~U~>ud!#+Lb%a zmG5`1`TzEK*qfT3cZDU*SR6m+`5t>Dg|TAmu-;K_^jU3>-KG0e2s)ZIuSa>5apacd zF%aT`mkwI=&NFCM>l?L0uOAA#HodjY9+-+oj`S*PY7q-uhr@|>30}c`nAV>mnTFq| zXf2jO5~Z1MeeOBh*KEdV^!7{$hT20AnGHLXOzYy>ftc{xwKY2|Or;ettEX?UR2}_& zqdxYmEsnB=u0G%E+a2{iRm-~PE4|R8?U7+iVHS1*eIEJou`|}T z<3}@#gSDdVP!{o}`}UJf+*a(VU(vNR3yyT32(|6fP_i}K#16qg>`OOivl5Ti?C+j? zA^Coz_vd=Y(ru0*lX#j6tHj@wPpV~1*iMAlYO}xi&jX=_;lzr`R^v9TupyD1APT*eXTtvVJyWW- z7ge(2Ow1h%^PT54adV7Zw~fvC(@}@$dC&bI&sUYl zTNNvDtYu!QZ1c!RPeCJ#{N6Th;8~*IF8Y;fW4Bdhd?I^B4r+1i$Vau^B4#>R;?$G5^`>0e`+e!3i{s5V*|+|g0Be`kVVn{WDK!5cOlof zP{u#P{S9?&&}#t`+Rh=DB90QHN}j{vLNaY#6p%Y877d} zw{1ngi4YqW%f+doP$kJ{%8O)I6^goWG5dROrDE>8uW!l0=X>0$P&70bQnC23A^DMc z5pjc$Y-dyQu-XSXEg7xFsvXIJx(SS)tiWtWLi>v1TH+K{FS0?ZBruuUV~D&T#Hy!Y za`YB&BqyYlZiqMgnvWb}vT78W9P+l^IdzAsc-+*kP>G~2IuwIPvhd~H(&}0By?#}A z4#k)=)dK!dHK6UQJWbUDu&QVl`I^;wEMG88uiUQt-ubd(#eM2?6Y`r&u5nSV} z%Jh^NiIS1rTkR>fax=R}es8ru;&ZB}48G z*Eg%`?qn8$);r!4!y?OatTqvOWpP1QBgGkb+VD2kDxB-~KE`c}np(nnt5xN>4@Rq6 z;SKxx6i+|!j?}BD4B8n2et{myf@&e!-qY@oALZ-(<@kHK_{B1tt}HL}<@1B2lJWEF z=EUMwKUP_a`LxZno=W%h402U#x9SeZ`pr7`6{ZCJvcFBc9>bfkk5TL5xsuhi8FiUG zJeKXHf)zCxRLk}@z4^Sej%W$iIZF}QV`N;^*qCdWS3l5hVjn+NqL{Nb{2AKWN#NLLc_W|K^*6Axwy#cKY0bo%=~NWObLw?+rsa3x%3H+CN3DKRFU==# zk}%57X4Yo|IY|wt`R1$b1eWam7r!=q)li?vhN(y<`V5o#%{$RGC&YL(>QS@mI;*k~ zIn6_RsOQLxTye+y+Hji8dA!-tc*c+Y=A?OF*g5m4X%*%A)q113Me8xYPHiWi!W?RS zSdgcc^oca@vJklt-EcMl6JRa&XkPvIbGd-5iVMlWG%8< z-C7;}QC7KNq-bSA>`%Ti`!r+o{2&92x23*3X_qtDvh-_WPq^n3v~#^Yr@qOF1^(lM z7{FLfuey$9xm1;xpQX8vS;zKiMZ@PwoH5v6`5GTF z6#l6%#t}n3tlgp?X|_~6jrW(P`^sYVpEg*;aF;fJ8*|;9UKTNt>!RDdhIM!5UANV( z__AorJhgMWWAOlOu-K`}NP1Go9*fjY&A4|(Zm>O!4RYgmIMok49qGGpZni$FwX8%# zB=lIM%hYFzb&PUV@l5i_-y@CZRnu>ZSxqBRFYJ|NCEQ@=Or@u4=7q21;y2?NwCUQc zczT=HV5dRj%ogu%&HWY5V3EJsW-723(m!H!VwHgUNT11e=iBAY%fIMR>S9V`fw$W{ zM*OkW?Crig9{E(B8F|_b7Sq{q_Aosj-8^pdeGQ=uCn{x+9qtZrGC`e#sKYdP+txCR z<#sm*{DJA*C^43A%RWrU9`F`56U^tO8~yhG1Ju)sw|4e)Z~U1mZ~RXIn)61LxM$g2 Qav}+KonF`nZW=T8|3Ad-cmMzZ From 82537d943cbd4a2239bf45378361bf2183ee45d0 Mon Sep 17 00:00:00 2001 From: Violet Date: Mon, 13 Nov 2023 13:39:35 +0000 Subject: [PATCH 08/34] variable types updates --- .../Main files/Harden-Windows-Security.ps1 | 51 ++++++++++--------- Harden-Windows-Security.ps1 | 51 ++++++++++--------- 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index b19b0fba4..b45aededd 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -95,7 +95,7 @@ Set-ExecutionPolicy Bypass -Scope Process # Minimum OS build number required for the hardening measures used in this script [decimal]$Requiredbuild = '22621.2428' # Fetching Temp Directory -[string]$global:UserTempDirectoryPath = [System.IO.Path]::GetTempPath() +[System.String]$global:UserTempDirectoryPath = [System.IO.Path]::GetTempPath() # The total number of the main categories for the parent/main progress bar to render [System.Int64]$TotalMainSteps = 18 @@ -145,10 +145,10 @@ if ([version]$PSVersionTable.PSVersion -ge [version]7.3) { # Questions function function Select-Option { param( - [parameter(Mandatory = $True)][string]$Message, # Contains the main prompt message + [parameter(Mandatory = $True)][System.String]$Message, # Contains the main prompt message [parameter(Mandatory = $True)][string[]]$Options, [parameter(Mandatory = $false)][switch]$SubCategory, - [parameter(Mandatory = $false)][string]$ExtraMessage # Contains any extra notes for sub-categories + [parameter(Mandatory = $false)][System.String]$ExtraMessage # Contains any extra notes for sub-categories ) $Selected = $null @@ -324,7 +324,7 @@ try { } else { - [string]$InfoMsg = "`r`n" + + [System.String]$InfoMsg = "`r`n" + "############################################################################################################`r`n" + "### Please read the Readme in the GitHub repository: https://github.com/HotCakeX/Harden-Windows-Security ###`r`n" + "############################################################################################################`r`n" @@ -392,7 +392,7 @@ try { # create our working directory New-Item -ItemType Directory -Path "$global:UserTempDirectoryPath\HardeningXStuff\" -Force | Out-Null # working directory assignment - [string]$WorkingDir = "$global:UserTempDirectoryPath\HardeningXStuff\" + [System.String]$WorkingDir = "$global:UserTempDirectoryPath\HardeningXStuff\" # change location to the new directory Set-Location $WorkingDir @@ -515,9 +515,9 @@ try { Expand-Archive -Path .\Security-Baselines-X.zip -DestinationPath .\Security-Baselines-X\ -Force -ErrorAction Stop # capturing the Microsoft Security Baselines extracted path in a variable using wildcard and storing it in a variable so that we won't need to change anything in the code other than the download link when they are updated - [string]$MicrosoftSecurityBaselinePath = (Get-ChildItem -Path '.\MicrosoftSecurityBaseline\*\').FullName + [System.String]$MicrosoftSecurityBaselinePath = (Get-ChildItem -Path '.\MicrosoftSecurityBaseline\*\').FullName # capturing the Microsoft 365 Security Baselines extracted path in a variable using wildcard and storing it in a variable so that we won't need to change anything in the code other than the download link when they are updated - [string]$Microsoft365SecurityBaselinePath = (Get-ChildItem -Path '.\Microsoft365SecurityBaseline\*\').FullName + [System.String]$Microsoft365SecurityBaselinePath = (Get-ChildItem -Path '.\Microsoft365SecurityBaseline\*\').FullName #region Windows-Boot-Manager-revocations-for-Secure-Boot KB5025885 # ============================May 9 2023 Windows Boot Manager revocations for Secure Boot ================================= @@ -720,7 +720,7 @@ try { } # Get the state of fast weekly Microsoft recommended driver block list update scheduled task - [string]$BlockListScheduledTaskState = (Get-ScheduledTask -TaskName 'MSFT Driver Block list update' -TaskPath '\MSFT Driver Block list update\' -ErrorAction SilentlyContinue).State + [System.String]$BlockListScheduledTaskState = (Get-ScheduledTask -TaskName 'MSFT Driver Block list update' -TaskPath '\MSFT Driver Block list update\' -ErrorAction SilentlyContinue).State # Create scheduled task for fast weekly Microsoft recommended driver block list update if it doesn't exist or exists but is not Ready/Running if (-NOT (($BlockListScheduledTaskState -eq 'Ready' -or $BlockListScheduledTaskState -eq 'Running'))) { @@ -796,7 +796,7 @@ try { # The Script will show this by emitting True \ False for On \ Off respectively. # bootDMAProtection check - checks for Kernel DMA Protection status in System information or msinfo32 - [string]$BootDMAProtectionCheck = + [System.String]$BootDMAProtectionCheck = @' namespace SystemInfo { @@ -875,8 +875,11 @@ try { } # check if Bitlocker is enabled for the system drive - if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { + if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { + + # Get the key protectors of the OS Drive [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector.keyprotectortype + # check if TPM+PIN and recovery password are being used with Bitlocker which are the safest settings if ($KeyProtectors -contains 'Tpmpin' -and $KeyProtectors -contains 'recoveryPassword') { Write-Host 'Bitlocker is fully and securely enabled for the OS drive' -ForegroundColor Green @@ -885,8 +888,8 @@ try { # if Bitlocker is using TPM+PIN but not recovery password (for key protectors) if ($KeyProtectors -contains 'Tpmpin' -and $KeyProtectors -notcontains 'recoveryPassword') { - $BitLockerMsg = "`nTPM and Startup Pin are available but the recovery password is missing, adding it now... `n" + - "The recovery password will be saved in a Text file in $env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" + [System.String]$BitLockerMsg = "`nTPM and Startup Pin are available but the recovery password is missing, adding it now... `n" + + "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" Write-Host $BitLockerMsg -ForegroundColor Yellow Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" @@ -950,9 +953,9 @@ try { Resume-BitLocker -MountPoint $env:SystemDrive | Out-Null Write-Host "`nBitlocker is now fully and securely enabled for OS drive" -ForegroundColor Green - [string]$BitLockerMsg = "The recovery password will be saved in a Text file in $env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt `n" + + [System.String]$BitLockerMsg = "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt' `n" + "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." - Write-Host $BitLockerMsg -ForegroundColor Cyan + Write-Host $BitLockerMsg -ForegroundColor Cyan } # Enabling Hibernate after making sure OS drive is property encrypted for holding hibernate data @@ -1058,10 +1061,10 @@ try { Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).KeyProtectorId if ($RecoveryPasswordKeyProtectors.Count -gt 1) { - [string]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + + [System.String]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + "Removing all of them and adding a new one now. `n" + "Bitlocker Recovery Password has been added for drive $MountPoint `n" + - "It will be saved in a Text file in $($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt `n" + + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." Write-Host $BitLockerMsg -ForegroundColor Yellow @@ -1092,9 +1095,9 @@ try { # Add Recovery Password Key protector and save it to a file inside the drive Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" - [string]$BitLockerMsg = "`nDrive $MountPoint is auto-unlocked but doesn't have Recovery Password, adding it now... `n" + + [System.String]$BitLockerMsg = "`nDrive $MountPoint is auto-unlocked but doesn't have Recovery Password, adding it now... `n" + "Bitlocker Recovery Password has been added for drive $MountPoint `n" + - "It will be saved in a Text file in $($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt `n" + + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." Write-Host $BitLockerMsg -ForegroundColor Cyan } @@ -1109,9 +1112,9 @@ try { if ($RecoveryPasswordKeyProtectors.Count -gt 1) { - [string]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + + [System.String]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + "Removing all of them and adding a new one now. Bitlocker Recovery Password has been added for drive $MountPoint `n" + - "It will be saved in a Text file in $($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt `n" + + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." Write-Host $BitLockerMsg -ForegroundColor Yellow @@ -1131,8 +1134,8 @@ try { Enable-BitLocker -MountPoint $MountPoint -RecoveryPasswordProtector *> "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null - [string]$BitLockerMsg1 = "`nBitLocker has started encrypting drive $MountPoint" - [string]$BitLockerMsg2 = "Recovery password will be saved in a Text file in $($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt `n" + + [System.String]$BitLockerMsg1 = "`nBitLocker has started encrypting drive $MountPoint" + [System.String]$BitLockerMsg2 = "Recovery password will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." Write-Host $BitLockerMsg1 -ForegroundColor Green Write-Host $BitLockerMsg2 -ForegroundColor Cyan @@ -1727,7 +1730,7 @@ try { # -RemoteAddress in New-NetFirewallRule accepts array according to Microsoft Docs, # so we use "[string[]]$IPList = $IPList -split '\r?\n' -ne ''" to convert the IP lists, which is a single multiline string, into an array function Block-CountryIP { - param ([string[]]$IPList , [string]$ListName) + param ([string[]]$IPList , [System.String]$ListName) # deletes previous rules (if any) to get new up-to-date IP ranges from the sources and set new rules Remove-NetFirewallRule -DisplayName "$ListName IP range blocking" -PolicyStore localhost -ErrorAction SilentlyContinue @@ -1807,7 +1810,7 @@ try { &$WriteRainbow "################################################################################################`r`n" } else { - [string]$InfoMsg = "`r`n" + + [System.String]$InfoMsg = "`r`n" + "################################################################################################`r`n" + "### Please Restart your device to completely apply the security measures and Group Policies ###`r`n" + "################################################################################################`r`n" diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index 0ec405d1c..bafa9ff58 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -95,7 +95,7 @@ Set-ExecutionPolicy Bypass -Scope Process # Minimum OS build number required for the hardening measures used in this script [decimal]$Requiredbuild = '22621.2428' # Fetching Temp Directory -[string]$global:UserTempDirectoryPath = [System.IO.Path]::GetTempPath() +[System.String]$global:UserTempDirectoryPath = [System.IO.Path]::GetTempPath() # The total number of the main categories for the parent/main progress bar to render [System.Int64]$TotalMainSteps = 18 @@ -145,10 +145,10 @@ if ([version]$PSVersionTable.PSVersion -ge [version]7.3) { # Questions function function Select-Option { param( - [parameter(Mandatory = $True)][string]$Message, # Contains the main prompt message + [parameter(Mandatory = $True)][System.String]$Message, # Contains the main prompt message [parameter(Mandatory = $True)][string[]]$Options, [parameter(Mandatory = $false)][switch]$SubCategory, - [parameter(Mandatory = $false)][string]$ExtraMessage # Contains any extra notes for sub-categories + [parameter(Mandatory = $false)][System.String]$ExtraMessage # Contains any extra notes for sub-categories ) $Selected = $null @@ -324,7 +324,7 @@ try { } else { - [string]$InfoMsg = "`r`n" + + [System.String]$InfoMsg = "`r`n" + "############################################################################################################`r`n" + "### Please read the Readme in the GitHub repository: https://github.com/HotCakeX/Harden-Windows-Security ###`r`n" + "############################################################################################################`r`n" @@ -392,7 +392,7 @@ try { # create our working directory New-Item -ItemType Directory -Path "$global:UserTempDirectoryPath\HardeningXStuff\" -Force | Out-Null # working directory assignment - [string]$WorkingDir = "$global:UserTempDirectoryPath\HardeningXStuff\" + [System.String]$WorkingDir = "$global:UserTempDirectoryPath\HardeningXStuff\" # change location to the new directory Set-Location $WorkingDir @@ -515,9 +515,9 @@ try { Expand-Archive -Path .\Security-Baselines-X.zip -DestinationPath .\Security-Baselines-X\ -Force -ErrorAction Stop # capturing the Microsoft Security Baselines extracted path in a variable using wildcard and storing it in a variable so that we won't need to change anything in the code other than the download link when they are updated - [string]$MicrosoftSecurityBaselinePath = (Get-ChildItem -Path '.\MicrosoftSecurityBaseline\*\').FullName + [System.String]$MicrosoftSecurityBaselinePath = (Get-ChildItem -Path '.\MicrosoftSecurityBaseline\*\').FullName # capturing the Microsoft 365 Security Baselines extracted path in a variable using wildcard and storing it in a variable so that we won't need to change anything in the code other than the download link when they are updated - [string]$Microsoft365SecurityBaselinePath = (Get-ChildItem -Path '.\Microsoft365SecurityBaseline\*\').FullName + [System.String]$Microsoft365SecurityBaselinePath = (Get-ChildItem -Path '.\Microsoft365SecurityBaseline\*\').FullName #region Windows-Boot-Manager-revocations-for-Secure-Boot KB5025885 # ============================May 9 2023 Windows Boot Manager revocations for Secure Boot ================================= @@ -720,7 +720,7 @@ try { } # Get the state of fast weekly Microsoft recommended driver block list update scheduled task - [string]$BlockListScheduledTaskState = (Get-ScheduledTask -TaskName 'MSFT Driver Block list update' -TaskPath '\MSFT Driver Block list update\' -ErrorAction SilentlyContinue).State + [System.String]$BlockListScheduledTaskState = (Get-ScheduledTask -TaskName 'MSFT Driver Block list update' -TaskPath '\MSFT Driver Block list update\' -ErrorAction SilentlyContinue).State # Create scheduled task for fast weekly Microsoft recommended driver block list update if it doesn't exist or exists but is not Ready/Running if (-NOT (($BlockListScheduledTaskState -eq 'Ready' -or $BlockListScheduledTaskState -eq 'Running'))) { @@ -796,7 +796,7 @@ try { # The Script will show this by emitting True \ False for On \ Off respectively. # bootDMAProtection check - checks for Kernel DMA Protection status in System information or msinfo32 - [string]$BootDMAProtectionCheck = + [System.String]$BootDMAProtectionCheck = @' namespace SystemInfo { @@ -875,8 +875,11 @@ try { } # check if Bitlocker is enabled for the system drive - if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { + if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { + + # Get the key protectors of the OS Drive [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector.keyprotectortype + # check if TPM+PIN and recovery password are being used with Bitlocker which are the safest settings if ($KeyProtectors -contains 'Tpmpin' -and $KeyProtectors -contains 'recoveryPassword') { Write-Host 'Bitlocker is fully and securely enabled for the OS drive' -ForegroundColor Green @@ -885,8 +888,8 @@ try { # if Bitlocker is using TPM+PIN but not recovery password (for key protectors) if ($KeyProtectors -contains 'Tpmpin' -and $KeyProtectors -notcontains 'recoveryPassword') { - $BitLockerMsg = "`nTPM and Startup Pin are available but the recovery password is missing, adding it now... `n" + - "The recovery password will be saved in a Text file in $env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" + [System.String]$BitLockerMsg = "`nTPM and Startup Pin are available but the recovery password is missing, adding it now... `n" + + "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" Write-Host $BitLockerMsg -ForegroundColor Yellow Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" @@ -950,9 +953,9 @@ try { Resume-BitLocker -MountPoint $env:SystemDrive | Out-Null Write-Host "`nBitlocker is now fully and securely enabled for OS drive" -ForegroundColor Green - [string]$BitLockerMsg = "The recovery password will be saved in a Text file in $env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt `n" + + [System.String]$BitLockerMsg = "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt' `n" + "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." - Write-Host $BitLockerMsg -ForegroundColor Cyan + Write-Host $BitLockerMsg -ForegroundColor Cyan } # Enabling Hibernate after making sure OS drive is property encrypted for holding hibernate data @@ -1058,10 +1061,10 @@ try { Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).KeyProtectorId if ($RecoveryPasswordKeyProtectors.Count -gt 1) { - [string]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + + [System.String]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + "Removing all of them and adding a new one now. `n" + "Bitlocker Recovery Password has been added for drive $MountPoint `n" + - "It will be saved in a Text file in $($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt `n" + + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." Write-Host $BitLockerMsg -ForegroundColor Yellow @@ -1092,9 +1095,9 @@ try { # Add Recovery Password Key protector and save it to a file inside the drive Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" - [string]$BitLockerMsg = "`nDrive $MountPoint is auto-unlocked but doesn't have Recovery Password, adding it now... `n" + + [System.String]$BitLockerMsg = "`nDrive $MountPoint is auto-unlocked but doesn't have Recovery Password, adding it now... `n" + "Bitlocker Recovery Password has been added for drive $MountPoint `n" + - "It will be saved in a Text file in $($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt `n" + + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." Write-Host $BitLockerMsg -ForegroundColor Cyan } @@ -1109,9 +1112,9 @@ try { if ($RecoveryPasswordKeyProtectors.Count -gt 1) { - [string]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + + [System.String]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + "Removing all of them and adding a new one now. Bitlocker Recovery Password has been added for drive $MountPoint `n" + - "It will be saved in a Text file in $($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt `n" + + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." Write-Host $BitLockerMsg -ForegroundColor Yellow @@ -1131,8 +1134,8 @@ try { Enable-BitLocker -MountPoint $MountPoint -RecoveryPasswordProtector *> "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null - [string]$BitLockerMsg1 = "`nBitLocker has started encrypting drive $MountPoint" - [string]$BitLockerMsg2 = "Recovery password will be saved in a Text file in $($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt `n" + + [System.String]$BitLockerMsg1 = "`nBitLocker has started encrypting drive $MountPoint" + [System.String]$BitLockerMsg2 = "Recovery password will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." Write-Host $BitLockerMsg1 -ForegroundColor Green Write-Host $BitLockerMsg2 -ForegroundColor Cyan @@ -1727,7 +1730,7 @@ try { # -RemoteAddress in New-NetFirewallRule accepts array according to Microsoft Docs, # so we use "[string[]]$IPList = $IPList -split '\r?\n' -ne ''" to convert the IP lists, which is a single multiline string, into an array function Block-CountryIP { - param ([string[]]$IPList , [string]$ListName) + param ([string[]]$IPList , [System.String]$ListName) # deletes previous rules (if any) to get new up-to-date IP ranges from the sources and set new rules Remove-NetFirewallRule -DisplayName "$ListName IP range blocking" -PolicyStore localhost -ErrorAction SilentlyContinue @@ -1807,7 +1810,7 @@ try { &$WriteRainbow "################################################################################################`r`n" } else { - [string]$InfoMsg = "`r`n" + + [System.String]$InfoMsg = "`r`n" + "################################################################################################`r`n" + "### Please Restart your device to completely apply the security measures and Group Policies ###`r`n" + "################################################################################################`r`n" From 893429bafd008918b5f6aba8bc2263ec7ba44c43 Mon Sep 17 00:00:00 2001 From: Violet Date: Tue, 14 Nov 2023 22:12:43 +0000 Subject: [PATCH 09/34] Significantly improved BitLocker Category The BitLocker category saves the recovery password in the same format as Windows does when using the GUI to encrypt a drive. Added a function to automate the console text outputs' colors based on PowerShell edition --- .../Main files/Harden-Windows-Security.ps1 | 480 +++++++++++------- Harden-Windows-Security.ps1 | 480 +++++++++++------- 2 files changed, 582 insertions(+), 378 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index b45aededd..d7d58f7dd 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -98,49 +98,9 @@ Set-ExecutionPolicy Bypass -Scope Process [System.String]$global:UserTempDirectoryPath = [System.IO.Path]::GetTempPath() # The total number of the main categories for the parent/main progress bar to render [System.Int64]$TotalMainSteps = 18 - -# Determining if PowerShell is core to use modern styling -[bool]$global:IsCore = $false -if ([version]$PSVersionTable.PSVersion -ge [version]7.3) { - [bool]$global:IsCore = $True -} - # Defining a global boolean variable to determine whether optional diagnostic data should be enabled for Smart App Control or not [bool]$ShouldEnableOptionalDiagnosticData = $false -#Region Custom-colors -[scriptblock]$WriteFuchsia = { Write-Host "$($PSStyle.Foreground.FromRGB(236,68,155))$($args[0])$($PSStyle.Reset)" } -[scriptblock]$WriteOrange = { Write-Host "$($PSStyle.Foreground.FromRGB(255,165,0))$($args[0])$($PSStyle.Reset)" } -[scriptblock]$WriteNeonGreen = { Write-Host "$($PSStyle.Foreground.FromRGB(153,244,67))$($args[0])$($PSStyle.Reset)" } -[scriptblock]$WriteMintGreen = { Write-Host "$($PSStyle.Foreground.FromRGB(152,255,152))$($args[0])$($PSStyle.Reset)" } -[scriptblock]$WritePinkBoldBlink = { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Blink)$($args[0])$($PSStyle.Reset)" } -[scriptblock]$WritePinkBold = { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Reverse)$($args[0])$($PSStyle.Reset)" } - - -[scriptblock]$WriteRainbow = { - $text = $args[0] - $colors = @( - [System.Drawing.Color]::Pink, - [System.Drawing.Color]::HotPink, - [System.Drawing.Color]::SkyBlue, - [System.Drawing.Color]::HotPink, - [System.Drawing.Color]::SkyBlue, - [System.Drawing.Color]::LightSkyBlue, - [System.Drawing.Color]::LightGreen, - [System.Drawing.Color]::Coral, - [System.Drawing.Color]::Plum, - [System.Drawing.Color]::Gold - ) - - $output = '' - for ($i = 0; $i -lt $text.Length; $i++) { - $color = $colors[$i % $colors.Length] - $output += "$($PSStyle.Foreground.FromRGB($color.R, $color.G, $color.B))$($PSStyle.Blink)$($text[$i])$($PSStyle.BlinkOff)$($PSStyle.Reset)" - } - Write-Output $output -} -#EndRegion Custom-colors - #region Functions # Questions function function Select-Option { @@ -155,21 +115,21 @@ function Select-Option { while ($null -eq $Selected) { # Use this style if showing main categories only - if (!$SubCategory) { - if ($IsCore) { &$WriteFuchsia $Message } else { Write-Host $Message -ForegroundColor Magenta } + if (!$SubCategory) { + Write-SmartText -C Fuchsia -G Magenta -I $Message } # Use this style if showing sub-categories only that need additional confirmation else { # Show sub-category's main prompt - if ($IsCore) { &$WriteOrange $Message } else { Write-Host $Message -ForegroundColor Cyan } + Write-SmartText -C Orange -G Cyan -I $Message # Show sub-category's notes/extra message if any if ($ExtraMessage) { - if ($IsCore) { &$WritePinkBoldBlink $ExtraMessage } else { Write-Host $ExtraMessage -ForegroundColor Yellow } + Write-SmartText -C PinkBoldBlink -G Yellow -I $ExtraMessage } } for ($i = 0; $i -lt $Options.Length; $i++) { - if ($IsCore) { &$WriteMintGreen "$($i+1): $($Options[$i])" } else { Write-Host "$($i+1): $($Options[$i])" } + Write-SmartText -C MintGreen -G White -I "$($i+1): $($Options[$i])" } # Make sure user only inputs a positive integer @@ -273,7 +233,83 @@ function Compare-SecureString { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr2) } } -} +} + +# Function to write colorful text based on PS edition +Function Write-SmartText { + [CmdletBinding()] + [Alias('WST')] + + param ( + [Parameter(Mandatory = $True)] + [Alias('C')] + [ValidateSet('Fuchsia', 'Orange', 'NeonGreen', 'MintGreen', 'PinkBoldBlink', 'PinkBold', 'Rainbow' , 'Gold')] + [System.String]$CustomColor, + + [Parameter(Mandatory = $True)] + [Alias('G')] + [ValidateSet('Green', 'Red', 'Magenta', 'Blue', 'Black', 'Cyan', 'DarkBlue', 'DarkCyan', 'DarkRed', 'Gray', 'Yellow', 'White', 'DarkGray', 'DarkGreen', 'DarkMagenta', 'DarkYellow')] + [System.String]$GenericColor, + + [parameter(Mandatory = $True)] + [Alias('I')] + [System.String]$InputText + ) + + begin { + + # Determining if PowerShell is core to use modern styling + [bool]$IsCore = $false + if ([version]$PSVersionTable.PSVersion -ge [version]7.3) { + [bool]$IsCore = $True + } + + } + + process { + + # Check if the current PowerShell is Core using the global variable + if ($IsCore) { + + switch ($CustomColor) { + 'Fuchsia' { Write-Host "$($PSStyle.Foreground.FromRGB(236,68,155))$InputText$($PSStyle.Reset)"; break } + 'Orange' { Write-Host "$($PSStyle.Foreground.FromRGB(255,165,0))$InputText$($PSStyle.Reset)"; break } + 'NeonGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(153,244,67))$InputText$($PSStyle.Reset)"; break } + 'MintGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(152,255,152))$InputText$($PSStyle.Reset)"; break } + 'PinkBoldBlink' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Blink)$InputText$($PSStyle.Reset)"; break } + 'PinkBold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Reverse)$InputText$($PSStyle.Reset)"; break } + 'Gold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,215,0))$($PSStyle.Bold)$($PSStyle.Reverse)$InputText$($PSStyle.Reset)"; break } + 'Rainbow' { + $colors = @( + [System.Drawing.Color]::Pink, + [System.Drawing.Color]::HotPink, + [System.Drawing.Color]::SkyBlue, + [System.Drawing.Color]::HotPink, + [System.Drawing.Color]::SkyBlue, + [System.Drawing.Color]::LightSkyBlue, + [System.Drawing.Color]::LightGreen, + [System.Drawing.Color]::Coral, + [System.Drawing.Color]::Plum, + [System.Drawing.Color]::Gold + ) + + $output = '' + for ($i = 0; $i -lt $InputText.Length; $i++) { + $color = $colors[$i % $colors.Length] + $output += "$($PSStyle.Foreground.FromRGB($color.R, $color.G, $color.B))$($PSStyle.Blink)$($InputText[$i])$($PSStyle.BlinkOff)$($PSStyle.Reset)" + } + Write-Output $output + break + } + + Default { Throw 'Unspecified Color' } + } + } + else { + Write-Host $InputText -ForegroundColor $GenericColor + } + } +} #endregion functions if (Test-IsAdmin) { @@ -315,21 +351,11 @@ try { Write-Host 'You can view the change log on GitHub: https://github.com/HotCakeX/Harden-Windows-Security/releases' -ForegroundColor Magenta break } - - if ($IsCore) { - &$WriteRainbow "`r`n" - &$WriteRainbow "############################################################################################################`r`n" - &$WriteMintGreen "### Please read the Readme in the GitHub repository: https://github.com/HotCakeX/Harden-Windows-Security ###`r`n" - &$WriteRainbow "############################################################################################################`r`n" - - } - else { - [System.String]$InfoMsg = "`r`n" + - "############################################################################################################`r`n" + - "### Please read the Readme in the GitHub repository: https://github.com/HotCakeX/Harden-Windows-Security ###`r`n" + - "############################################################################################################`r`n" - Write-Host $InfoMsg -ForegroundColor Cyan - } + + Write-Host "`r`n" + Write-SmartText -CustomColor Rainbow -GenericColor Cyan -InputText "############################################################################################################`r`n" + Write-SmartText -CustomColor MintGreen -GenericColor Cyan -InputText "### Please read the Readme in the GitHub repository: https://github.com/HotCakeX/Harden-Windows-Security ###`r`n" + Write-SmartText -CustomColor Rainbow -GenericColor Cyan -InputText "############################################################################################################`r`n" #region RequirementsCheck # check if user's OS is Windows Home edition @@ -406,7 +432,7 @@ try { } if (-NOT (Test-IsAdmin)) { - if ($IsCore) { &$WriteNeonGreen 'Skipping commands that require Administrator privileges' } else { Write-Host 'Skipping commands that require Administrator privileges' -ForegroundColor Magenta } + Write-SmartText -CustomColor NeonGreen -GenericColor Magenta -InputText 'Skipping commands that require Administrator privileges' } else { @@ -874,34 +900,84 @@ try { break } + # A script block that generates recovery code just like the Windows does + [scriptblock]$RecoveryPasswordContentGenerator = { + param ([System.Object[]]$KeyProtectorsInputFromScriptBlock) + + return @" +BitLocker Drive Encryption recovery key + +To verify that this is the correct recovery key, compare the start of the following identifier with the identifier value displayed on your PC. + +Identifier: + + $(($KeyProtectorsInputFromScriptBlock | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).KeyProtectorId.Trim('{', '}')) + +If the above identifier matches the one displayed by your PC, then use the following key to unlock your drive. + +Recovery Key: + + $(($KeyProtectorsInputFromScriptBlock | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword) + +If the above identifier doesn't match the one displayed by your PC, then this isn't the right key to unlock your drive. +Try another recovery key, or refer to https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/recovery-overview for additional assistance. + +IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Vault which requires additional authentication to access. + +"@ + } + # check if Bitlocker is enabled for the system drive if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { - + # Get the key protectors of the OS Drive - [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector.keyprotectortype + [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + # Get the key protector types of the OS Drive + [System.String[]]$KeyProtectorTypes = $KeyProtectors.keyprotectortype + + # check if TPM + PIN + recovery password are being used as key protectors for the OS Drive + if ($KeyProtectorTypes -contains 'Tpmpin' -and $KeyProtectorTypes -contains 'recoveryPassword') { + Write-Host 'Bitlocker is already fully and securely enabled for the OS drive' -ForegroundColor Green - # check if TPM+PIN and recovery password are being used with Bitlocker which are the safest settings - if ($KeyProtectors -contains 'Tpmpin' -and $KeyProtectors -contains 'recoveryPassword') { - Write-Host 'Bitlocker is fully and securely enabled for the OS drive' -ForegroundColor Green + Write-SmartText -C Fuchsia -GenericColor Magenta -I 'Here is your 48-digits recovery password for the OS drive in case you were looking for it:' + Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectors | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" + } - else { - # if Bitlocker is using TPM+PIN but not recovery password (for key protectors) - if ($KeyProtectors -contains 'Tpmpin' -and $KeyProtectors -notcontains 'recoveryPassword') { - - [System.String]$BitLockerMsg = "`nTPM and Startup Pin are available but the recovery password is missing, adding it now... `n" + - "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" + else { + # if Bitlocker is using TPM + PIN but not recovery password (for key protectors) + if ($KeyProtectorTypes -contains 'Tpmpin' -and $KeyProtectorTypes -notcontains 'recoveryPassword') { + + [System.String]$BitLockerMsg = "`nTPM and Startup PIN are available but the recovery password is missing, adding it now... `n" + + "It will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" Write-Host $BitLockerMsg -ForegroundColor Yellow - - Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" - Write-Host "`nMake sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." -ForegroundColor Cyan + + # Add RecoveryPasswordProtector key protector to the OS drive + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null + + # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + + # Backup the recovery code of the OS drive in a file + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectors) -ItemType File -Force | Out-Null + } - # if Bitlocker is using recovery password but not TPM+PIN - if ($KeyProtectors -notcontains 'Tpmpin' -and $KeyProtectors -contains 'recoveryPassword') { - Write-Host "`nTPM and Start up PIN are missing but recovery password is in place, `nAdding TPM and Start up PIN now..." -ForegroundColor Cyan - do { - [securestring]$Pin1 = $(if ($IsCore) { &$WritePinkBold "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)" } else { Write-Host "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)" -ForegroundColor Magenta }; Read-Host -AsSecureString) - [securestring]$Pin2 = $(if ($IsCore) { &$WritePinkBold 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)' } else { Write-Host 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)' -ForegroundColor Magenta }; Read-Host -AsSecureString) - + # if Bitlocker is using recovery password but not TPM + PIN as key protectors for the OS Drive + elseif ($KeyProtectorTypes -notcontains 'Tpmpin' -and $KeyProtectorTypes -contains 'recoveryPassword') { + + Write-Host "`nTPM and Start up PIN are missing but recovery password is in place`nAdding TPM and Start up PIN now..." -ForegroundColor Cyan + + # Get the key protectors of the OS Drive + [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + + # Backup the recovery code of the OS drive in a file just in case - This is for when the disk is automatically encrypted and using TPM + Recovery code by default + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectors) -ItemType File -Force | Out-Null + + Write-Host "The recovery password was backed up in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan + + do { + [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)"; Read-Host -AsSecureString) + [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) + # Compare the PINs and make sure they match [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 # If the PINs match and they are at least 10 characters long, max 20 characters @@ -912,7 +988,7 @@ try { } # Repeat this process until the entered PINs match and they are at least 10 characters long, max 20 characters until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) - + try { Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -TpmAndPinProtector -Pin $Pin -ErrorAction Stop Write-Host "`nPINs matched, enabling TPM and startup PIN now" -ForegroundColor Green @@ -927,35 +1003,40 @@ try { } # Do this if Bitlocker is not enabled for the OS drive else { - Write-Host "`nBitlocker is Not enabled for the System Drive, activating now..." -ForegroundColor Yellow - do { - [securestring]$Pin1 = $(if ($IsCore) { &$WritePinkBold 'Enter a Pin for Bitlocker startup (between 10 to 20 characters)' } else { Write-Host 'Enter a Pin for Bitlocker startup (between 10 to 20 characters)' -ForegroundColor Magenta }; Read-Host -AsSecureString) - [securestring]$Pin2 = $(if ($IsCore) { &$WritePinkBold 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)' } else { Write-Host 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)' -ForegroundColor Magenta }; Read-Host -AsSecureString) - + Write-Host "`nBitlocker is not enabled for the OS Drive, activating it now..." -ForegroundColor Yellow + do { + [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I 'Enter a Pin for Bitlocker startup (between 10 to 20 characters)'; Read-Host -AsSecureString) + [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) + [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 - + if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { [securestring]$Pin = $Pin1 } else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } } until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) - + try { - Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod XtsAes256 -Pin $Pin -TpmAndPinProtector -SkipHardwareTest -ErrorAction Stop | Out-Null + Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod 'XtsAes256' -Pin $Pin -TpmAndPinProtector -SkipHardwareTest -ErrorAction Stop *> $null } catch { Write-Host 'These errors occured, run Bitlocker category again after meeting the requirements' -ForegroundColor Red $_ break } - Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null + + # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + + # Backup the recovery code of the OS drive in a file + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectors) -ItemType File -Force | Out-Null + Resume-BitLocker -MountPoint $env:SystemDrive | Out-Null - + Write-Host "`nBitlocker is now fully and securely enabled for OS drive" -ForegroundColor Green - [System.String]$BitLockerMsg = "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt' `n" + - "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." - Write-Host $BitLockerMsg -ForegroundColor Cyan + Write-Host "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan } # Enabling Hibernate after making sure OS drive is property encrypted for holding hibernate data @@ -1016,11 +1097,11 @@ try { # Loop through each non-OS volume and prompt for encryption foreach ($MountPoint in $($NonOSBitLockerVolumes | Sort-Object).MountPoint) { - + # Prompt for confirmation before encrypting each drive switch (Select-Option -SubCategory -Options 'Yes', 'No', 'Exit' -Message "`nEncrypt $MountPoint drive ?") { - 'Yes' { - + 'Yes' { + # Check if the non-OS drive that the user selected to be encrypted is not in the middle of any encryption/decryption operation if ((Get-BitLockerVolume -MountPoint $MountPoint).EncryptionPercentage -ne '100' -and (Get-BitLockerVolume -MountPoint $MountPoint).EncryptionPercentage -ne '0') { # Check if the drive isn't already encrypted and locked @@ -1035,118 +1116,148 @@ try { break } } - + # Check to see if Bitlocker is already turned on for the user selected drive # if it is, perform multiple checks on its key protectors - if ((Get-BitLockerVolume -MountPoint $MountPoint).ProtectionStatus -eq 'on') { - + if ((Get-BitLockerVolume -MountPoint $MountPoint).ProtectionStatus -eq 'on') { + + # Get the key protector types of the Non-OS Drive + [System.String[]]$KeyProtectorTypesNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector.keyprotectortype + # Check 1: if Recovery Password and Auto Unlock key protectors are available on the drive - [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector.keyprotectortype - if ($KeyProtectors -contains 'RecoveryPassword' -and $KeyProtectors -contains 'ExternalKey') { - - # Additional Check 1: if there is any External key key protector, try delete all of them and add a new one + if ($KeyProtectorTypesNonOS -contains 'RecoveryPassword' -and $KeyProtectorTypesNonOS -contains 'ExternalKey') { + + # Additional Check 1: if there are more than 1 ExternalKey key protector, try delete all of them and add a new one # The external key protector that is being used to unlock the drive will not be deleted - $ExternalKeyProtectors = ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | - Where-Object { $_.keyprotectortype -eq 'ExternalKey' }).KeyProtectorId - - $ExternalKeyProtectors | ForEach-Object { + ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | + Where-Object { $_.keyprotectortype -eq 'ExternalKey' }).KeyProtectorId | + ForEach-Object { # -ErrorAction SilentlyContinue makes sure no error is thrown if the drive only has 1 External key key protector # and it's being used to unlock the drive Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue } Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null - + # Additional Check 2: if there are more than 1 Recovery Password, delete all of them and add a new one - $RecoveryPasswordKeyProtectors = ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | + [System.String[]]$RecoveryPasswordKeyProtectors = ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).KeyProtectorId + if ($RecoveryPasswordKeyProtectors.Count -gt 1) { - + [System.String]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + - "Removing all of them and adding a new one now. `n" + - "Bitlocker Recovery Password has been added for drive $MountPoint `n" + - "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + - "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." + "Removing all of them and adding a new one. `n" + + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" Write-Host $BitLockerMsg -ForegroundColor Yellow - + $RecoveryPasswordKeyProtectors | ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ } + # Add new Recovery Password key protector after removing the previous ones - Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" + Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> $null + + # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector + + # Backup the recovery code of the Non-OS drive in a file + New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null + } Write-Host "`nBitlocker is already fully and securely enabled for drive $MountPoint" -ForegroundColor Green - } - - # This happens if the drive has either Recovery Password or Auto Unlock key protector missing - else { - - # Check 2: If the selected drive has Auto Unlock key protector but doesn't have Recovery Password - if ($KeyProtectors -contains 'ExternalKey' -and $KeyProtectors -notcontains 'RecoveryPassword' ) { + + # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector - # if there is any External key key protector, delete all of them and add a new one - $ExternalKeyProtectors = ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | - Where-Object { $_.keyprotectortype -eq 'ExternalKey' }).KeyProtectorId - if ($ExternalKeyProtectors) { - $ExternalKeyProtectors | ForEach-Object { - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue - } - } - Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null - # Add Recovery Password Key protector and save it to a file inside the drive - Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" + Write-SmartText -C Fuchsia -GenericColor Magenta -I "Here is your 48-digits recovery password for drive $MountPoint in case you were looking for it:" + Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectorsNonOS | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" - [System.String]$BitLockerMsg = "`nDrive $MountPoint is auto-unlocked but doesn't have Recovery Password, adding it now... `n" + - "Bitlocker Recovery Password has been added for drive $MountPoint `n" + - "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + - "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." - Write-Host $BitLockerMsg -ForegroundColor Cyan + } + + # If the selected drive has Auto Unlock key protector but doesn't have Recovery Password + elseif ($KeyProtectorTypesNonOS -contains 'ExternalKey' -and $KeyProtectorTypesNonOS -notcontains 'RecoveryPassword' ) { + + # if there are more than 1 ExternalKey key protector, try delete all of them and add a new one + # The external key protector that is being used to unlock the drive will not be deleted + ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | + Where-Object { $_.keyprotectortype -eq 'ExternalKey' }).KeyProtectorId | + ForEach-Object { + # -ErrorAction SilentlyContinue makes sure no error is thrown if the drive only has 1 External key key protector + # and it's being used to unlock the drive + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue } - - # Check 3: If the selected drive has Recovery Password key protector but doesn't have Auto Unlock enabled - if ($KeyProtectors -contains 'RecoveryPassword' -and $KeyProtectors -notcontains 'ExternalKey') { - Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null + Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null + + # Add Recovery Password Key protector and save it to a file inside the drive + Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> $null + + # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector - # if there are more than 1 Recovery Password, delete all of them and add a new one - $RecoveryPasswordKeyProtectors = ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | - Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).KeyProtectorId - - if ($RecoveryPasswordKeyProtectors.Count -gt 1) { - - [System.String]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + - "Removing all of them and adding a new one now. Bitlocker Recovery Password has been added for drive $MountPoint `n" + - "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + - "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." - Write-Host $BitLockerMsg -ForegroundColor Yellow + # Backup the recovery code of the Non-OS drive in a file + New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null + + [System.String]$BitLockerMsg = "`nDrive $MountPoint is auto-unlocked but doesn't have Recovery Password, adding it now... `n" + + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" + Write-Host $BitLockerMsg -ForegroundColor Cyan + } + + # Check 3: If the selected drive has Recovery Password key protector but doesn't have Auto Unlock enabled + elseif ($KeyProtectorTypesNonOS -contains 'RecoveryPassword' -and $KeyProtectorTypesNonOS -notcontains 'ExternalKey') { - # Delete all Recovery Passwords because there were more than 1 - $RecoveryPasswordKeyProtectors | ForEach-Object { - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ - } - # Add a new Recovery Password - Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" - } - } + # Add Auto-unlock (a.k.a ExternalKey key protector to the drive) + Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null + + # if there are more than 1 Recovery Password, delete all of them and add a new one + [System.String[]]$RecoveryPasswordKeyProtectors = ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | + Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).KeyProtectorId + + if ($RecoveryPasswordKeyProtectors.Count -gt 1) { + + [System.String]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + + 'Removing all of them and adding a new one.' + + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" + Write-Host $BitLockerMsg -ForegroundColor Yellow + + # Delete all Recovery Passwords because there were more than 1 + $RecoveryPasswordKeyProtectors | ForEach-Object { + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ + } + + # Add a new Recovery Password + Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> $null + + # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector + + # Backup the recovery code of the Non-OS drive in a file + New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null + } } } - + # Do this if Bitlocker isn't turned on at all on the user selected drive else { - Enable-BitLocker -MountPoint $MountPoint -RecoveryPasswordProtector *> "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" + # Enable BitLocker with RecoveryPassword key protector for the selected Non-OS drive + Enable-BitLocker -MountPoint $MountPoint -RecoveryPasswordProtector *> $null + + # Add Auto-unlock (a.k.a ExternalKey key protector to the drive) Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null - - [System.String]$BitLockerMsg1 = "`nBitLocker has started encrypting drive $MountPoint" - [System.String]$BitLockerMsg2 = "Recovery password will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + - "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." - Write-Host $BitLockerMsg1 -ForegroundColor Green - Write-Host $BitLockerMsg2 -ForegroundColor Cyan + + # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector + + # Backup the recovery code of the Non-OS drive in a file + New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null + + Write-Host "`nBitLocker has started encrypting drive $MountPoint" -ForegroundColor Green + Write-Host "Recovery password will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" -ForegroundColor Cyan } - + } 'No' { break } 'Exit' { &$CleanUp } } } } - } 'No' { break } 'Exit' { &$CleanUp } } @@ -1803,19 +1914,10 @@ try { # Only suggest restarting the device if Admin related categories were run if (Test-IsAdmin) { - if ($IsCore) { - &$WriteRainbow "`r`n" - &$WriteRainbow "################################################################################################`r`n" - &$WriteMintGreen "### Please Restart your device to completely apply the security measures and Group Policies ###`r`n" - &$WriteRainbow "################################################################################################`r`n" - } - else { - [System.String]$InfoMsg = "`r`n" + - "################################################################################################`r`n" + - "### Please Restart your device to completely apply the security measures and Group Policies ###`r`n" + - "################################################################################################`r`n" - Write-Host $InfoMsg -ForegroundColor Cyan - } + Write-Host "`r`n" + Write-SmartText -C Rainbow -G Cyan -I "################################################################################################`r`n" + Write-SmartText -C MintGreen -G Cyan -I "### Please Restart your device to completely apply the security measures and Group Policies ###`r`n" + Write-SmartText -C Rainbow -G Cyan -I "################################################################################################`r`n" } } 'No' { &$CleanUp } diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index bafa9ff58..6055b2ffc 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -98,49 +98,9 @@ Set-ExecutionPolicy Bypass -Scope Process [System.String]$global:UserTempDirectoryPath = [System.IO.Path]::GetTempPath() # The total number of the main categories for the parent/main progress bar to render [System.Int64]$TotalMainSteps = 18 - -# Determining if PowerShell is core to use modern styling -[bool]$global:IsCore = $false -if ([version]$PSVersionTable.PSVersion -ge [version]7.3) { - [bool]$global:IsCore = $True -} - # Defining a global boolean variable to determine whether optional diagnostic data should be enabled for Smart App Control or not [bool]$ShouldEnableOptionalDiagnosticData = $false -#Region Custom-colors -[scriptblock]$WriteFuchsia = { Write-Host "$($PSStyle.Foreground.FromRGB(236,68,155))$($args[0])$($PSStyle.Reset)" } -[scriptblock]$WriteOrange = { Write-Host "$($PSStyle.Foreground.FromRGB(255,165,0))$($args[0])$($PSStyle.Reset)" } -[scriptblock]$WriteNeonGreen = { Write-Host "$($PSStyle.Foreground.FromRGB(153,244,67))$($args[0])$($PSStyle.Reset)" } -[scriptblock]$WriteMintGreen = { Write-Host "$($PSStyle.Foreground.FromRGB(152,255,152))$($args[0])$($PSStyle.Reset)" } -[scriptblock]$WritePinkBoldBlink = { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Blink)$($args[0])$($PSStyle.Reset)" } -[scriptblock]$WritePinkBold = { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Reverse)$($args[0])$($PSStyle.Reset)" } - - -[scriptblock]$WriteRainbow = { - $text = $args[0] - $colors = @( - [System.Drawing.Color]::Pink, - [System.Drawing.Color]::HotPink, - [System.Drawing.Color]::SkyBlue, - [System.Drawing.Color]::HotPink, - [System.Drawing.Color]::SkyBlue, - [System.Drawing.Color]::LightSkyBlue, - [System.Drawing.Color]::LightGreen, - [System.Drawing.Color]::Coral, - [System.Drawing.Color]::Plum, - [System.Drawing.Color]::Gold - ) - - $output = '' - for ($i = 0; $i -lt $text.Length; $i++) { - $color = $colors[$i % $colors.Length] - $output += "$($PSStyle.Foreground.FromRGB($color.R, $color.G, $color.B))$($PSStyle.Blink)$($text[$i])$($PSStyle.BlinkOff)$($PSStyle.Reset)" - } - Write-Output $output -} -#EndRegion Custom-colors - #region Functions # Questions function function Select-Option { @@ -155,21 +115,21 @@ function Select-Option { while ($null -eq $Selected) { # Use this style if showing main categories only - if (!$SubCategory) { - if ($IsCore) { &$WriteFuchsia $Message } else { Write-Host $Message -ForegroundColor Magenta } + if (!$SubCategory) { + Write-SmartText -C Fuchsia -G Magenta -I $Message } # Use this style if showing sub-categories only that need additional confirmation else { # Show sub-category's main prompt - if ($IsCore) { &$WriteOrange $Message } else { Write-Host $Message -ForegroundColor Cyan } + Write-SmartText -C Orange -G Cyan -I $Message # Show sub-category's notes/extra message if any if ($ExtraMessage) { - if ($IsCore) { &$WritePinkBoldBlink $ExtraMessage } else { Write-Host $ExtraMessage -ForegroundColor Yellow } + Write-SmartText -C PinkBoldBlink -G Yellow -I $ExtraMessage } } for ($i = 0; $i -lt $Options.Length; $i++) { - if ($IsCore) { &$WriteMintGreen "$($i+1): $($Options[$i])" } else { Write-Host "$($i+1): $($Options[$i])" } + Write-SmartText -C MintGreen -G White -I "$($i+1): $($Options[$i])" } # Make sure user only inputs a positive integer @@ -273,7 +233,83 @@ function Compare-SecureString { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr2) } } -} +} + +# Function to write colorful text based on PS edition +Function Write-SmartText { + [CmdletBinding()] + [Alias('WST')] + + param ( + [Parameter(Mandatory = $True)] + [Alias('C')] + [ValidateSet('Fuchsia', 'Orange', 'NeonGreen', 'MintGreen', 'PinkBoldBlink', 'PinkBold', 'Rainbow' , 'Gold')] + [System.String]$CustomColor, + + [Parameter(Mandatory = $True)] + [Alias('G')] + [ValidateSet('Green', 'Red', 'Magenta', 'Blue', 'Black', 'Cyan', 'DarkBlue', 'DarkCyan', 'DarkRed', 'Gray', 'Yellow', 'White', 'DarkGray', 'DarkGreen', 'DarkMagenta', 'DarkYellow')] + [System.String]$GenericColor, + + [parameter(Mandatory = $True)] + [Alias('I')] + [System.String]$InputText + ) + + begin { + + # Determining if PowerShell is core to use modern styling + [bool]$IsCore = $false + if ([version]$PSVersionTable.PSVersion -ge [version]7.3) { + [bool]$IsCore = $True + } + + } + + process { + + # Check if the current PowerShell is Core using the global variable + if ($IsCore) { + + switch ($CustomColor) { + 'Fuchsia' { Write-Host "$($PSStyle.Foreground.FromRGB(236,68,155))$InputText$($PSStyle.Reset)"; break } + 'Orange' { Write-Host "$($PSStyle.Foreground.FromRGB(255,165,0))$InputText$($PSStyle.Reset)"; break } + 'NeonGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(153,244,67))$InputText$($PSStyle.Reset)"; break } + 'MintGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(152,255,152))$InputText$($PSStyle.Reset)"; break } + 'PinkBoldBlink' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Blink)$InputText$($PSStyle.Reset)"; break } + 'PinkBold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Reverse)$InputText$($PSStyle.Reset)"; break } + 'Gold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,215,0))$($PSStyle.Bold)$($PSStyle.Reverse)$InputText$($PSStyle.Reset)"; break } + 'Rainbow' { + $colors = @( + [System.Drawing.Color]::Pink, + [System.Drawing.Color]::HotPink, + [System.Drawing.Color]::SkyBlue, + [System.Drawing.Color]::HotPink, + [System.Drawing.Color]::SkyBlue, + [System.Drawing.Color]::LightSkyBlue, + [System.Drawing.Color]::LightGreen, + [System.Drawing.Color]::Coral, + [System.Drawing.Color]::Plum, + [System.Drawing.Color]::Gold + ) + + $output = '' + for ($i = 0; $i -lt $InputText.Length; $i++) { + $color = $colors[$i % $colors.Length] + $output += "$($PSStyle.Foreground.FromRGB($color.R, $color.G, $color.B))$($PSStyle.Blink)$($InputText[$i])$($PSStyle.BlinkOff)$($PSStyle.Reset)" + } + Write-Output $output + break + } + + Default { Throw 'Unspecified Color' } + } + } + else { + Write-Host $InputText -ForegroundColor $GenericColor + } + } +} #endregion functions if (Test-IsAdmin) { @@ -315,21 +351,11 @@ try { Write-Host 'You can view the change log on GitHub: https://github.com/HotCakeX/Harden-Windows-Security/releases' -ForegroundColor Magenta break } - - if ($IsCore) { - &$WriteRainbow "`r`n" - &$WriteRainbow "############################################################################################################`r`n" - &$WriteMintGreen "### Please read the Readme in the GitHub repository: https://github.com/HotCakeX/Harden-Windows-Security ###`r`n" - &$WriteRainbow "############################################################################################################`r`n" - - } - else { - [System.String]$InfoMsg = "`r`n" + - "############################################################################################################`r`n" + - "### Please read the Readme in the GitHub repository: https://github.com/HotCakeX/Harden-Windows-Security ###`r`n" + - "############################################################################################################`r`n" - Write-Host $InfoMsg -ForegroundColor Cyan - } + + Write-Host "`r`n" + Write-SmartText -CustomColor Rainbow -GenericColor Cyan -InputText "############################################################################################################`r`n" + Write-SmartText -CustomColor MintGreen -GenericColor Cyan -InputText "### Please read the Readme in the GitHub repository: https://github.com/HotCakeX/Harden-Windows-Security ###`r`n" + Write-SmartText -CustomColor Rainbow -GenericColor Cyan -InputText "############################################################################################################`r`n" #region RequirementsCheck # check if user's OS is Windows Home edition @@ -406,7 +432,7 @@ try { } if (-NOT (Test-IsAdmin)) { - if ($IsCore) { &$WriteNeonGreen 'Skipping commands that require Administrator privileges' } else { Write-Host 'Skipping commands that require Administrator privileges' -ForegroundColor Magenta } + Write-SmartText -CustomColor NeonGreen -GenericColor Magenta -InputText 'Skipping commands that require Administrator privileges' } else { @@ -874,34 +900,84 @@ try { break } + # A script block that generates recovery code just like the Windows does + [scriptblock]$RecoveryPasswordContentGenerator = { + param ([System.Object[]]$KeyProtectorsInputFromScriptBlock) + + return @" +BitLocker Drive Encryption recovery key + +To verify that this is the correct recovery key, compare the start of the following identifier with the identifier value displayed on your PC. + +Identifier: + + $(($KeyProtectorsInputFromScriptBlock | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).KeyProtectorId.Trim('{', '}')) + +If the above identifier matches the one displayed by your PC, then use the following key to unlock your drive. + +Recovery Key: + + $(($KeyProtectorsInputFromScriptBlock | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword) + +If the above identifier doesn't match the one displayed by your PC, then this isn't the right key to unlock your drive. +Try another recovery key, or refer to https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/recovery-overview for additional assistance. + +IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Vault which requires additional authentication to access. + +"@ + } + # check if Bitlocker is enabled for the system drive if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { - + # Get the key protectors of the OS Drive - [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector.keyprotectortype + [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + # Get the key protector types of the OS Drive + [System.String[]]$KeyProtectorTypes = $KeyProtectors.keyprotectortype + + # check if TPM + PIN + recovery password are being used as key protectors for the OS Drive + if ($KeyProtectorTypes -contains 'Tpmpin' -and $KeyProtectorTypes -contains 'recoveryPassword') { + Write-Host 'Bitlocker is already fully and securely enabled for the OS drive' -ForegroundColor Green - # check if TPM+PIN and recovery password are being used with Bitlocker which are the safest settings - if ($KeyProtectors -contains 'Tpmpin' -and $KeyProtectors -contains 'recoveryPassword') { - Write-Host 'Bitlocker is fully and securely enabled for the OS drive' -ForegroundColor Green + Write-SmartText -C Fuchsia -GenericColor Magenta -I 'Here is your 48-digits recovery password for the OS drive in case you were looking for it:' + Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectors | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" + } - else { - # if Bitlocker is using TPM+PIN but not recovery password (for key protectors) - if ($KeyProtectors -contains 'Tpmpin' -and $KeyProtectors -notcontains 'recoveryPassword') { - - [System.String]$BitLockerMsg = "`nTPM and Startup Pin are available but the recovery password is missing, adding it now... `n" + - "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" + else { + # if Bitlocker is using TPM + PIN but not recovery password (for key protectors) + if ($KeyProtectorTypes -contains 'Tpmpin' -and $KeyProtectorTypes -notcontains 'recoveryPassword') { + + [System.String]$BitLockerMsg = "`nTPM and Startup PIN are available but the recovery password is missing, adding it now... `n" + + "It will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" Write-Host $BitLockerMsg -ForegroundColor Yellow - - Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" - Write-Host "`nMake sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." -ForegroundColor Cyan + + # Add RecoveryPasswordProtector key protector to the OS drive + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null + + # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + + # Backup the recovery code of the OS drive in a file + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectors) -ItemType File -Force | Out-Null + } - # if Bitlocker is using recovery password but not TPM+PIN - if ($KeyProtectors -notcontains 'Tpmpin' -and $KeyProtectors -contains 'recoveryPassword') { - Write-Host "`nTPM and Start up PIN are missing but recovery password is in place, `nAdding TPM and Start up PIN now..." -ForegroundColor Cyan - do { - [securestring]$Pin1 = $(if ($IsCore) { &$WritePinkBold "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)" } else { Write-Host "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)" -ForegroundColor Magenta }; Read-Host -AsSecureString) - [securestring]$Pin2 = $(if ($IsCore) { &$WritePinkBold 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)' } else { Write-Host 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)' -ForegroundColor Magenta }; Read-Host -AsSecureString) - + # if Bitlocker is using recovery password but not TPM + PIN as key protectors for the OS Drive + elseif ($KeyProtectorTypes -notcontains 'Tpmpin' -and $KeyProtectorTypes -contains 'recoveryPassword') { + + Write-Host "`nTPM and Start up PIN are missing but recovery password is in place`nAdding TPM and Start up PIN now..." -ForegroundColor Cyan + + # Get the key protectors of the OS Drive + [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + + # Backup the recovery code of the OS drive in a file just in case - This is for when the disk is automatically encrypted and using TPM + Recovery code by default + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectors) -ItemType File -Force | Out-Null + + Write-Host "The recovery password was backed up in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan + + do { + [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)"; Read-Host -AsSecureString) + [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) + # Compare the PINs and make sure they match [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 # If the PINs match and they are at least 10 characters long, max 20 characters @@ -912,7 +988,7 @@ try { } # Repeat this process until the entered PINs match and they are at least 10 characters long, max 20 characters until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) - + try { Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -TpmAndPinProtector -Pin $Pin -ErrorAction Stop Write-Host "`nPINs matched, enabling TPM and startup PIN now" -ForegroundColor Green @@ -927,35 +1003,40 @@ try { } # Do this if Bitlocker is not enabled for the OS drive else { - Write-Host "`nBitlocker is Not enabled for the System Drive, activating now..." -ForegroundColor Yellow - do { - [securestring]$Pin1 = $(if ($IsCore) { &$WritePinkBold 'Enter a Pin for Bitlocker startup (between 10 to 20 characters)' } else { Write-Host 'Enter a Pin for Bitlocker startup (between 10 to 20 characters)' -ForegroundColor Magenta }; Read-Host -AsSecureString) - [securestring]$Pin2 = $(if ($IsCore) { &$WritePinkBold 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)' } else { Write-Host 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)' -ForegroundColor Magenta }; Read-Host -AsSecureString) - + Write-Host "`nBitlocker is not enabled for the OS Drive, activating it now..." -ForegroundColor Yellow + do { + [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I 'Enter a Pin for Bitlocker startup (between 10 to 20 characters)'; Read-Host -AsSecureString) + [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) + [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 - + if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { [securestring]$Pin = $Pin1 } else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } } until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) - + try { - Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod XtsAes256 -Pin $Pin -TpmAndPinProtector -SkipHardwareTest -ErrorAction Stop | Out-Null + Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod 'XtsAes256' -Pin $Pin -TpmAndPinProtector -SkipHardwareTest -ErrorAction Stop *> $null } catch { Write-Host 'These errors occured, run Bitlocker category again after meeting the requirements' -ForegroundColor Red $_ break } - Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null + + # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + + # Backup the recovery code of the OS drive in a file + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectors) -ItemType File -Force | Out-Null + Resume-BitLocker -MountPoint $env:SystemDrive | Out-Null - + Write-Host "`nBitlocker is now fully and securely enabled for OS drive" -ForegroundColor Green - [System.String]$BitLockerMsg = "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt' `n" + - "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." - Write-Host $BitLockerMsg -ForegroundColor Cyan + Write-Host "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan } # Enabling Hibernate after making sure OS drive is property encrypted for holding hibernate data @@ -1016,11 +1097,11 @@ try { # Loop through each non-OS volume and prompt for encryption foreach ($MountPoint in $($NonOSBitLockerVolumes | Sort-Object).MountPoint) { - + # Prompt for confirmation before encrypting each drive switch (Select-Option -SubCategory -Options 'Yes', 'No', 'Exit' -Message "`nEncrypt $MountPoint drive ?") { - 'Yes' { - + 'Yes' { + # Check if the non-OS drive that the user selected to be encrypted is not in the middle of any encryption/decryption operation if ((Get-BitLockerVolume -MountPoint $MountPoint).EncryptionPercentage -ne '100' -and (Get-BitLockerVolume -MountPoint $MountPoint).EncryptionPercentage -ne '0') { # Check if the drive isn't already encrypted and locked @@ -1035,118 +1116,148 @@ try { break } } - + # Check to see if Bitlocker is already turned on for the user selected drive # if it is, perform multiple checks on its key protectors - if ((Get-BitLockerVolume -MountPoint $MountPoint).ProtectionStatus -eq 'on') { - + if ((Get-BitLockerVolume -MountPoint $MountPoint).ProtectionStatus -eq 'on') { + + # Get the key protector types of the Non-OS Drive + [System.String[]]$KeyProtectorTypesNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector.keyprotectortype + # Check 1: if Recovery Password and Auto Unlock key protectors are available on the drive - [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector.keyprotectortype - if ($KeyProtectors -contains 'RecoveryPassword' -and $KeyProtectors -contains 'ExternalKey') { - - # Additional Check 1: if there is any External key key protector, try delete all of them and add a new one + if ($KeyProtectorTypesNonOS -contains 'RecoveryPassword' -and $KeyProtectorTypesNonOS -contains 'ExternalKey') { + + # Additional Check 1: if there are more than 1 ExternalKey key protector, try delete all of them and add a new one # The external key protector that is being used to unlock the drive will not be deleted - $ExternalKeyProtectors = ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | - Where-Object { $_.keyprotectortype -eq 'ExternalKey' }).KeyProtectorId - - $ExternalKeyProtectors | ForEach-Object { + ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | + Where-Object { $_.keyprotectortype -eq 'ExternalKey' }).KeyProtectorId | + ForEach-Object { # -ErrorAction SilentlyContinue makes sure no error is thrown if the drive only has 1 External key key protector # and it's being used to unlock the drive Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue } Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null - + # Additional Check 2: if there are more than 1 Recovery Password, delete all of them and add a new one - $RecoveryPasswordKeyProtectors = ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | + [System.String[]]$RecoveryPasswordKeyProtectors = ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).KeyProtectorId + if ($RecoveryPasswordKeyProtectors.Count -gt 1) { - + [System.String]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + - "Removing all of them and adding a new one now. `n" + - "Bitlocker Recovery Password has been added for drive $MountPoint `n" + - "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + - "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." + "Removing all of them and adding a new one. `n" + + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" Write-Host $BitLockerMsg -ForegroundColor Yellow - + $RecoveryPasswordKeyProtectors | ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ } + # Add new Recovery Password key protector after removing the previous ones - Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" + Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> $null + + # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector + + # Backup the recovery code of the Non-OS drive in a file + New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null + } Write-Host "`nBitlocker is already fully and securely enabled for drive $MountPoint" -ForegroundColor Green - } - - # This happens if the drive has either Recovery Password or Auto Unlock key protector missing - else { - - # Check 2: If the selected drive has Auto Unlock key protector but doesn't have Recovery Password - if ($KeyProtectors -contains 'ExternalKey' -and $KeyProtectors -notcontains 'RecoveryPassword' ) { + + # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector - # if there is any External key key protector, delete all of them and add a new one - $ExternalKeyProtectors = ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | - Where-Object { $_.keyprotectortype -eq 'ExternalKey' }).KeyProtectorId - if ($ExternalKeyProtectors) { - $ExternalKeyProtectors | ForEach-Object { - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue - } - } - Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null - # Add Recovery Password Key protector and save it to a file inside the drive - Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" + Write-SmartText -C Fuchsia -GenericColor Magenta -I "Here is your 48-digits recovery password for drive $MountPoint in case you were looking for it:" + Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectorsNonOS | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" - [System.String]$BitLockerMsg = "`nDrive $MountPoint is auto-unlocked but doesn't have Recovery Password, adding it now... `n" + - "Bitlocker Recovery Password has been added for drive $MountPoint `n" + - "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + - "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." - Write-Host $BitLockerMsg -ForegroundColor Cyan + } + + # If the selected drive has Auto Unlock key protector but doesn't have Recovery Password + elseif ($KeyProtectorTypesNonOS -contains 'ExternalKey' -and $KeyProtectorTypesNonOS -notcontains 'RecoveryPassword' ) { + + # if there are more than 1 ExternalKey key protector, try delete all of them and add a new one + # The external key protector that is being used to unlock the drive will not be deleted + ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | + Where-Object { $_.keyprotectortype -eq 'ExternalKey' }).KeyProtectorId | + ForEach-Object { + # -ErrorAction SilentlyContinue makes sure no error is thrown if the drive only has 1 External key key protector + # and it's being used to unlock the drive + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue } - - # Check 3: If the selected drive has Recovery Password key protector but doesn't have Auto Unlock enabled - if ($KeyProtectors -contains 'RecoveryPassword' -and $KeyProtectors -notcontains 'ExternalKey') { - Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null + Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null + + # Add Recovery Password Key protector and save it to a file inside the drive + Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> $null + + # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector - # if there are more than 1 Recovery Password, delete all of them and add a new one - $RecoveryPasswordKeyProtectors = ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | - Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).KeyProtectorId - - if ($RecoveryPasswordKeyProtectors.Count -gt 1) { - - [System.String]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + - "Removing all of them and adding a new one now. Bitlocker Recovery Password has been added for drive $MountPoint `n" + - "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + - "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." - Write-Host $BitLockerMsg -ForegroundColor Yellow + # Backup the recovery code of the Non-OS drive in a file + New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null + + [System.String]$BitLockerMsg = "`nDrive $MountPoint is auto-unlocked but doesn't have Recovery Password, adding it now... `n" + + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" + Write-Host $BitLockerMsg -ForegroundColor Cyan + } + + # Check 3: If the selected drive has Recovery Password key protector but doesn't have Auto Unlock enabled + elseif ($KeyProtectorTypesNonOS -contains 'RecoveryPassword' -and $KeyProtectorTypesNonOS -notcontains 'ExternalKey') { - # Delete all Recovery Passwords because there were more than 1 - $RecoveryPasswordKeyProtectors | ForEach-Object { - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ - } - # Add a new Recovery Password - Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" - } - } + # Add Auto-unlock (a.k.a ExternalKey key protector to the drive) + Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null + + # if there are more than 1 Recovery Password, delete all of them and add a new one + [System.String[]]$RecoveryPasswordKeyProtectors = ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | + Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).KeyProtectorId + + if ($RecoveryPasswordKeyProtectors.Count -gt 1) { + + [System.String]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + + 'Removing all of them and adding a new one.' + + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" + Write-Host $BitLockerMsg -ForegroundColor Yellow + + # Delete all Recovery Passwords because there were more than 1 + $RecoveryPasswordKeyProtectors | ForEach-Object { + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ + } + + # Add a new Recovery Password + Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> $null + + # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector + + # Backup the recovery code of the Non-OS drive in a file + New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null + } } } - + # Do this if Bitlocker isn't turned on at all on the user selected drive else { - Enable-BitLocker -MountPoint $MountPoint -RecoveryPasswordProtector *> "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" + # Enable BitLocker with RecoveryPassword key protector for the selected Non-OS drive + Enable-BitLocker -MountPoint $MountPoint -RecoveryPasswordProtector *> $null + + # Add Auto-unlock (a.k.a ExternalKey key protector to the drive) Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null - - [System.String]$BitLockerMsg1 = "`nBitLocker has started encrypting drive $MountPoint" - [System.String]$BitLockerMsg2 = "Recovery password will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt' `n" + - "Make sure to keep it in a safe place, e.g. in OneDrive's Personal Vault which requires authentication to access." - Write-Host $BitLockerMsg1 -ForegroundColor Green - Write-Host $BitLockerMsg2 -ForegroundColor Cyan + + # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector + + # Backup the recovery code of the Non-OS drive in a file + New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null + + Write-Host "`nBitLocker has started encrypting drive $MountPoint" -ForegroundColor Green + Write-Host "Recovery password will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" -ForegroundColor Cyan } - + } 'No' { break } 'Exit' { &$CleanUp } } } } - } 'No' { break } 'Exit' { &$CleanUp } } @@ -1803,19 +1914,10 @@ try { # Only suggest restarting the device if Admin related categories were run if (Test-IsAdmin) { - if ($IsCore) { - &$WriteRainbow "`r`n" - &$WriteRainbow "################################################################################################`r`n" - &$WriteMintGreen "### Please Restart your device to completely apply the security measures and Group Policies ###`r`n" - &$WriteRainbow "################################################################################################`r`n" - } - else { - [System.String]$InfoMsg = "`r`n" + - "################################################################################################`r`n" + - "### Please Restart your device to completely apply the security measures and Group Policies ###`r`n" + - "################################################################################################`r`n" - Write-Host $InfoMsg -ForegroundColor Cyan - } + Write-Host "`r`n" + Write-SmartText -C Rainbow -G Cyan -I "################################################################################################`r`n" + Write-SmartText -C MintGreen -G Cyan -I "### Please Restart your device to completely apply the security measures and Group Policies ###`r`n" + Write-SmartText -C Rainbow -G Cyan -I "################################################################################################`r`n" } } 'No' { &$CleanUp } From bc724f11991b12955da1dfa1f0dd6c49d4104a4f Mon Sep 17 00:00:00 2001 From: Violet Date: Wed, 15 Nov 2023 11:57:04 +0000 Subject: [PATCH 10/34] Making sure variables follow PascalCasing --- Harden-Windows-Security.ps1 | 41 +++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index 6055b2ffc..124c7b8ef 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -167,9 +167,9 @@ function Edit-Registry { # https://devblogs.microsoft.com/scripting/use-function-to-determine-elevation-of-powershell-console/ # Function to test if current session has administrator privileges Function Test-IsAdmin { - $identity = [Security.Principal.WindowsIdentity]::GetCurrent() - $principal = New-Object Security.Principal.WindowsPrincipal $identity - $principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) + $Identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $Principal = New-Object Security.Principal.WindowsPrincipal $Identity + $Principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) } # Hiding Invoke-WebRequest progress because it creates lingering visual effect on PowerShell console for some reason @@ -181,10 +181,11 @@ $null = New-Module { function Invoke-WithoutProgress { [CmdletBinding()] param ( - [Parameter(Mandatory)][scriptblock]$ScriptBlock + [Parameter(Mandatory = $true)] + [scriptblock]$ScriptBlock ) # Save current progress preference and hide the progress - $prevProgressPreference = $global:ProgressPreference + [System.Management.Automation.ActionPreference]$PrevProgressPreference = $global:ProgressPreference $global:ProgressPreference = 'SilentlyContinue' try { # Run the script block in the scope of the caller of this module function @@ -192,7 +193,7 @@ $null = New-Module { } finally { # Restore the original behavior - $global:ProgressPreference = $prevProgressPreference + $global:ProgressPreference = $PrevProgressPreference } } } @@ -205,20 +206,20 @@ https://stackoverflow.com/questions/48809012/compare-two-credentials-in-powershe #> function Compare-SecureString { param( - [Security.SecureString] $secureString1, - [Security.SecureString] $secureString2 + [Security.SecureString]$SecureString1, + [Security.SecureString]$SecureString2 ) try { - $bstr1 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureString1) - $bstr2 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureString2) - $length1 = [Runtime.InteropServices.Marshal]::ReadInt32($bstr1, -4) - $length2 = [Runtime.InteropServices.Marshal]::ReadInt32($bstr2, -4) - if ( $length1 -ne $length2 ) { + $Bstr1 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString1) + $Bstr2 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString2) + $Length1 = [Runtime.InteropServices.Marshal]::ReadInt32($Bstr1, -4) + $Length2 = [Runtime.InteropServices.Marshal]::ReadInt32($Bstr2, -4) + if ( $Length1 -ne $Length2 ) { return $false } - for ( $i = 0; $i -lt $length1; ++$i ) { - $b1 = [Runtime.InteropServices.Marshal]::ReadByte($bstr1, $i) - $b2 = [Runtime.InteropServices.Marshal]::ReadByte($bstr2, $i) + for ( $i = 0; $i -lt $Length1; ++$i ) { + $b1 = [Runtime.InteropServices.Marshal]::ReadByte($Bstr1, $i) + $b2 = [Runtime.InteropServices.Marshal]::ReadByte($Bstr2, $i) if ( $b1 -ne $b2 ) { return $false } @@ -226,11 +227,11 @@ function Compare-SecureString { return $true } finally { - if ( $bstr1 -ne [IntPtr]::Zero ) { - [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr1) + if ( $Bstr1 -ne [IntPtr]::Zero ) { + [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($Bstr1) } - if ( $bstr2 -ne [IntPtr]::Zero ) { - [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr2) + if ( $Bstr2 -ne [IntPtr]::Zero ) { + [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($Bstr2) } } } From ec72f7776146c129a11776f6b605d3b3a6889fca Mon Sep 17 00:00:00 2001 From: Violet Date: Wed, 15 Nov 2023 12:55:40 +0000 Subject: [PATCH 11/34] Minor improvements to the BitLocker category Improved code readability and host output messages --- .../Main files/Harden-Windows-Security.ps1 | 238 +++++++++--------- Harden-Windows-Security.ps1 | 197 ++++++++------- 2 files changed, 225 insertions(+), 210 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index d7d58f7dd..a8d22a0f0 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -106,7 +106,7 @@ Set-ExecutionPolicy Bypass -Scope Process function Select-Option { param( [parameter(Mandatory = $True)][System.String]$Message, # Contains the main prompt message - [parameter(Mandatory = $True)][string[]]$Options, + [parameter(Mandatory = $True)][System.String[]]$Options, [parameter(Mandatory = $false)][switch]$SubCategory, [parameter(Mandatory = $false)][System.String]$ExtraMessage # Contains any extra notes for sub-categories ) @@ -167,9 +167,9 @@ function Edit-Registry { # https://devblogs.microsoft.com/scripting/use-function-to-determine-elevation-of-powershell-console/ # Function to test if current session has administrator privileges Function Test-IsAdmin { - $identity = [Security.Principal.WindowsIdentity]::GetCurrent() - $principal = New-Object Security.Principal.WindowsPrincipal $identity - $principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) + $Identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $Principal = New-Object Security.Principal.WindowsPrincipal $Identity + $Principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) } # Hiding Invoke-WebRequest progress because it creates lingering visual effect on PowerShell console for some reason @@ -181,10 +181,11 @@ $null = New-Module { function Invoke-WithoutProgress { [CmdletBinding()] param ( - [Parameter(Mandatory)][scriptblock]$ScriptBlock + [Parameter(Mandatory = $true)] + [scriptblock]$ScriptBlock ) # Save current progress preference and hide the progress - $prevProgressPreference = $global:ProgressPreference + [System.Management.Automation.ActionPreference]$PrevProgressPreference = $global:ProgressPreference $global:ProgressPreference = 'SilentlyContinue' try { # Run the script block in the scope of the caller of this module function @@ -192,7 +193,7 @@ $null = New-Module { } finally { # Restore the original behavior - $global:ProgressPreference = $prevProgressPreference + $global:ProgressPreference = $PrevProgressPreference } } } @@ -205,20 +206,20 @@ https://stackoverflow.com/questions/48809012/compare-two-credentials-in-powershe #> function Compare-SecureString { param( - [Security.SecureString] $secureString1, - [Security.SecureString] $secureString2 + [Security.SecureString]$SecureString1, + [Security.SecureString]$SecureString2 ) try { - $bstr1 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureString1) - $bstr2 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureString2) - $length1 = [Runtime.InteropServices.Marshal]::ReadInt32($bstr1, -4) - $length2 = [Runtime.InteropServices.Marshal]::ReadInt32($bstr2, -4) - if ( $length1 -ne $length2 ) { + $Bstr1 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString1) + $Bstr2 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString2) + $Length1 = [Runtime.InteropServices.Marshal]::ReadInt32($Bstr1, -4) + $Length2 = [Runtime.InteropServices.Marshal]::ReadInt32($Bstr2, -4) + if ( $Length1 -ne $Length2 ) { return $false } - for ( $i = 0; $i -lt $length1; ++$i ) { - $b1 = [Runtime.InteropServices.Marshal]::ReadByte($bstr1, $i) - $b2 = [Runtime.InteropServices.Marshal]::ReadByte($bstr2, $i) + for ( $i = 0; $i -lt $Length1; ++$i ) { + $b1 = [Runtime.InteropServices.Marshal]::ReadByte($Bstr1, $i) + $b2 = [Runtime.InteropServices.Marshal]::ReadByte($Bstr2, $i) if ( $b1 -ne $b2 ) { return $false } @@ -226,11 +227,11 @@ function Compare-SecureString { return $true } finally { - if ( $bstr1 -ne [IntPtr]::Zero ) { - [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr1) + if ( $Bstr1 -ne [IntPtr]::Zero ) { + [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($Bstr1) } - if ( $bstr2 -ne [IntPtr]::Zero ) { - [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr2) + if ( $Bstr2 -ne [IntPtr]::Zero ) { + [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($Bstr2) } } } @@ -321,7 +322,7 @@ if (Test-IsAdmin) { # backup the current allowed apps list in Controlled folder access in order to restore them at the end of the script # doing this so that when we Add and then Remove PowerShell executables in Controlled folder access exclusions # no user customization will be affected - [string[]]$CFAAllowedAppsBackup = $MDAVPreferencesCurrent.ControlledFolderAccessAllowedApplications + [System.String[]]$CFAAllowedAppsBackup = $MDAVPreferencesCurrent.ControlledFolderAccessAllowedApplications # Temporarily allow the currently running PowerShell executables to the Controlled Folder Access allowed apps # so that the script can run without interruption. This change is reverted at the end. @@ -928,24 +929,25 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } # check if Bitlocker is enabled for the system drive - if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { - + if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { + # Get the key protectors of the OS Drive - [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector # Get the key protector types of the OS Drive - [System.String[]]$KeyProtectorTypes = $KeyProtectors.keyprotectortype + [System.String[]]$KeyProtectorTypesOSDrive = $KeyProtectorsOSDrive.keyprotectortype # check if TPM + PIN + recovery password are being used as key protectors for the OS Drive - if ($KeyProtectorTypes -contains 'Tpmpin' -and $KeyProtectorTypes -contains 'recoveryPassword') { - Write-Host 'Bitlocker is already fully and securely enabled for the OS drive' -ForegroundColor Green - + if ($KeyProtectorTypesOSDrive -contains 'Tpmpin' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { + + Write-SmartText -C MintGreen -G Green -I 'Bitlocker is already fully and securely enabled for the OS drive' + Write-SmartText -C Fuchsia -GenericColor Magenta -I 'Here is your 48-digits recovery password for the OS drive in case you were looking for it:' - Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectors | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" - + Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectorsOSDrive | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" + } else { # if Bitlocker is using TPM + PIN but not recovery password (for key protectors) - if ($KeyProtectorTypes -contains 'Tpmpin' -and $KeyProtectorTypes -notcontains 'recoveryPassword') { + if ($KeyProtectorTypesOSDrive -contains 'Tpmpin' -and $KeyProtectorTypesOSDrive -notcontains 'recoveryPassword') { [System.String]$BitLockerMsg = "`nTPM and Startup PIN are available but the recovery password is missing, adding it now... `n" + "It will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" @@ -953,89 +955,92 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # Add RecoveryPasswordProtector key protector to the OS drive Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null - + # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it - [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector - + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + # Backup the recovery code of the OS drive in a file - New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectors) -ItemType File -Force | Out-Null - - } + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null + + } # if Bitlocker is using recovery password but not TPM + PIN as key protectors for the OS Drive - elseif ($KeyProtectorTypes -notcontains 'Tpmpin' -and $KeyProtectorTypes -contains 'recoveryPassword') { - + elseif ($KeyProtectorTypesOSDrive -notcontains 'Tpmpin' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { + Write-Host "`nTPM and Start up PIN are missing but recovery password is in place`nAdding TPM and Start up PIN now..." -ForegroundColor Cyan - + # Get the key protectors of the OS Drive - [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector - + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + # Backup the recovery code of the OS drive in a file just in case - This is for when the disk is automatically encrypted and using TPM + Recovery code by default - New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectors) -ItemType File -Force | Out-Null - + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null + Write-Host "The recovery password was backed up in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan - - do { + + do { [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)"; Read-Host -AsSecureString) [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) - + # Compare the PINs and make sure they match [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 # If the PINs match and they are at least 10 characters long, max 20 characters if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { - [securestring]$Pin = $Pin1 + [securestring]$Pin = $Pin1 } - else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } + else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } } - # Repeat this process until the entered PINs match and they are at least 10 characters long, max 20 characters + # Repeat this process until the entered PINs match and they are at least 10 characters long, max 20 characters until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) - + try { - Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -TpmAndPinProtector -Pin $Pin -ErrorAction Stop - Write-Host "`nPINs matched, enabling TPM and startup PIN now" -ForegroundColor Green + # Add TPM + PIN key protectors to the OS Drive + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -TpmAndPinProtector -Pin $Pin -ErrorAction Stop | Out-Null + Write-SmartText -C MintGreen -G Green -I "`nPINs matched, enabling TPM and startup PIN now" } - catch { + catch { Write-Host 'These errors occured, run Bitlocker category again after meeting the requirements' -ForegroundColor Red $_ break } - } - } + } + } } # Do this if Bitlocker is not enabled for the OS drive else { - Write-Host "`nBitlocker is not enabled for the OS Drive, activating it now..." -ForegroundColor Yellow - do { + Write-Host "`nBitlocker is not enabled for the OS Drive, activating it now..." -ForegroundColor Yellow + do { [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I 'Enter a Pin for Bitlocker startup (between 10 to 20 characters)'; Read-Host -AsSecureString) [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) - + [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 - - if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { - [securestring]$Pin = $Pin1 + + if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { + [securestring]$Pin = $Pin1 } - else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } - } + else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } + } until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) try { + # Enable BitLocker for the OS Drive with TPM + PIN key protectors Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod 'XtsAes256' -Pin $Pin -TpmAndPinProtector -SkipHardwareTest -ErrorAction Stop *> $null } catch { Write-Host 'These errors occured, run Bitlocker category again after meeting the requirements' -ForegroundColor Red $_ break - } + } + # Add recovery password key protector to the OS Drive Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it - [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector # Backup the recovery code of the OS drive in a file - New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectors) -ItemType File -Force | Out-Null + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null Resume-BitLocker -MountPoint $env:SystemDrive | Out-Null - Write-Host "`nBitlocker is now fully and securely enabled for OS drive" -ForegroundColor Green + Write-SmartText -C MintGreen -G Green -I "`nBitlocker is now fully and securely enabled for OS drive" Write-Host "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan } @@ -1067,9 +1072,8 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Non-OS-BitLocker-Drives-Detection # Get the list of non OS volumes - [System.Object[]]$NonOSBitLockerVolumes = Get-BitLockerVolume | Where-Object { - ($_.volumeType -ne 'OperatingSystem') - } + [System.Object[]]$NonOSBitLockerVolumes = Get-BitLockerVolume | + Where-Object { ($_.volumeType -ne 'OperatingSystem') } # Get all the volumes and filter out removable ones [System.Object[]]$RemovableVolumes = Get-Volume | @@ -1085,15 +1089,14 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } # Filter out removable drives from BitLocker volumes to process - $NonOSBitLockerVolumes = $NonOSBitLockerVolumes | Where-Object { - ($_.MountPoint -notin $RemovableVolumesLetters) - } + $NonOSBitLockerVolumes = $NonOSBitLockerVolumes | + Where-Object { ($_.MountPoint -notin $RemovableVolumesLetters) } } #endregion Non-OS-BitLocker-Drives-Detection # Check if there is any non-OS volumes - if ($NonOSBitLockerVolumes) { + if ($NonOSBitLockerVolumes) { # Loop through each non-OS volume and prompt for encryption foreach ($MountPoint in $($NonOSBitLockerVolumes | Sort-Object).MountPoint) { @@ -1115,16 +1118,16 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va Write-Host "Drive $MountPoint encryption is currently at $EncryptionPercentageVar percent." -ForegroundColor Magenta break } - } - + } + # Check to see if Bitlocker is already turned on for the user selected drive - # if it is, perform multiple checks on its key protectors + # if it is, perform multiple checks on its key protectors if ((Get-BitLockerVolume -MountPoint $MountPoint).ProtectionStatus -eq 'on') { # Get the key protector types of the Non-OS Drive [System.String[]]$KeyProtectorTypesNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector.keyprotectortype - - # Check 1: if Recovery Password and Auto Unlock key protectors are available on the drive + + # If Recovery Password and Auto Unlock key protectors are available on the drive if ($KeyProtectorTypesNonOS -contains 'RecoveryPassword' -and $KeyProtectorTypesNonOS -contains 'ExternalKey') { # Additional Check 1: if there are more than 1 ExternalKey key protector, try delete all of them and add a new one @@ -1134,8 +1137,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va ForEach-Object { # -ErrorAction SilentlyContinue makes sure no error is thrown if the drive only has 1 External key key protector # and it's being used to unlock the drive - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue } + + # Renew the External key of the selected Non-OS Drive Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null # Additional Check 2: if there are more than 1 Recovery Password, delete all of them and add a new one @@ -1149,53 +1154,57 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" Write-Host $BitLockerMsg -ForegroundColor Yellow + # Remove all of the recovery password key protectors of the selected Non-OS Drive $RecoveryPasswordKeyProtectors | ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ } - # Add new Recovery Password key protector after removing the previous ones + # Add a new Recovery Password key protector after removing all of the previous ones Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> $null - + # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector - + # Backup the recovery code of the Non-OS drive in a file New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null - - } - Write-Host "`nBitlocker is already fully and securely enabled for drive $MountPoint" -ForegroundColor Green - + + } + Write-SmartText -C MintGreen -G Green -I "`nBitlocker is already fully and securely enabled for drive $MountPoint" + # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it + # Just to simply display it on the console for the user [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector - + Write-SmartText -C Fuchsia -GenericColor Magenta -I "Here is your 48-digits recovery password for drive $MountPoint in case you were looking for it:" Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectorsNonOS | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" - - } - + + } + # If the selected drive has Auto Unlock key protector but doesn't have Recovery Password elseif ($KeyProtectorTypesNonOS -contains 'ExternalKey' -and $KeyProtectorTypesNonOS -notcontains 'RecoveryPassword' ) { - + # if there are more than 1 ExternalKey key protector, try delete all of them and add a new one # The external key protector that is being used to unlock the drive will not be deleted - ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | + ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | Where-Object { $_.keyprotectortype -eq 'ExternalKey' }).KeyProtectorId | ForEach-Object { # -ErrorAction SilentlyContinue makes sure no error is thrown if the drive only has 1 External key key protector # and it's being used to unlock the drive - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue } + + # Renew the External key of the selected Non-OS Drive Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null - # Add Recovery Password Key protector and save it to a file inside the drive + # Add Recovery Password Key protector and save it to a file inside the drive Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> $null # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector - + # Backup the recovery code of the Non-OS drive in a file New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null - + [System.String]$BitLockerMsg = "`nDrive $MountPoint is auto-unlocked but doesn't have Recovery Password, adding it now... `n" + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" Write-Host $BitLockerMsg -ForegroundColor Cyan @@ -1203,10 +1212,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # Check 3: If the selected drive has Recovery Password key protector but doesn't have Auto Unlock enabled elseif ($KeyProtectorTypesNonOS -contains 'RecoveryPassword' -and $KeyProtectorTypesNonOS -notcontains 'ExternalKey') { - + # Add Auto-unlock (a.k.a ExternalKey key protector to the drive) Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null - + # if there are more than 1 Recovery Password, delete all of them and add a new one [System.String[]]$RecoveryPasswordKeyProtectors = ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).KeyProtectorId @@ -1216,8 +1225,8 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [System.String]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + 'Removing all of them and adding a new one.' + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" - Write-Host $BitLockerMsg -ForegroundColor Yellow - + Write-Host $BitLockerMsg -ForegroundColor Yellow + # Delete all Recovery Passwords because there were more than 1 $RecoveryPasswordKeyProtectors | ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ @@ -1228,10 +1237,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector - + # Backup the recovery code of the Non-OS drive in a file New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null - } + } } } @@ -1239,23 +1248,22 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va else { # Enable BitLocker with RecoveryPassword key protector for the selected Non-OS drive Enable-BitLocker -MountPoint $MountPoint -RecoveryPasswordProtector *> $null - + # Add Auto-unlock (a.k.a ExternalKey key protector to the drive) Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector - + # Backup the recovery code of the Non-OS drive in a file New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null - - Write-Host "`nBitLocker has started encrypting drive $MountPoint" -ForegroundColor Green - Write-Host "Recovery password will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" -ForegroundColor Cyan - } + Write-SmartText -C MintGreen -G Green -I "`nBitLocker has started encrypting drive $MountPoint" + Write-Host "Recovery password will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" -ForegroundColor Cyan + } } 'No' { break } 'Exit' { &$CleanUp } - } + } } } } 'No' { break } @@ -1839,15 +1847,15 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va Write-Progress -Id 0 -Activity 'Country IP Blocking' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) # -RemoteAddress in New-NetFirewallRule accepts array according to Microsoft Docs, - # so we use "[string[]]$IPList = $IPList -split '\r?\n' -ne ''" to convert the IP lists, which is a single multiline string, into an array + # so we use "[System.String[]]$IPList = $IPList -split '\r?\n' -ne ''" to convert the IP lists, which is a single multiline string, into an array function Block-CountryIP { - param ([string[]]$IPList , [System.String]$ListName) + param ([System.String[]]$IPList , [System.String]$ListName) # deletes previous rules (if any) to get new up-to-date IP ranges from the sources and set new rules Remove-NetFirewallRule -DisplayName "$ListName IP range blocking" -PolicyStore localhost -ErrorAction SilentlyContinue # converts the list which is in string into array - [string[]]$IPList = $IPList -split '\r?\n' -ne '' + [System.String[]]$IPList = $IPList -split '\r?\n' -ne '' # makes sure the list isn't empty if ($IPList.count -eq 0) { diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index 124c7b8ef..6907f7e63 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -106,7 +106,7 @@ Set-ExecutionPolicy Bypass -Scope Process function Select-Option { param( [parameter(Mandatory = $True)][System.String]$Message, # Contains the main prompt message - [parameter(Mandatory = $True)][string[]]$Options, + [parameter(Mandatory = $True)][System.String[]]$Options, [parameter(Mandatory = $false)][switch]$SubCategory, [parameter(Mandatory = $false)][System.String]$ExtraMessage # Contains any extra notes for sub-categories ) @@ -322,7 +322,7 @@ if (Test-IsAdmin) { # backup the current allowed apps list in Controlled folder access in order to restore them at the end of the script # doing this so that when we Add and then Remove PowerShell executables in Controlled folder access exclusions # no user customization will be affected - [string[]]$CFAAllowedAppsBackup = $MDAVPreferencesCurrent.ControlledFolderAccessAllowedApplications + [System.String[]]$CFAAllowedAppsBackup = $MDAVPreferencesCurrent.ControlledFolderAccessAllowedApplications # Temporarily allow the currently running PowerShell executables to the Controlled Folder Access allowed apps # so that the script can run without interruption. This change is reverted at the end. @@ -929,24 +929,25 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } # check if Bitlocker is enabled for the system drive - if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { - + if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { + # Get the key protectors of the OS Drive - [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector # Get the key protector types of the OS Drive - [System.String[]]$KeyProtectorTypes = $KeyProtectors.keyprotectortype + [System.String[]]$KeyProtectorTypesOSDrive = $KeyProtectorsOSDrive.keyprotectortype # check if TPM + PIN + recovery password are being used as key protectors for the OS Drive - if ($KeyProtectorTypes -contains 'Tpmpin' -and $KeyProtectorTypes -contains 'recoveryPassword') { - Write-Host 'Bitlocker is already fully and securely enabled for the OS drive' -ForegroundColor Green - + if ($KeyProtectorTypesOSDrive -contains 'Tpmpin' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { + + Write-SmartText -C MintGreen -G Green -I 'Bitlocker is already fully and securely enabled for the OS drive' + Write-SmartText -C Fuchsia -GenericColor Magenta -I 'Here is your 48-digits recovery password for the OS drive in case you were looking for it:' - Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectors | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" - + Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectorsOSDrive | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" + } else { # if Bitlocker is using TPM + PIN but not recovery password (for key protectors) - if ($KeyProtectorTypes -contains 'Tpmpin' -and $KeyProtectorTypes -notcontains 'recoveryPassword') { + if ($KeyProtectorTypesOSDrive -contains 'Tpmpin' -and $KeyProtectorTypesOSDrive -notcontains 'recoveryPassword') { [System.String]$BitLockerMsg = "`nTPM and Startup PIN are available but the recovery password is missing, adding it now... `n" + "It will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" @@ -954,89 +955,92 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # Add RecoveryPasswordProtector key protector to the OS drive Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null - + # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it - [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector - + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + # Backup the recovery code of the OS drive in a file - New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectors) -ItemType File -Force | Out-Null - - } + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null + + } # if Bitlocker is using recovery password but not TPM + PIN as key protectors for the OS Drive - elseif ($KeyProtectorTypes -notcontains 'Tpmpin' -and $KeyProtectorTypes -contains 'recoveryPassword') { - + elseif ($KeyProtectorTypesOSDrive -notcontains 'Tpmpin' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { + Write-Host "`nTPM and Start up PIN are missing but recovery password is in place`nAdding TPM and Start up PIN now..." -ForegroundColor Cyan - + # Get the key protectors of the OS Drive - [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector - + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + # Backup the recovery code of the OS drive in a file just in case - This is for when the disk is automatically encrypted and using TPM + Recovery code by default - New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectors) -ItemType File -Force | Out-Null - + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null + Write-Host "The recovery password was backed up in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan - - do { + + do { [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)"; Read-Host -AsSecureString) [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) - + # Compare the PINs and make sure they match [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 # If the PINs match and they are at least 10 characters long, max 20 characters if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { - [securestring]$Pin = $Pin1 + [securestring]$Pin = $Pin1 } - else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } + else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } } - # Repeat this process until the entered PINs match and they are at least 10 characters long, max 20 characters + # Repeat this process until the entered PINs match and they are at least 10 characters long, max 20 characters until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) - + try { - Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -TpmAndPinProtector -Pin $Pin -ErrorAction Stop - Write-Host "`nPINs matched, enabling TPM and startup PIN now" -ForegroundColor Green + # Add TPM + PIN key protectors to the OS Drive + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -TpmAndPinProtector -Pin $Pin -ErrorAction Stop | Out-Null + Write-SmartText -C MintGreen -G Green -I "`nPINs matched, enabling TPM and startup PIN now" } - catch { + catch { Write-Host 'These errors occured, run Bitlocker category again after meeting the requirements' -ForegroundColor Red $_ break } - } - } + } + } } # Do this if Bitlocker is not enabled for the OS drive else { - Write-Host "`nBitlocker is not enabled for the OS Drive, activating it now..." -ForegroundColor Yellow - do { + Write-Host "`nBitlocker is not enabled for the OS Drive, activating it now..." -ForegroundColor Yellow + do { [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I 'Enter a Pin for Bitlocker startup (between 10 to 20 characters)'; Read-Host -AsSecureString) [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) - + [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 - - if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { - [securestring]$Pin = $Pin1 + + if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { + [securestring]$Pin = $Pin1 } - else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } - } + else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } + } until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) try { + # Enable BitLocker for the OS Drive with TPM + PIN key protectors Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod 'XtsAes256' -Pin $Pin -TpmAndPinProtector -SkipHardwareTest -ErrorAction Stop *> $null } catch { Write-Host 'These errors occured, run Bitlocker category again after meeting the requirements' -ForegroundColor Red $_ break - } + } + # Add recovery password key protector to the OS Drive Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it - [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector # Backup the recovery code of the OS drive in a file - New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectors) -ItemType File -Force | Out-Null + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null Resume-BitLocker -MountPoint $env:SystemDrive | Out-Null - Write-Host "`nBitlocker is now fully and securely enabled for OS drive" -ForegroundColor Green + Write-SmartText -C MintGreen -G Green -I "`nBitlocker is now fully and securely enabled for OS drive" Write-Host "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan } @@ -1068,9 +1072,8 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Non-OS-BitLocker-Drives-Detection # Get the list of non OS volumes - [System.Object[]]$NonOSBitLockerVolumes = Get-BitLockerVolume | Where-Object { - ($_.volumeType -ne 'OperatingSystem') - } + [System.Object[]]$NonOSBitLockerVolumes = Get-BitLockerVolume | + Where-Object { ($_.volumeType -ne 'OperatingSystem') } # Get all the volumes and filter out removable ones [System.Object[]]$RemovableVolumes = Get-Volume | @@ -1086,15 +1089,14 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } # Filter out removable drives from BitLocker volumes to process - $NonOSBitLockerVolumes = $NonOSBitLockerVolumes | Where-Object { - ($_.MountPoint -notin $RemovableVolumesLetters) - } + $NonOSBitLockerVolumes = $NonOSBitLockerVolumes | + Where-Object { ($_.MountPoint -notin $RemovableVolumesLetters) } } #endregion Non-OS-BitLocker-Drives-Detection # Check if there is any non-OS volumes - if ($NonOSBitLockerVolumes) { + if ($NonOSBitLockerVolumes) { # Loop through each non-OS volume and prompt for encryption foreach ($MountPoint in $($NonOSBitLockerVolumes | Sort-Object).MountPoint) { @@ -1116,16 +1118,16 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va Write-Host "Drive $MountPoint encryption is currently at $EncryptionPercentageVar percent." -ForegroundColor Magenta break } - } - + } + # Check to see if Bitlocker is already turned on for the user selected drive - # if it is, perform multiple checks on its key protectors + # if it is, perform multiple checks on its key protectors if ((Get-BitLockerVolume -MountPoint $MountPoint).ProtectionStatus -eq 'on') { # Get the key protector types of the Non-OS Drive [System.String[]]$KeyProtectorTypesNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector.keyprotectortype - - # Check 1: if Recovery Password and Auto Unlock key protectors are available on the drive + + # If Recovery Password and Auto Unlock key protectors are available on the drive if ($KeyProtectorTypesNonOS -contains 'RecoveryPassword' -and $KeyProtectorTypesNonOS -contains 'ExternalKey') { # Additional Check 1: if there are more than 1 ExternalKey key protector, try delete all of them and add a new one @@ -1135,8 +1137,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va ForEach-Object { # -ErrorAction SilentlyContinue makes sure no error is thrown if the drive only has 1 External key key protector # and it's being used to unlock the drive - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue } + + # Renew the External key of the selected Non-OS Drive Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null # Additional Check 2: if there are more than 1 Recovery Password, delete all of them and add a new one @@ -1150,53 +1154,57 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" Write-Host $BitLockerMsg -ForegroundColor Yellow + # Remove all of the recovery password key protectors of the selected Non-OS Drive $RecoveryPasswordKeyProtectors | ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ } - # Add new Recovery Password key protector after removing the previous ones + # Add a new Recovery Password key protector after removing all of the previous ones Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> $null - + # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector - + # Backup the recovery code of the Non-OS drive in a file New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null - - } - Write-Host "`nBitlocker is already fully and securely enabled for drive $MountPoint" -ForegroundColor Green - + + } + Write-SmartText -C MintGreen -G Green -I "`nBitlocker is already fully and securely enabled for drive $MountPoint" + # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it + # Just to simply display it on the console for the user [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector - + Write-SmartText -C Fuchsia -GenericColor Magenta -I "Here is your 48-digits recovery password for drive $MountPoint in case you were looking for it:" Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectorsNonOS | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" - - } - + + } + # If the selected drive has Auto Unlock key protector but doesn't have Recovery Password elseif ($KeyProtectorTypesNonOS -contains 'ExternalKey' -and $KeyProtectorTypesNonOS -notcontains 'RecoveryPassword' ) { - + # if there are more than 1 ExternalKey key protector, try delete all of them and add a new one # The external key protector that is being used to unlock the drive will not be deleted - ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | + ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | Where-Object { $_.keyprotectortype -eq 'ExternalKey' }).KeyProtectorId | ForEach-Object { # -ErrorAction SilentlyContinue makes sure no error is thrown if the drive only has 1 External key key protector # and it's being used to unlock the drive - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue } + + # Renew the External key of the selected Non-OS Drive Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null - # Add Recovery Password Key protector and save it to a file inside the drive + # Add Recovery Password Key protector and save it to a file inside the drive Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector *> $null # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector - + # Backup the recovery code of the Non-OS drive in a file New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null - + [System.String]$BitLockerMsg = "`nDrive $MountPoint is auto-unlocked but doesn't have Recovery Password, adding it now... `n" + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" Write-Host $BitLockerMsg -ForegroundColor Cyan @@ -1204,10 +1212,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # Check 3: If the selected drive has Recovery Password key protector but doesn't have Auto Unlock enabled elseif ($KeyProtectorTypesNonOS -contains 'RecoveryPassword' -and $KeyProtectorTypesNonOS -notcontains 'ExternalKey') { - + # Add Auto-unlock (a.k.a ExternalKey key protector to the drive) Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null - + # if there are more than 1 Recovery Password, delete all of them and add a new one [System.String[]]$RecoveryPasswordKeyProtectors = ((Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).KeyProtectorId @@ -1217,8 +1225,8 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [System.String]$BitLockerMsg = "`nThere are more than 1 recovery password key protector associated with the drive $mountpoint `n" + 'Removing all of them and adding a new one.' + "It will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" - Write-Host $BitLockerMsg -ForegroundColor Yellow - + Write-Host $BitLockerMsg -ForegroundColor Yellow + # Delete all Recovery Passwords because there were more than 1 $RecoveryPasswordKeyProtectors | ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ @@ -1229,10 +1237,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector - + # Backup the recovery code of the Non-OS drive in a file New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null - } + } } } @@ -1240,23 +1248,22 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va else { # Enable BitLocker with RecoveryPassword key protector for the selected Non-OS drive Enable-BitLocker -MountPoint $MountPoint -RecoveryPasswordProtector *> $null - + # Add Auto-unlock (a.k.a ExternalKey key protector to the drive) Enable-BitLockerAutoUnlock -MountPoint $MountPoint | Out-Null # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it [System.Object[]]$KeyProtectorsNonOS = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector - + # Backup the recovery code of the Non-OS drive in a file New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null - - Write-Host "`nBitLocker has started encrypting drive $MountPoint" -ForegroundColor Green - Write-Host "Recovery password will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" -ForegroundColor Cyan - } + Write-SmartText -C MintGreen -G Green -I "`nBitLocker has started encrypting drive $MountPoint" + Write-Host "Recovery password will be saved in a text file in '$($MountPoint)\Drive $($MountPoint.Remove(1)) recovery password.txt'" -ForegroundColor Cyan + } } 'No' { break } 'Exit' { &$CleanUp } - } + } } } } 'No' { break } @@ -1840,15 +1847,15 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va Write-Progress -Id 0 -Activity 'Country IP Blocking' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) # -RemoteAddress in New-NetFirewallRule accepts array according to Microsoft Docs, - # so we use "[string[]]$IPList = $IPList -split '\r?\n' -ne ''" to convert the IP lists, which is a single multiline string, into an array + # so we use "[System.String[]]$IPList = $IPList -split '\r?\n' -ne ''" to convert the IP lists, which is a single multiline string, into an array function Block-CountryIP { - param ([string[]]$IPList , [System.String]$ListName) + param ([System.String[]]$IPList , [System.String]$ListName) # deletes previous rules (if any) to get new up-to-date IP ranges from the sources and set new rules Remove-NetFirewallRule -DisplayName "$ListName IP range blocking" -PolicyStore localhost -ErrorAction SilentlyContinue # converts the list which is in string into array - [string[]]$IPList = $IPList -split '\r?\n' -ne '' + [System.String[]]$IPList = $IPList -split '\r?\n' -ne '' # makes sure the list isn't empty if ($IPList.count -eq 0) { From 6e6d758dc557cb6d30997440325b17345bec3e22 Mon Sep 17 00:00:00 2001 From: Violet Date: Thu, 16 Nov 2023 13:53:26 +0000 Subject: [PATCH 12/34] BitLocker category now has multifactor Auth Added multifactor authentication to the BitLocker category, allowing users to easily and quickly setup encryption for the OS drive using TPM + Startup PIN + Startup Key --- .../Main files/Harden-Windows-Security.ps1 | 455 ++++++++++++++---- Harden-Windows-Security.ps1 | 326 +++++++++---- 2 files changed, 613 insertions(+), 168 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index a8d22a0f0..e28297cbd 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -244,7 +244,7 @@ Function Write-SmartText { param ( [Parameter(Mandatory = $True)] [Alias('C')] - [ValidateSet('Fuchsia', 'Orange', 'NeonGreen', 'MintGreen', 'PinkBoldBlink', 'PinkBold', 'Rainbow' , 'Gold')] + [ValidateSet('Fuchsia', 'Orange', 'NeonGreen', 'MintGreen', 'PinkBoldBlink', 'PinkBold', 'Rainbow' , 'Gold', 'TeaGreenNoNewLine', 'LavenderNoNewLine', 'PinkNoNewLine', 'VioletNoNewLine', 'Violet', 'Pink')] [System.String]$CustomColor, [Parameter(Mandatory = $True)] @@ -279,7 +279,13 @@ Function Write-SmartText { 'MintGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(152,255,152))$InputText$($PSStyle.Reset)"; break } 'PinkBoldBlink' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Blink)$InputText$($PSStyle.Reset)"; break } 'PinkBold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Reverse)$InputText$($PSStyle.Reset)"; break } - 'Gold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,215,0))$($PSStyle.Bold)$($PSStyle.Reverse)$InputText$($PSStyle.Reset)"; break } + 'Gold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,215,0))$InputText$($PSStyle.Reset)"; break } + 'VioletNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRGB(153,0,255))$InputText$($PSStyle.Reset)" -NoNewline } + 'PinkNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRGB(255,0,230))$InputText$($PSStyle.Reset)" -NoNewline } + 'Violet' { Write-Host "$($PSStyle.Foreground.FromRGB(153,0,255))$InputText$($PSStyle.Reset)" -NoNewline } + 'Pink' { Write-Host "$($PSStyle.Foreground.FromRGB(255,0,230))$InputText$($PSStyle.Reset)" -NoNewline } + 'LavenderNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(255,179,255))$InputText$($PSStyle.Reset)" -NoNewline } + 'TeaGreenNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(133, 222, 119))$InputText$($PSStyle.Reset)" -NoNewline } 'Rainbow' { $colors = @( [System.Drawing.Color]::Pink, @@ -311,6 +317,135 @@ Function Write-SmartText { } } } + +# Function to get a removable drive to be used by BitLocker category +function Get-AvailableRemovableDrives { + + # Grab the list of volumes that are removable and have drive letter, display their size in GBs instead of Bytes + [System.Object[]]$AvailableRemovableDrives = Get-Volume | Where-Object { $_.DriveLetter -and $_.DriveType -eq 'Removable' } | + Sort-Object -Property DriveLetter | + Select-Object DriveLetter, FileSystemType, DriveType, @{Name = 'Size'; Expression = { '{0:N2}' -f ($_.Size / 1GB) + ' GB' } } + + + if (!$AvailableRemovableDrives) { + do { + switch (Select-Option -Options 'Check for removable flash drives again', 'Skip encryptions altogether', 'Exit' -Message "`nNo removable flash drives found. Please insert a USB flash drive") { + 'Check for removable flash drives again' { + [System.Object[]]$AvailableRemovableDrives = Get-Volume | Where-Object { $_.DriveLetter -and $_.DriveType -eq 'Removable' } | + Sort-Object -Property DriveLetter | + Select-Object DriveLetter, FileSystemType, DriveType, @{Name = 'Size'; Expression = { '{0:N2}' -f ($_.Size / 1GB) + ' GB' } } + } + 'Skip encryptions altogether' { break BitLockerCategoryLabel } + 'Exit' { &$CleanUp } + } + } + until ($AvailableRemovableDrives) + } + + # Initialize the maximum length variables but make sure the column widths are at least as wide as their titles such as 'DriveLetter' or 'FileSystemType' etc. + [int]$DriveLetterLength = 10 + [int]$FileSystemTypeLength = 13 + [int]$DriveTypeLength = 8 + [int]$SizeLength = 3 + + # Loop through each element in the array + foreach ($drive in $AvailableRemovableDrives) { + # Compare the length of the current element with the maximum length and update if needed + if ($drive.DriveLetter.Length -gt $DriveLetterLength) { + $DriveLetterLength = $drive.DriveLetter.Length + } + if ($drive.FileSystemType.Length -gt $FileSystemTypeLength) { + $FileSystemTypeLength = $drive.FileSystemType.Length + } + if ($drive.DriveType.Length -gt $DriveTypeLength) { + $DriveTypeLength = $drive.DriveType.Length + } + if (($drive.Size | Measure-Object -Character).Characters -gt $SizeLength) { + # The method below is used to calculate size of the string that consists only number, but since it now has "GB" in it, it's no longer needed + # $SizeLength = ($drive.Size | Measure-Object -Character).Characters + $SizeLength = $drive.Size.Length + } + } + + # Add 3 to each maximum length for spacing + $DriveLetterLength += 3 + $FileSystemTypeLength += 3 + $DriveTypeLength += 3 + $SizeLength += 3 + + # Creating a heading for the columns + # Write the index of the drive + Write-SmartText -C LavenderNoNewLine -G Blue -I ('{0,-4}' -f '#') + # Write the name of the drive + Write-SmartText -C TeaGreenNoNewLine -G Yellow -I ("|{0,-$DriveLetterLength}" -f 'DriveLetter') + # Write the File System Type of the drive + Write-SmartText -C PinkNoNewLine -G Magenta -I ("|{0,-$FileSystemTypeLength}" -f 'FileSystemType') + # Write the Drive Type of the drive + Write-SmartText -C VioletNoNewLine -G Green -I ("|{0,-$DriveTypeLength}" -f 'DriveType') + # Write the Size of the drive + Write-SmartText -C Gold -G Cyan ("|{0,-$SizeLength}" -f 'Size') + + # Loop through the drives and display them in a table with colors + for ($i = 0; $i -lt $AvailableRemovableDrives.Count; $i++) { + # Write the index of the drive + Write-SmartText -C LavenderNoNewLine -G Blue -I ('{0,-4}' -f ($i + 1)) + # Write the name of the drive + Write-SmartText -C TeaGreenNoNewLine -G Yellow -I ("|{0,-$DriveLetterLength}" -f $AvailableRemovableDrives[$i].DriveLetter) + # Write the File System Type of the drive + Write-SmartText -C PinkNoNewLine -G Magenta -I ("|{0,-$FileSystemTypeLength}" -f $AvailableRemovableDrives[$i].FileSystemType) + # Write the Drive Type of the drive + Write-SmartText -C VioletNoNewLine -G Green -I ("|{0,-$DriveTypeLength}" -f $AvailableRemovableDrives[$i].DriveType) + # Write the Size of the drive + Write-SmartText -C Gold -G Cyan ("|{0,-$SizeLength}" -f $AvailableRemovableDrives[$i].Size) + } + + # Get the max count of available network drives and add 1 to it, assign the number as exit value to break the loop when selected + [int]$ExitCodeRemovableDriveSelection = $AvailableRemovableDrives.Count + 1 + + # Write an exit option at the end of the table + Write-Host ('{0,-4}' -f "$ExitCodeRemovableDriveSelection") -NoNewline -ForegroundColor DarkRed + Write-Host '|Skip encryptions altogether' -ForegroundColor DarkRed + + # A function to validate the user input + function Confirm-Choice { + param([string]$Choice) + # Initialize a flag to indicate if the input is valid or not + [bool]$IsValid = $false + # Initialize a variable to store the parsed integer value + [int]$ParsedChoice = 0 + # Try to parse the input as an integer + # If the parsing succeeded, check if the input is within the range + if ([int]::TryParse($Choice, [ref]$ParsedChoice)) { + if ($ParsedChoice -in 1..$ExitCodeRemovableDriveSelection) { + $IsValid = $true + break + } + } + # Return the flag value + return $IsValid + } + + # Prompt the user to enter the number of the drive they want to select, or exit value to exit, until they enter a valid input + do { + # Read the user input as a string + [string]$Choice = $(Write-Host "Enter the number of the drive you want to select or press $ExitCodeRemovableDriveSelection to Cancel" -ForegroundColor cyan; Read-Host) + + # Check if the input is valid using the Confirm-Choice function + if (-not (Confirm-Choice $Choice)) { + # Write an error message in red if invalid + Write-Host "Invalid input. Please enter a number between 1 and $ExitCodeRemovableDriveSelection." -ForegroundColor Red + } + } while (-not (Confirm-Choice $Choice)) + + # Check if the user entered the exit value to break out of the loop + if ($Choice -eq $ExitCodeRemovableDriveSelection) { + break BitLockerCategoryLabel + } + else { + # Get the selected drive from the array and display it + return ($($AvailableRemovableDrives[$Choice - 1]).DriveLetter + ':') + } +} #endregion functions if (Test-IsAdmin) { @@ -810,7 +945,7 @@ try { #region Bitlocker-Settings # ==========================================Bitlocker Settings============================================================= $CurrentMainStep++ - switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Bitlocker category ?") { + :BitLockerCategoryLabel switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Bitlocker category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Bitlocker Settings' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -889,8 +1024,8 @@ try { $CdDvdCheck = (Get-CimInstance -ClassName Win32_CDROMDrive -Property *).MediaLoaded if ($CdDvdCheck) { Write-Warning 'Remove any CD/DVD drives or mounted images/ISO from the system and run the Bitlocker category again.' - # break from the current loop and continue to the next hardening category - break + # break from the entire BitLocker category and continue to the next category + break BitLockerCategoryLabel } # check make sure Bitlocker isn't in the middle of decryption/encryption operation (on System Drive) @@ -898,7 +1033,8 @@ try { $EncryptionPercentageVar = (Get-BitLockerVolume -MountPoint $env:SystemDrive).EncryptionPercentage Write-Host "`nPlease wait for Bitlocker to finish encrypting or decrypting the Operation System Drive." -ForegroundColor Yellow Write-Host "Drive $env:SystemDrive encryption is currently at $EncryptionPercentageVar percent." -ForegroundColor Yellow - break + # break from the entire BitLocker category and continue to the next category + break BitLockerCategoryLabel } # A script block that generates recovery code just like the Windows does @@ -927,33 +1063,116 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va "@ } + + switch (Select-Option -SubCategory -Options 'Normal: TPM + Startup PIN + Recovery Password', 'Enhanced: TPM + Startup PIN + Startup Key + Recovery Password', 'Skip encryptions altogether', 'Exit' -Message "`nPlease select your desired security level" -ExtraMessage "If you are not sure, refer to the BitLocker category in the GitHub Readme`n") { + 'Normal: TPM + Startup PIN + Recovery Password' { - # check if Bitlocker is enabled for the system drive - if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { + # check if Bitlocker is enabled for the system drive with Normal security level + if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { - # Get the key protectors of the OS Drive - [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector - # Get the key protector types of the OS Drive - [System.String[]]$KeyProtectorTypesOSDrive = $KeyProtectorsOSDrive.keyprotectortype + # Get the key protectors of the OS Drive + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + # Get the key protector types of the OS Drive + [System.String[]]$KeyProtectorTypesOSDrive = $KeyProtectorsOSDrive.keyprotectortype - # check if TPM + PIN + recovery password are being used as key protectors for the OS Drive - if ($KeyProtectorTypesOSDrive -contains 'Tpmpin' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { + # check if TPM + PIN + recovery password are being used as key protectors for the OS Drive + if ($KeyProtectorTypesOSDrive -contains 'Tpmpin' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { - Write-SmartText -C MintGreen -G Green -I 'Bitlocker is already fully and securely enabled for the OS drive' + Write-SmartText -C MintGreen -G Green -I 'Bitlocker is already enabled for the OS drive with Normal security level.' - Write-SmartText -C Fuchsia -GenericColor Magenta -I 'Here is your 48-digits recovery password for the OS drive in case you were looking for it:' - Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectorsOSDrive | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" + Write-SmartText -C Fuchsia -GenericColor Magenta -I 'Here is your 48-digits recovery password for the OS drive in case you were looking for it:' + Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectorsOSDrive | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" - } - else { - # if Bitlocker is using TPM + PIN but not recovery password (for key protectors) - if ($KeyProtectorTypesOSDrive -contains 'Tpmpin' -and $KeyProtectorTypesOSDrive -notcontains 'recoveryPassword') { + } + else { + + # If the OS Drive doesn't have recovery password key protector + if ($KeyProtectorTypesOSDrive -notcontains 'recoveryPassword') { + + [System.String]$BitLockerMsg = "`nThe recovery password is missing, adding it now... `n" + + "It will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" + Write-Host $BitLockerMsg -ForegroundColor Yellow + + # Add RecoveryPasswordProtector key protector to the OS drive + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null + + # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector - [System.String]$BitLockerMsg = "`nTPM and Startup PIN are available but the recovery password is missing, adding it now... `n" + - "It will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" - Write-Host $BitLockerMsg -ForegroundColor Yellow + # Backup the recovery code of the OS drive in a file + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null + + } + + # If the OS Drive doesn't have (TPM + PIN) key protector + if ($KeyProtectorTypesOSDrive -notcontains 'Tpmpin') { + + Write-Host "`nTPM and Start up PIN are missing, adding them now..." -ForegroundColor Cyan + + do { + [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)"; Read-Host -AsSecureString) + [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) + + # Compare the PINs and make sure they match + [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 + # If the PINs match and they are at least 10 characters long, max 20 characters + if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { + [securestring]$Pin = $Pin1 + } + else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } + } + # Repeat this process until the entered PINs match and they are at least 10 characters long, max 20 characters + until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) + + try { + # Add TPM + PIN key protectors to the OS Drive + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -TpmAndPinProtector -Pin $Pin -ErrorAction Stop | Out-Null + Write-SmartText -C MintGreen -G Green -I "`nPINs matched, enabling TPM and startup PIN now`n" + } + catch { + Write-Host 'These errors occured, run Bitlocker category again after meeting the requirements' -ForegroundColor Red + $_ + break BitLockerCategoryLabel + } + + # Get the key protectors of the OS Drive + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + + # Backup the recovery code of the OS drive in a file just in case - This is for when the disk is automatically encrypted and using TPM + Recovery code by default + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null + + Write-Host "The recovery password was backed up in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan + + } + } + } + + # Do this if Bitlocker is not enabled for the OS drive at all + else { + Write-Host "`nBitlocker is not enabled for the OS Drive, activating it now..." -ForegroundColor Yellow + do { + [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I 'Enter a Pin for Bitlocker startup (between 10 to 20 characters)'; Read-Host -AsSecureString) + [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) + + [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 + + if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { + [securestring]$Pin = $Pin1 + } + else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } + } + until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) - # Add RecoveryPasswordProtector key protector to the OS drive + try { + # Enable BitLocker for the OS Drive with TPM + PIN key protectors + Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod 'XtsAes256' -Pin $Pin -TpmAndPinProtector -SkipHardwareTest -ErrorAction Stop *> $null + } + catch { + Write-Host 'These errors occured, run Bitlocker category again after meeting the requirements' -ForegroundColor Red + $_ + break BitLockerCategoryLabel + } + # Add recovery password key protector to the OS Drive Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it @@ -961,25 +1180,115 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # Backup the recovery code of the OS drive in a file New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null - - } - # if Bitlocker is using recovery password but not TPM + PIN as key protectors for the OS Drive - elseif ($KeyProtectorTypesOSDrive -notcontains 'Tpmpin' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { - - Write-Host "`nTPM and Start up PIN are missing but recovery password is in place`nAdding TPM and Start up PIN now..." -ForegroundColor Cyan + Resume-BitLocker -MountPoint $env:SystemDrive | Out-Null + + Write-SmartText -C MintGreen -G Green -I "`nBitlocker is now enabled for the OS drive with Normal security level." + Write-Host "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan + } + + } + 'Enhanced: TPM + Startup PIN + Startup Key + Recovery Password' { + + # check if Bitlocker is enabled for the system drive with Enhanced security level + if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { + # Get the key protectors of the OS Drive [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + # Get the key protector types of the OS Drive + [System.String[]]$KeyProtectorTypesOSDrive = $KeyProtectorsOSDrive.keyprotectortype + + # check if TPM + PIN + recovery password are being used as key protectors for the OS Drive + if ($KeyProtectorTypesOSDrive -contains 'TpmAndPinAndStartupKeyProtector' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { + + Write-SmartText -C MintGreen -G Green -I 'Bitlocker is already enabled for the OS drive with Enhanced security level.' + + Write-SmartText -C Fuchsia -GenericColor Magenta -I 'Here is your 48-digits recovery password for the OS drive in case you were looking for it:' + Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectorsOSDrive | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" + + } + else { + + # If the OS Drive doesn't have recovery password key protector + if ($KeyProtectorTypesOSDrive -notcontains 'recoveryPassword') { + + [System.String]$BitLockerMsg = "`nThe recovery password is missing, adding it now... `n" + + "It will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" + Write-Host $BitLockerMsg -ForegroundColor Yellow + + # Add RecoveryPasswordProtector key protector to the OS drive + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null + + # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + + # Backup the recovery code of the OS drive in a file + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null - # Backup the recovery code of the OS drive in a file just in case - This is for when the disk is automatically encrypted and using TPM + Recovery code by default - New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null + } + + # If the OS Drive doesn't have (TpmAndPinAndStartupKeyProtector) key protector + if ($KeyProtectorTypesOSDrive -notcontains 'TpmAndPinAndStartupKeyProtector') { + + Write-SmartText -C Violet -G Cyan -I "`nTpm And Pin And StartupKey Protector is missing from the OS Drive, adding it now`n" + + # Check if the OS drive has TpmPinStartupKey key protector and if it does remove it + # Otherwise when trying to add TpmAndPinAndStartupKeyProtector to it there will be an error saying they both can't exist at the same time + if ($KeyProtectorTypesOSDrive -contains 'TpmPinStartupKey') { + + (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector | + Where-Object { $_.keyprotectortype -eq 'TpmPinStartupKey' } | + ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $env:SystemDrive -KeyProtectorId $_.KeyProtectorId | Out-Null } + + } + + do { + [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)"; Read-Host -AsSecureString) + [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) + + # Compare the PINs and make sure they match + [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 + # If the PINs match and they are at least 10 characters long, max 20 characters + if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { + [securestring]$Pin = $Pin1 + } + else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } + } + # Repeat this process until the entered PINs match and they are at least 10 characters long, max 20 characters + until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) + + Write-SmartText -C MintGreen -G Green -I "`nPINs matched, enabling TPM, Startup PIN and Startup Key protector now`n" + + try { + # Add TpmAndPinAndStartupKeyProtector to the OS Drive + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -TpmAndPinAndStartupKeyProtector -StartupKeyPath (Get-AvailableRemovableDrives) -Pin $Pin -ErrorAction Stop | Out-Null + } + catch { + Write-Error -Message 'There was a problem adding Startup Key to the removable drive, try ejecting and reinserting the flash drive into your device and run this category again.' + $_ + break BitLockerCategoryLabel + } + + # Get the key protectors of the OS Drive + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector - Write-Host "The recovery password was backed up in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan + # Backup the recovery code of the OS drive in a file just in case - This is for when the disk is automatically encrypted and using TPM + Recovery code by default + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null + + Write-Host "The recovery password was backed up in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan + } + } + } + + # Do this if Bitlocker is not enabled for the OS drive at all + else { + Write-Host "`nBitlocker is not enabled for the OS Drive, activating it now..." -ForegroundColor Yellow + do { [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)"; Read-Host -AsSecureString) [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) - + # Compare the PINs and make sure they match [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 # If the PINs match and they are at least 10 characters long, max 20 characters @@ -990,58 +1299,36 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } # Repeat this process until the entered PINs match and they are at least 10 characters long, max 20 characters until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) - + + Write-SmartText -C MintGreen -G Green -I "`nPINs matched, enabling TPM, Startup PIN and Startup Key protector now`n" + try { - # Add TPM + PIN key protectors to the OS Drive - Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -TpmAndPinProtector -Pin $Pin -ErrorAction Stop | Out-Null - Write-SmartText -C MintGreen -G Green -I "`nPINs matched, enabling TPM and startup PIN now" + # Add TpmAndPinAndStartupKeyProtector to the OS Drive + Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod 'XtsAes256' -TpmAndPinAndStartupKeyProtector -StartupKeyPath (Get-AvailableRemovableDrives) -Pin $Pin -SkipHardwareTest -ErrorAction Stop *> $null } catch { - Write-Host 'These errors occured, run Bitlocker category again after meeting the requirements' -ForegroundColor Red + Write-Error -Message 'There was a problem adding Startup Key to the removable drive, try ejecting and reinserting the flash drive into your device and run this category again.' $_ - break + break BitLockerCategoryLabel } - } - } - } - # Do this if Bitlocker is not enabled for the OS drive - else { - Write-Host "`nBitlocker is not enabled for the OS Drive, activating it now..." -ForegroundColor Yellow - do { - [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I 'Enter a Pin for Bitlocker startup (between 10 to 20 characters)'; Read-Host -AsSecureString) - [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) - - [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 - - if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { - [securestring]$Pin = $Pin1 - } - else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } - } - until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) - - try { - # Enable BitLocker for the OS Drive with TPM + PIN key protectors - Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod 'XtsAes256' -Pin $Pin -TpmAndPinProtector -SkipHardwareTest -ErrorAction Stop *> $null - } - catch { - Write-Host 'These errors occured, run Bitlocker category again after meeting the requirements' -ForegroundColor Red - $_ - break - } - # Add recovery password key protector to the OS Drive - Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null + + # Add recovery password key protector to the OS Drive + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null - # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it - [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector - # Backup the recovery code of the OS drive in a file - New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null + # Backup the recovery code of the OS drive in a file + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null - Resume-BitLocker -MountPoint $env:SystemDrive | Out-Null + Resume-BitLocker -MountPoint $env:SystemDrive | Out-Null - Write-SmartText -C MintGreen -G Green -I "`nBitlocker is now fully and securely enabled for OS drive" - Write-Host "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan + Write-SmartText -C MintGreen -G Green -I "`nBitlocker is now enabled for the OS drive with Enhanced security level." + Write-Host "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan + } + } + 'Skip encryptions altogether' { break BitLockerCategoryLabel } # Exit the entire BitLocker category, only + 'Exit' { &$CleanUp } } # Enabling Hibernate after making sure OS drive is property encrypted for holding hibernate data @@ -1065,7 +1352,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va Remove-MpPreference -ControlledFolderAccessAllowedApplications 'C:\Windows\System32\powercfg.exe' } else { - Write-Host 'Hibernate is already set to full.' -ForegroundColor Magenta + Write-SmartText -C Pink -G Magenta -I "`nHibernate is already set to full.`n" } } @@ -1137,7 +1424,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va ForEach-Object { # -ErrorAction SilentlyContinue makes sure no error is thrown if the drive only has 1 External key key protector # and it's being used to unlock the drive - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue | Out-Null } # Renew the External key of the selected Non-OS Drive @@ -1156,7 +1443,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # Remove all of the recovery password key protectors of the selected Non-OS Drive $RecoveryPasswordKeyProtectors | ForEach-Object { - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ | Out-Null } # Add a new Recovery Password key protector after removing all of the previous ones @@ -1169,7 +1456,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null } - Write-SmartText -C MintGreen -G Green -I "`nBitlocker is already fully and securely enabled for drive $MountPoint" + Write-SmartText -C MintGreen -G Green -I "`nBitlocker is already securely enabled for drive $MountPoint" # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it # Just to simply display it on the console for the user @@ -1190,7 +1477,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va ForEach-Object { # -ErrorAction SilentlyContinue makes sure no error is thrown if the drive only has 1 External key key protector # and it's being used to unlock the drive - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue | Out-Null } # Renew the External key of the selected Non-OS Drive @@ -1229,7 +1516,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # Delete all Recovery Passwords because there were more than 1 $RecoveryPasswordKeyProtectors | ForEach-Object { - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ | Out-Null } # Add a new Recovery Password diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index 6907f7e63..43feea43c 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -244,7 +244,7 @@ Function Write-SmartText { param ( [Parameter(Mandatory = $True)] [Alias('C')] - [ValidateSet('Fuchsia', 'Orange', 'NeonGreen', 'MintGreen', 'PinkBoldBlink', 'PinkBold', 'Rainbow' , 'Gold')] + [ValidateSet('Fuchsia', 'Orange', 'NeonGreen', 'MintGreen', 'PinkBoldBlink', 'PinkBold', 'Rainbow' , 'Gold', 'TeaGreenNoNewLine', 'LavenderNoNewLine', 'PinkNoNewLine', 'VioletNoNewLine', 'Violet', 'Pink')] [System.String]$CustomColor, [Parameter(Mandatory = $True)] @@ -279,7 +279,13 @@ Function Write-SmartText { 'MintGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(152,255,152))$InputText$($PSStyle.Reset)"; break } 'PinkBoldBlink' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Blink)$InputText$($PSStyle.Reset)"; break } 'PinkBold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Reverse)$InputText$($PSStyle.Reset)"; break } - 'Gold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,215,0))$($PSStyle.Bold)$($PSStyle.Reverse)$InputText$($PSStyle.Reset)"; break } + 'Gold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,215,0))$InputText$($PSStyle.Reset)"; break } + 'VioletNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRGB(153,0,255))$InputText$($PSStyle.Reset)" -NoNewline } + 'PinkNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRGB(255,0,230))$InputText$($PSStyle.Reset)" -NoNewline } + 'Violet' { Write-Host "$($PSStyle.Foreground.FromRGB(153,0,255))$InputText$($PSStyle.Reset)" -NoNewline } + 'Pink' { Write-Host "$($PSStyle.Foreground.FromRGB(255,0,230))$InputText$($PSStyle.Reset)" -NoNewline } + 'LavenderNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(255,179,255))$InputText$($PSStyle.Reset)" -NoNewline } + 'TeaGreenNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(133, 222, 119))$InputText$($PSStyle.Reset)" -NoNewline } 'Rainbow' { $colors = @( [System.Drawing.Color]::Pink, @@ -810,7 +816,7 @@ try { #region Bitlocker-Settings # ==========================================Bitlocker Settings============================================================= $CurrentMainStep++ - switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Bitlocker category ?") { + :BitLockerCategoryLabel switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Bitlocker category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Bitlocker Settings' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -889,8 +895,8 @@ try { $CdDvdCheck = (Get-CimInstance -ClassName Win32_CDROMDrive -Property *).MediaLoaded if ($CdDvdCheck) { Write-Warning 'Remove any CD/DVD drives or mounted images/ISO from the system and run the Bitlocker category again.' - # break from the current loop and continue to the next hardening category - break + # break from the entire BitLocker category and continue to the next category + break BitLockerCategoryLabel } # check make sure Bitlocker isn't in the middle of decryption/encryption operation (on System Drive) @@ -898,7 +904,8 @@ try { $EncryptionPercentageVar = (Get-BitLockerVolume -MountPoint $env:SystemDrive).EncryptionPercentage Write-Host "`nPlease wait for Bitlocker to finish encrypting or decrypting the Operation System Drive." -ForegroundColor Yellow Write-Host "Drive $env:SystemDrive encryption is currently at $EncryptionPercentageVar percent." -ForegroundColor Yellow - break + # break from the entire BitLocker category and continue to the next category + break BitLockerCategoryLabel } # A script block that generates recovery code just like the Windows does @@ -927,33 +934,116 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va "@ } + + switch (Select-Option -SubCategory -Options 'Normal: TPM + Startup PIN + Recovery Password', 'Enhanced: TPM + Startup PIN + Startup Key + Recovery Password', 'Skip encryptions altogether', 'Exit' -Message "`nPlease select your desired security level" -ExtraMessage "If you are not sure, refer to the BitLocker category in the GitHub Readme`n") { + 'Normal: TPM + Startup PIN + Recovery Password' { - # check if Bitlocker is enabled for the system drive - if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { + # check if Bitlocker is enabled for the system drive with Normal security level + if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { - # Get the key protectors of the OS Drive - [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector - # Get the key protector types of the OS Drive - [System.String[]]$KeyProtectorTypesOSDrive = $KeyProtectorsOSDrive.keyprotectortype + # Get the key protectors of the OS Drive + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + # Get the key protector types of the OS Drive + [System.String[]]$KeyProtectorTypesOSDrive = $KeyProtectorsOSDrive.keyprotectortype - # check if TPM + PIN + recovery password are being used as key protectors for the OS Drive - if ($KeyProtectorTypesOSDrive -contains 'Tpmpin' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { + # check if TPM + PIN + recovery password are being used as key protectors for the OS Drive + if ($KeyProtectorTypesOSDrive -contains 'Tpmpin' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { - Write-SmartText -C MintGreen -G Green -I 'Bitlocker is already fully and securely enabled for the OS drive' + Write-SmartText -C MintGreen -G Green -I 'Bitlocker is already enabled for the OS drive with Normal security level.' - Write-SmartText -C Fuchsia -GenericColor Magenta -I 'Here is your 48-digits recovery password for the OS drive in case you were looking for it:' - Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectorsOSDrive | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" + Write-SmartText -C Fuchsia -GenericColor Magenta -I 'Here is your 48-digits recovery password for the OS drive in case you were looking for it:' + Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectorsOSDrive | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" - } - else { - # if Bitlocker is using TPM + PIN but not recovery password (for key protectors) - if ($KeyProtectorTypesOSDrive -contains 'Tpmpin' -and $KeyProtectorTypesOSDrive -notcontains 'recoveryPassword') { + } + else { + + # If the OS Drive doesn't have recovery password key protector + if ($KeyProtectorTypesOSDrive -notcontains 'recoveryPassword') { + + [System.String]$BitLockerMsg = "`nThe recovery password is missing, adding it now... `n" + + "It will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" + Write-Host $BitLockerMsg -ForegroundColor Yellow + + # Add RecoveryPasswordProtector key protector to the OS drive + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null + + # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + + # Backup the recovery code of the OS drive in a file + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null + + } + + # If the OS Drive doesn't have (TPM + PIN) key protector + if ($KeyProtectorTypesOSDrive -notcontains 'Tpmpin') { + + Write-Host "`nTPM and Start up PIN are missing, adding them now..." -ForegroundColor Cyan + + do { + [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)"; Read-Host -AsSecureString) + [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) + + # Compare the PINs and make sure they match + [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 + # If the PINs match and they are at least 10 characters long, max 20 characters + if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { + [securestring]$Pin = $Pin1 + } + else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } + } + # Repeat this process until the entered PINs match and they are at least 10 characters long, max 20 characters + until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) + + try { + # Add TPM + PIN key protectors to the OS Drive + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -TpmAndPinProtector -Pin $Pin -ErrorAction Stop | Out-Null + Write-SmartText -C MintGreen -G Green -I "`nPINs matched, enabling TPM and startup PIN now`n" + } + catch { + Write-Host 'These errors occured, run Bitlocker category again after meeting the requirements' -ForegroundColor Red + $_ + break BitLockerCategoryLabel + } + + # Get the key protectors of the OS Drive + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + + # Backup the recovery code of the OS drive in a file just in case - This is for when the disk is automatically encrypted and using TPM + Recovery code by default + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null + + Write-Host "The recovery password was backed up in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan - [System.String]$BitLockerMsg = "`nTPM and Startup PIN are available but the recovery password is missing, adding it now... `n" + - "It will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" - Write-Host $BitLockerMsg -ForegroundColor Yellow + } + } + } + + # Do this if Bitlocker is not enabled for the OS drive at all + else { + Write-Host "`nBitlocker is not enabled for the OS Drive, activating it now..." -ForegroundColor Yellow + do { + [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I 'Enter a Pin for Bitlocker startup (between 10 to 20 characters)'; Read-Host -AsSecureString) + [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) + + [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 - # Add RecoveryPasswordProtector key protector to the OS drive + if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { + [securestring]$Pin = $Pin1 + } + else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } + } + until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) + + try { + # Enable BitLocker for the OS Drive with TPM + PIN key protectors + Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod 'XtsAes256' -Pin $Pin -TpmAndPinProtector -SkipHardwareTest -ErrorAction Stop *> $null + } + catch { + Write-Host 'These errors occured, run Bitlocker category again after meeting the requirements' -ForegroundColor Red + $_ + break BitLockerCategoryLabel + } + # Add recovery password key protector to the OS Drive Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it @@ -961,25 +1051,115 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # Backup the recovery code of the OS drive in a file New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null - - } - # if Bitlocker is using recovery password but not TPM + PIN as key protectors for the OS Drive - elseif ($KeyProtectorTypesOSDrive -notcontains 'Tpmpin' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { - - Write-Host "`nTPM and Start up PIN are missing but recovery password is in place`nAdding TPM and Start up PIN now..." -ForegroundColor Cyan + Resume-BitLocker -MountPoint $env:SystemDrive | Out-Null + + Write-SmartText -C MintGreen -G Green -I "`nBitlocker is now enabled for the OS drive with Normal security level." + Write-Host "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan + } + + } + 'Enhanced: TPM + Startup PIN + Startup Key + Recovery Password' { + + # check if Bitlocker is enabled for the system drive with Enhanced security level + if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { + # Get the key protectors of the OS Drive [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + # Get the key protector types of the OS Drive + [System.String[]]$KeyProtectorTypesOSDrive = $KeyProtectorsOSDrive.keyprotectortype + + # check if TPM + PIN + recovery password are being used as key protectors for the OS Drive + if ($KeyProtectorTypesOSDrive -contains 'TpmAndPinAndStartupKeyProtector' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { + + Write-SmartText -C MintGreen -G Green -I 'Bitlocker is already enabled for the OS drive with Enhanced security level.' + + Write-SmartText -C Fuchsia -GenericColor Magenta -I 'Here is your 48-digits recovery password for the OS drive in case you were looking for it:' + Write-SmartText -C Rainbow -GenericColor Yellow -I "$(($KeyProtectorsOSDrive | Where-Object { $_.keyprotectortype -eq 'RecoveryPassword' }).RecoveryPassword)" + + } + else { + + # If the OS Drive doesn't have recovery password key protector + if ($KeyProtectorTypesOSDrive -notcontains 'recoveryPassword') { + + [System.String]$BitLockerMsg = "`nThe recovery password is missing, adding it now... `n" + + "It will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" + Write-Host $BitLockerMsg -ForegroundColor Yellow + + # Add RecoveryPasswordProtector key protector to the OS drive + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null + + # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + + # Backup the recovery code of the OS drive in a file + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null - # Backup the recovery code of the OS drive in a file just in case - This is for when the disk is automatically encrypted and using TPM + Recovery code by default - New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null + } + + # If the OS Drive doesn't have (TpmAndPinAndStartupKeyProtector) key protector + if ($KeyProtectorTypesOSDrive -notcontains 'TpmAndPinAndStartupKeyProtector') { + + Write-SmartText -C Violet -G Cyan -I "`nTpm And Pin And StartupKey Protector is missing from the OS Drive, adding it now`n" + + # Check if the OS drive has TpmPinStartupKey key protector and if it does remove it + # Otherwise when trying to add TpmAndPinAndStartupKeyProtector to it there will be an error saying they both can't exist at the same time + if ($KeyProtectorTypesOSDrive -contains 'TpmPinStartupKey') { + + (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector | + Where-Object { $_.keyprotectortype -eq 'TpmPinStartupKey' } | + ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $env:SystemDrive -KeyProtectorId $_.KeyProtectorId | Out-Null } + + } + + do { + [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)"; Read-Host -AsSecureString) + [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) + + # Compare the PINs and make sure they match + [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 + # If the PINs match and they are at least 10 characters long, max 20 characters + if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { + [securestring]$Pin = $Pin1 + } + else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } + } + # Repeat this process until the entered PINs match and they are at least 10 characters long, max 20 characters + until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) + + Write-SmartText -C MintGreen -G Green -I "`nPINs matched, enabling TPM, Startup PIN and Startup Key protector now`n" + + try { + # Add TpmAndPinAndStartupKeyProtector to the OS Drive + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -TpmAndPinAndStartupKeyProtector -StartupKeyPath (Get-AvailableRemovableDrives) -Pin $Pin -ErrorAction Stop | Out-Null + } + catch { + Write-Error -Message 'There was a problem adding Startup Key to the removable drive, try ejecting and reinserting the flash drive into your device and run this category again.' + $_ + break BitLockerCategoryLabel + } + + # Get the key protectors of the OS Drive + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + + # Backup the recovery code of the OS drive in a file just in case - This is for when the disk is automatically encrypted and using TPM + Recovery code by default + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null - Write-Host "The recovery password was backed up in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan + Write-Host "The recovery password was backed up in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan + } + } + } + + # Do this if Bitlocker is not enabled for the OS drive at all + else { + Write-Host "`nBitlocker is not enabled for the OS Drive, activating it now..." -ForegroundColor Yellow + do { [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)"; Read-Host -AsSecureString) [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) - + # Compare the PINs and make sure they match [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 # If the PINs match and they are at least 10 characters long, max 20 characters @@ -990,58 +1170,36 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } # Repeat this process until the entered PINs match and they are at least 10 characters long, max 20 characters until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) - + + Write-SmartText -C MintGreen -G Green -I "`nPINs matched, enabling TPM, Startup PIN and Startup Key protector now`n" + try { - # Add TPM + PIN key protectors to the OS Drive - Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -TpmAndPinProtector -Pin $Pin -ErrorAction Stop | Out-Null - Write-SmartText -C MintGreen -G Green -I "`nPINs matched, enabling TPM and startup PIN now" + # Add TpmAndPinAndStartupKeyProtector to the OS Drive + Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod 'XtsAes256' -TpmAndPinAndStartupKeyProtector -StartupKeyPath (Get-AvailableRemovableDrives) -Pin $Pin -SkipHardwareTest -ErrorAction Stop *> $null } catch { - Write-Host 'These errors occured, run Bitlocker category again after meeting the requirements' -ForegroundColor Red + Write-Error -Message 'There was a problem adding Startup Key to the removable drive, try ejecting and reinserting the flash drive into your device and run this category again.' $_ - break + break BitLockerCategoryLabel } - } - } - } - # Do this if Bitlocker is not enabled for the OS drive - else { - Write-Host "`nBitlocker is not enabled for the OS Drive, activating it now..." -ForegroundColor Yellow - do { - [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I 'Enter a Pin for Bitlocker startup (between 10 to 20 characters)'; Read-Host -AsSecureString) - [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) - - [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 - - if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { - [securestring]$Pin = $Pin1 - } - else { Write-Host 'Please ensure that the PINs you entered match, and that they are between 10 to 20 characters.' -ForegroundColor red } - } - until ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) - - try { - # Enable BitLocker for the OS Drive with TPM + PIN key protectors - Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod 'XtsAes256' -Pin $Pin -TpmAndPinProtector -SkipHardwareTest -ErrorAction Stop *> $null - } - catch { - Write-Host 'These errors occured, run Bitlocker category again after meeting the requirements' -ForegroundColor Red - $_ - break - } - # Add recovery password key protector to the OS Drive - Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null + + # Add recovery password key protector to the OS Drive + Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector *> $null - # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it - [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector + # Get the new key protectors of the OS Drive after adding RecoveryPasswordProtector to it + [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector - # Backup the recovery code of the OS drive in a file - New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null + # Backup the recovery code of the OS drive in a file + New-Item -Path "$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsOSDrive) -ItemType File -Force | Out-Null - Resume-BitLocker -MountPoint $env:SystemDrive | Out-Null + Resume-BitLocker -MountPoint $env:SystemDrive | Out-Null - Write-SmartText -C MintGreen -G Green -I "`nBitlocker is now fully and securely enabled for OS drive" - Write-Host "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan + Write-SmartText -C MintGreen -G Green -I "`nBitlocker is now enabled for the OS drive with Enhanced security level." + Write-Host "The recovery password will be saved in a text file in '$env:SystemDrive\Drive $($env:SystemDrive.remove(1)) recovery password.txt'" -ForegroundColor Cyan + } + } + 'Skip encryptions altogether' { break BitLockerCategoryLabel } # Exit the entire BitLocker category, only + 'Exit' { &$CleanUp } } # Enabling Hibernate after making sure OS drive is property encrypted for holding hibernate data @@ -1065,7 +1223,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va Remove-MpPreference -ControlledFolderAccessAllowedApplications 'C:\Windows\System32\powercfg.exe' } else { - Write-Host 'Hibernate is already set to full.' -ForegroundColor Magenta + Write-SmartText -C Pink -G Magenta -I "`nHibernate is already set to full.`n" } } @@ -1137,7 +1295,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va ForEach-Object { # -ErrorAction SilentlyContinue makes sure no error is thrown if the drive only has 1 External key key protector # and it's being used to unlock the drive - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue | Out-Null } # Renew the External key of the selected Non-OS Drive @@ -1156,7 +1314,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # Remove all of the recovery password key protectors of the selected Non-OS Drive $RecoveryPasswordKeyProtectors | ForEach-Object { - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ | Out-Null } # Add a new Recovery Password key protector after removing all of the previous ones @@ -1169,7 +1327,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va New-Item -Path "$MountPoint\Drive $($MountPoint.Remove(1)) recovery password.txt" -Value $(&$RecoveryPasswordContentGenerator $KeyProtectorsNonOS) -ItemType File -Force | Out-Null } - Write-SmartText -C MintGreen -G Green -I "`nBitlocker is already fully and securely enabled for drive $MountPoint" + Write-SmartText -C MintGreen -G Green -I "`nBitlocker is already securely enabled for drive $MountPoint" # Get the new key protectors of the Non-OS Drive after adding RecoveryPasswordProtector to it # Just to simply display it on the console for the user @@ -1190,7 +1348,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va ForEach-Object { # -ErrorAction SilentlyContinue makes sure no error is thrown if the drive only has 1 External key key protector # and it's being used to unlock the drive - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ -ErrorAction SilentlyContinue | Out-Null } # Renew the External key of the selected Non-OS Drive @@ -1229,7 +1387,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # Delete all Recovery Passwords because there were more than 1 $RecoveryPasswordKeyProtectors | ForEach-Object { - Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ + Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_ | Out-Null } # Add a new Recovery Password From e6b42e1111ae6ed63e1a360074c78c732ef5ffd4 Mon Sep 17 00:00:00 2001 From: Violet Date: Thu, 16 Nov 2023 14:14:10 +0000 Subject: [PATCH 13/34] Finalizing BitLocker category multifactor Auth --- .../Main files/Harden-Windows-Security.ps1 | 16 +- Harden-Windows-Security.ps1 | 145 +++++++++++++++++- 2 files changed, 145 insertions(+), 16 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index e28297cbd..41d46e04e 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -1199,7 +1199,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [System.String[]]$KeyProtectorTypesOSDrive = $KeyProtectorsOSDrive.keyprotectortype # check if TPM + PIN + recovery password are being used as key protectors for the OS Drive - if ($KeyProtectorTypesOSDrive -contains 'TpmAndPinAndStartupKeyProtector' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { + if ($KeyProtectorTypesOSDrive -contains 'TpmPinStartupKey' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { Write-SmartText -C MintGreen -G Green -I 'Bitlocker is already enabled for the OS drive with Enhanced security level.' @@ -1227,20 +1227,20 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } - # If the OS Drive doesn't have (TpmAndPinAndStartupKeyProtector) key protector - if ($KeyProtectorTypesOSDrive -notcontains 'TpmAndPinAndStartupKeyProtector') { + # If the OS Drive doesn't have (TpmPinStartupKey) key protector + if ($KeyProtectorTypesOSDrive -notcontains 'TpmPinStartupKey') { Write-SmartText -C Violet -G Cyan -I "`nTpm And Pin And StartupKey Protector is missing from the OS Drive, adding it now`n" - # Check if the OS drive has TpmPinStartupKey key protector and if it does remove it - # Otherwise when trying to add TpmAndPinAndStartupKeyProtector to it there will be an error saying they both can't exist at the same time - if ($KeyProtectorTypesOSDrive -contains 'TpmPinStartupKey') { + # Check if the OS drive has ExternalKey key protector and if it does remove it + # It's the standalone Startup Key protector which isn't secure on its own for the OS Drive + if ($KeyProtectorTypesOSDrive -contains 'ExternalKey') { (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector | - Where-Object { $_.keyprotectortype -eq 'TpmPinStartupKey' } | + Where-Object { $_.keyprotectortype -eq 'ExternalKey' } | ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $env:SystemDrive -KeyProtectorId $_.KeyProtectorId | Out-Null } - } + } do { [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)"; Read-Host -AsSecureString) diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index 43feea43c..0196a7083 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -317,6 +317,135 @@ Function Write-SmartText { } } } + +# Function to get a removable drive to be used by BitLocker category +function Get-AvailableRemovableDrives { + + # Grab the list of volumes that are removable and have drive letter, display their size in GBs instead of Bytes + [System.Object[]]$AvailableRemovableDrives = Get-Volume | Where-Object { $_.DriveLetter -and $_.DriveType -eq 'Removable' } | + Sort-Object -Property DriveLetter | + Select-Object DriveLetter, FileSystemType, DriveType, @{Name = 'Size'; Expression = { '{0:N2}' -f ($_.Size / 1GB) + ' GB' } } + + + if (!$AvailableRemovableDrives) { + do { + switch (Select-Option -Options 'Check for removable flash drives again', 'Skip encryptions altogether', 'Exit' -Message "`nNo removable flash drives found. Please insert a USB flash drive") { + 'Check for removable flash drives again' { + [System.Object[]]$AvailableRemovableDrives = Get-Volume | Where-Object { $_.DriveLetter -and $_.DriveType -eq 'Removable' } | + Sort-Object -Property DriveLetter | + Select-Object DriveLetter, FileSystemType, DriveType, @{Name = 'Size'; Expression = { '{0:N2}' -f ($_.Size / 1GB) + ' GB' } } + } + 'Skip encryptions altogether' { break BitLockerCategoryLabel } + 'Exit' { &$CleanUp } + } + } + until ($AvailableRemovableDrives) + } + + # Initialize the maximum length variables but make sure the column widths are at least as wide as their titles such as 'DriveLetter' or 'FileSystemType' etc. + [int]$DriveLetterLength = 10 + [int]$FileSystemTypeLength = 13 + [int]$DriveTypeLength = 8 + [int]$SizeLength = 3 + + # Loop through each element in the array + foreach ($drive in $AvailableRemovableDrives) { + # Compare the length of the current element with the maximum length and update if needed + if ($drive.DriveLetter.Length -gt $DriveLetterLength) { + $DriveLetterLength = $drive.DriveLetter.Length + } + if ($drive.FileSystemType.Length -gt $FileSystemTypeLength) { + $FileSystemTypeLength = $drive.FileSystemType.Length + } + if ($drive.DriveType.Length -gt $DriveTypeLength) { + $DriveTypeLength = $drive.DriveType.Length + } + if (($drive.Size | Measure-Object -Character).Characters -gt $SizeLength) { + # The method below is used to calculate size of the string that consists only number, but since it now has "GB" in it, it's no longer needed + # $SizeLength = ($drive.Size | Measure-Object -Character).Characters + $SizeLength = $drive.Size.Length + } + } + + # Add 3 to each maximum length for spacing + $DriveLetterLength += 3 + $FileSystemTypeLength += 3 + $DriveTypeLength += 3 + $SizeLength += 3 + + # Creating a heading for the columns + # Write the index of the drive + Write-SmartText -C LavenderNoNewLine -G Blue -I ('{0,-4}' -f '#') + # Write the name of the drive + Write-SmartText -C TeaGreenNoNewLine -G Yellow -I ("|{0,-$DriveLetterLength}" -f 'DriveLetter') + # Write the File System Type of the drive + Write-SmartText -C PinkNoNewLine -G Magenta -I ("|{0,-$FileSystemTypeLength}" -f 'FileSystemType') + # Write the Drive Type of the drive + Write-SmartText -C VioletNoNewLine -G Green -I ("|{0,-$DriveTypeLength}" -f 'DriveType') + # Write the Size of the drive + Write-SmartText -C Gold -G Cyan ("|{0,-$SizeLength}" -f 'Size') + + # Loop through the drives and display them in a table with colors + for ($i = 0; $i -lt $AvailableRemovableDrives.Count; $i++) { + # Write the index of the drive + Write-SmartText -C LavenderNoNewLine -G Blue -I ('{0,-4}' -f ($i + 1)) + # Write the name of the drive + Write-SmartText -C TeaGreenNoNewLine -G Yellow -I ("|{0,-$DriveLetterLength}" -f $AvailableRemovableDrives[$i].DriveLetter) + # Write the File System Type of the drive + Write-SmartText -C PinkNoNewLine -G Magenta -I ("|{0,-$FileSystemTypeLength}" -f $AvailableRemovableDrives[$i].FileSystemType) + # Write the Drive Type of the drive + Write-SmartText -C VioletNoNewLine -G Green -I ("|{0,-$DriveTypeLength}" -f $AvailableRemovableDrives[$i].DriveType) + # Write the Size of the drive + Write-SmartText -C Gold -G Cyan ("|{0,-$SizeLength}" -f $AvailableRemovableDrives[$i].Size) + } + + # Get the max count of available network drives and add 1 to it, assign the number as exit value to break the loop when selected + [int]$ExitCodeRemovableDriveSelection = $AvailableRemovableDrives.Count + 1 + + # Write an exit option at the end of the table + Write-Host ('{0,-4}' -f "$ExitCodeRemovableDriveSelection") -NoNewline -ForegroundColor DarkRed + Write-Host '|Skip encryptions altogether' -ForegroundColor DarkRed + + # A function to validate the user input + function Confirm-Choice { + param([string]$Choice) + # Initialize a flag to indicate if the input is valid or not + [bool]$IsValid = $false + # Initialize a variable to store the parsed integer value + [int]$ParsedChoice = 0 + # Try to parse the input as an integer + # If the parsing succeeded, check if the input is within the range + if ([int]::TryParse($Choice, [ref]$ParsedChoice)) { + if ($ParsedChoice -in 1..$ExitCodeRemovableDriveSelection) { + $IsValid = $true + break + } + } + # Return the flag value + return $IsValid + } + + # Prompt the user to enter the number of the drive they want to select, or exit value to exit, until they enter a valid input + do { + # Read the user input as a string + [string]$Choice = $(Write-Host "Enter the number of the drive you want to select or press $ExitCodeRemovableDriveSelection to Cancel" -ForegroundColor cyan; Read-Host) + + # Check if the input is valid using the Confirm-Choice function + if (-not (Confirm-Choice $Choice)) { + # Write an error message in red if invalid + Write-Host "Invalid input. Please enter a number between 1 and $ExitCodeRemovableDriveSelection." -ForegroundColor Red + } + } while (-not (Confirm-Choice $Choice)) + + # Check if the user entered the exit value to break out of the loop + if ($Choice -eq $ExitCodeRemovableDriveSelection) { + break BitLockerCategoryLabel + } + else { + # Get the selected drive from the array and display it + return ($($AvailableRemovableDrives[$Choice - 1]).DriveLetter + ':') + } +} #endregion functions if (Test-IsAdmin) { @@ -1070,7 +1199,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [System.String[]]$KeyProtectorTypesOSDrive = $KeyProtectorsOSDrive.keyprotectortype # check if TPM + PIN + recovery password are being used as key protectors for the OS Drive - if ($KeyProtectorTypesOSDrive -contains 'TpmAndPinAndStartupKeyProtector' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { + if ($KeyProtectorTypesOSDrive -contains 'TpmPinStartupKey' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { Write-SmartText -C MintGreen -G Green -I 'Bitlocker is already enabled for the OS drive with Enhanced security level.' @@ -1098,20 +1227,20 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } - # If the OS Drive doesn't have (TpmAndPinAndStartupKeyProtector) key protector - if ($KeyProtectorTypesOSDrive -notcontains 'TpmAndPinAndStartupKeyProtector') { + # If the OS Drive doesn't have (TpmPinStartupKey) key protector + if ($KeyProtectorTypesOSDrive -notcontains 'TpmPinStartupKey') { Write-SmartText -C Violet -G Cyan -I "`nTpm And Pin And StartupKey Protector is missing from the OS Drive, adding it now`n" - # Check if the OS drive has TpmPinStartupKey key protector and if it does remove it - # Otherwise when trying to add TpmAndPinAndStartupKeyProtector to it there will be an error saying they both can't exist at the same time - if ($KeyProtectorTypesOSDrive -contains 'TpmPinStartupKey') { + # Check if the OS drive has ExternalKey key protector and if it does remove it + # It's the standalone Startup Key protector which isn't secure on its own for the OS Drive + if ($KeyProtectorTypesOSDrive -contains 'ExternalKey') { (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector | - Where-Object { $_.keyprotectortype -eq 'TpmPinStartupKey' } | + Where-Object { $_.keyprotectortype -eq 'ExternalKey' } | ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $env:SystemDrive -KeyProtectorId $_.KeyProtectorId | Out-Null } - } + } do { [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I "`nEnter a Pin for Bitlocker startup (between 10 to 20 characters)"; Read-Host -AsSecureString) From 5fe3b5a4ec3d7a06f330ce567ce25f74a2fa93ef Mon Sep 17 00:00:00 2001 From: Violet Date: Thu, 16 Nov 2023 19:03:20 +0000 Subject: [PATCH 14/34] Added Steps recorder removal info to Readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bc59128c4..846613790 100644 --- a/README.md +++ b/README.md @@ -714,7 +714,9 @@ NistP384 - WordPad: Old and [deprecated](https://learn.microsoft.com/en-us/windows/whats-new/deprecated-features). None of the new features of Word documents are supported in it. Recommended to use [Word Online](https://www.microsoft.com/en-us/microsoft-365/free-office-online-for-the-web), Notepad or M365 Word. - - [PowerShell ISE](https://learn.microsoft.com/en-us/powershell/scripting/windows-powershell/ise/introducing-the-windows-powershell-ise): Old PowerShell environment that doesn't support versions above 5.1. Highly recommended to use [Visual Studio Code](https://apps.microsoft.com/detail/visual-studio-code/XP9KHM4BK9FZ7Q) for PowerShell usage and [learning](https://github.com/HotCakeX/Harden-Windows-Security/wiki#-powershell). You can even replicate the [ISE experience in Visual Studio Code](https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/vscode/how-to-replicate-the-ise-experience-in-vscode). You can access [Visual Studio Code online in your browser](https://vscode.dev/) without the need to install anything. + - [PowerShell ISE](https://learn.microsoft.com/en-us/powershell/scripting/windows-powershell/ise/introducing-the-windows-powershell-ise): Old PowerShell environment that doesn't support versions above 5.1. Highly recommended to use [Visual Studio Code](https://apps.microsoft.com/detail/visual-studio-code/XP9KHM4BK9FZ7Q) for PowerShell usage and [learning](https://github.com/HotCakeX/Harden-Windows-Security/wiki#-powershell). You can even replicate the [ISE experience in Visual Studio Code](https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/vscode/how-to-replicate-the-ise-experience-in-vscode). You can access [Visual Studio Code online in your browser](https://vscode.dev/) without the need to install anything. + + - Steps Recorder: it's [deprecated](https://prod.support.services.microsoft.com/en-us/windows/steps-recorder-deprecation-a64888d7-8482-4965-8ce3-25fb004e975f). - Rotating pink checkmark denoting registry or cmdlet [Enables](https://learn.microsoft.com/en-us/powershell/module/dism/enable-windowsoptionalfeature) these optional features (Control Panel): From 9251e79a6f22fd62b1b23d3d1f3df1e351b09b43 Mon Sep 17 00:00:00 2001 From: Violet Date: Thu, 16 Nov 2023 19:38:38 +0000 Subject: [PATCH 15/34] Updated Readme with new BitLocker info changes --- README.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 846613790..c6af05617 100644 --- a/README.md +++ b/README.md @@ -463,21 +463,24 @@ Such software behaviors are sometimes seen in legitimate applications. However,
-- Rotating pink checkmark denoting registry or cmdletBlue Check mark denoting Group Policy This module sets up and configures Bitlocker [using official documentation](https://learn.microsoft.com/en-us/windows/security/information-protection/bitlocker/bitlocker-group-policy-settings), with the most secure configuration and Military Grade encryption algorithm, **XTS-AES-256, TPM 2.0 and start-up PIN**, to protect the confidentiality and integrity of all information at rest and in use. Rotating green checkmark denoting CSP [CSP](https://learn.microsoft.com/en-us/windows/client-management/mdm/bitlocker-csp#encryptionmethodbydrivetype) Rotating green checkmark denoting CSP [CSP](https://learn.microsoft.com/en-us/windows/client-management/mdm/bitlocker-csp#systemdrivesrequirestartupauthentication) +- Rotating pink checkmark denoting registry or cmdletBlue Check mark denoting Group Policy The module sets up and configures Bitlocker [using official documentation](https://learn.microsoft.com/en-us/windows/security/information-protection/bitlocker/bitlocker-group-policy-settings), with the most secure configuration and military grade encryption algorithm, XTS-AES-256, to protect the confidentiality and integrity of all information at rest and in use. Rotating green checkmark denoting CSP [CSP](https://learn.microsoft.com/en-us/windows/client-management/mdm/bitlocker-csp#encryptionmethodbydrivetype) Rotating green checkmark denoting CSP [CSP](https://learn.microsoft.com/en-us/windows/client-management/mdm/bitlocker-csp#systemdrivesrequirestartupauthentication) - - OS drive is automatically encrypted (if it isn't already) when you run this category and select a startup PIN. For every other non-OS drive, there will be prompts for confirmation before encrypting it. Removable flash drives are skipped. Non-OS drives will have Recovery Password and Auto-unlock methods for authentication once they are encrypted. + - It offers 2 security levels for OS drive encryption: **Enhanced** and **Normal**. - - You will be asked to enter a Startup PIN when activating Bitlocker for the first time. Make sure it contains at least 10 characters (uppercase and lowercase letters, symbols, numbers, spaces) and it's not the same as your Windows Hello PIN. + - In **Normal** security level, the OS drive is encrypted with TPM and Startup PIN. This provides very high security for your data, specially with a PIN that's long, complicated (uppercase and lowercase letters, symbols, numbers, spaces) and isn't the same as your Windows Hello PIN. - - Once you run this module for the first time, there will be a text file containing the 48-digit recovery password for each encrypted drive that will be saved in itself, with the names like `Drive C recovery password.txt`. It is **very important to keep it in a safe and reachable place, e.g., in OneDrive's Personal Vault which requires authentication to access. See [Here](https://www.microsoft.com/en-us/microsoft-365/onedrive/personal-vault) and [Here](https://support.microsoft.com/en-us/office/protect-your-onedrive-files-in-personal-vault-6540ef37-e9bf-4121-a773-56f98dce78c4) for more info about OneDrive's Personal Vault** + - In **Enhanced** security level, the OS drive is encrypted with TPM and Startup PIN and Startup key. This provides the highest level of protection by offering Multifactor Authentication. You will need to enter your PIN and also plug in a flash drive, containing a special BitLocker key, into your device in order to unlock it. [Continue reading more about it here](https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/countermeasures#preboot-authentication). - - TPM has [special anti-hammering logic](https://learn.microsoft.com/en-us/windows/security/information-protection/tpm/tpm-fundamentals) which prevents malicious user from guessing the authorization data indefinitely. [Microsoft defines that maximum number of failed attempts](https://learn.microsoft.com/en-us/archive/blogs/dubaisec/tpm-lockout) in Windows is 32 and every single failed attempt is forgotten after 2 hours. This means that every continuous two hours of powered on (and successfully booted) operation without an event which increases the counter will cause the counter to decrease by 1. You can view all the details using this [PowerShell command](https://learn.microsoft.com/en-us/powershell/module/trustedplatformmodule/get-tpm): `Get-TPM`. + - Once the OS drive is encrypted, for every other non-OS drive, there will be prompts for confirmation before encrypting it. The encryption will use the same algorithm as the OS drive and uses [Auto-unlock key protector](https://learn.microsoft.com/en-us/powershell/module/bitlocker/enable-bitlockerautounlock). Removable flash drives are skipped. - - Check out Lock Screen category for more info about the recovery password and the 2nd anti-hammering mechanism. + - All of the encrypted drives will have recovery password too. It's a 48-digit password that is saved in each drive's root after the encryption begins. It's **very important to keep it in a safe and reachable place as soon as possible, e.g., in OneDrive's Personal Vault which requires additional authentication to access.** See [here](https://www.microsoft.com/en-us/microsoft-365/onedrive/personal-vault) and [here](https://support.microsoft.com/en-us/office/protect-your-onedrive-files-in-personal-vault-6540ef37-e9bf-4121-a773-56f98dce78c4) for more info. You can use it to unlock your drive if you ever lose access to one of your key protectors, such as TPM, Startup PIN or Startup Key. - - To have even more security than what the module provides, you can utilize a [Startup key in addition to the other 3 key protectors](https://learn.microsoft.com/en-us/windows/security/information-protection/bitlocker/bitlocker-countermeasures#pre-boot-authentication) (TPM, Startup PIN and Recovery password). with this method, part of the encryption key is stored on a USB flash drive, and a PIN is required to authenticate the user to the TPM. This configuration **provides multifactor authentication** so that if the USB key is lost or stolen, it can't be used for access to the drive, because the correct PIN is also required. + - TPM has [special anti-hammering logic](https://learn.microsoft.com/en-us/windows/security/information-protection/tpm/tpm-fundamentals) which prevents malicious user from guessing the authorization data indefinitely. [Microsoft defines that maximum number of failed attempts](https://learn.microsoft.com/en-us/archive/blogs/dubaisec/tpm-lockout) in Windows is 32 and every single failed attempt is forgotten after 2 hours. This means that every continuous two hours of powered on (and successfully booted) operation without an event which increases the counter will cause the counter to decrease by 1. You can view all the details using this [PowerShell command](https://learn.microsoft.com/en-us/powershell/module/trustedplatformmodule/get-tpm): `Get-TPM`. - - BitLocker will bring you a real security against the theft of your device if you strictly abide by the following basic rules: + - Check out Lock Screen category for more info about the recovery password and the 2nd anti-hammering mechanism. + + - BitLocker will bring you a [real security](https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/countermeasures#attacker-with-skill-and-lengthy-physical-access) against the theft of your device if you strictly abide by the following basic rules: + - As soon as you have finished working, either Hibernate or shut Windows down and allow for every shadow of information to disappear from RAM within 2 minutes. **This practice is recommended in High-Risk Environments.** - Do not mix 3rd party encryption software and tools with Bitlocker. Bitlocker creates a secure end-to-end encrypted ecosystem for your device and its peripherals, this secure ecosystem is backed by things such as software, Virtualization Technology, TPM 2.0 and UEFI firmware, Bitlocker protects your data and entire device against **real-life attacks and threats**. You can encrypt your external SSDs and flash drives with Bitlocker too. @@ -499,8 +502,6 @@ Such software behaviors are sometimes seen in legitimate applications. However, - Blue Check mark denoting Group Policy (Only on Physical machines) Enables Hibernate and adds Hibernate to Start menu's power options. Rotating green checkmark denoting CSP [CSP](https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-power#allowhibernate) - - Refer to the [Bitlocker countermeasures](https://learn.microsoft.com/en-us/windows/security/information-protection/bitlocker/bitlocker-countermeasures#attacker-with-skill-and-lengthy-physical-access) for more info. - - Devices that support [Modern Standby](https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/modern-standby) have the most security because [(S1-S3) power states](https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/system-power-states) which belong to the [legacy sleep modes](https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/modern-standby-vs-s3) are not available. In Modern Standby, security components remain vigilant and the OS stays protected. Applying Microsoft Security Baselines also automatically disables the legacy (S1-S3) sleep states. - Rotating pink checkmark denoting registry or cmdlet [sets Hibernate to full](https://learn.microsoft.com/en-us/windows/win32/power/system-power-states#hibernation-file-types) From 2015470133dfdd19b20ef470c56ad727abb3e435 Mon Sep 17 00:00:00 2001 From: Violet Date: Thu, 16 Nov 2023 20:20:27 +0000 Subject: [PATCH 16/34] Added Steps recorder removal --- .../Main files/Harden-Windows-Security.ps1 | 20 +++++++++++++++++++ Harden-Windows-Security.ps1 | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index 41d46e04e..cd89f9477 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -1970,6 +1970,26 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } } + powershell.exe { + + # Uninstall Steps Recorder + Write-Host "`nUninstalling Steps Recorder" -ForegroundColor Yellow + if ((Get-WindowsCapability -Online | Where-Object { $_.Name -like '*App.StepsRecorder*' }).state -ne 'NotPresent') { + try { + Get-WindowsCapability -Online | Where-Object { $_.Name -like '*App.StepsRecorder*' } | Remove-WindowsCapability -Online -ErrorAction Stop + # Shows the successful message only if removal process was successful + Write-Host 'Steps Recorder has been uninstalled.' -ForegroundColor Green + } + catch { + # show error + $_ + } + } + else { + Write-Host 'Steps Recorder is already uninstalled.' -ForegroundColor Green + } + } + } 'No' { break } 'Exit' { &$CleanUp } } diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index 0196a7083..432794e04 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -1970,6 +1970,26 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } } + powershell.exe { + + # Uninstall Steps Recorder + Write-Host "`nUninstalling Steps Recorder" -ForegroundColor Yellow + if ((Get-WindowsCapability -Online | Where-Object { $_.Name -like '*App.StepsRecorder*' }).state -ne 'NotPresent') { + try { + Get-WindowsCapability -Online | Where-Object { $_.Name -like '*App.StepsRecorder*' } | Remove-WindowsCapability -Online -ErrorAction Stop + # Shows the successful message only if removal process was successful + Write-Host 'Steps Recorder has been uninstalled.' -ForegroundColor Green + } + catch { + # show error + $_ + } + } + else { + Write-Host 'Steps Recorder is already uninstalled.' -ForegroundColor Green + } + } + } 'No' { break } 'Exit' { &$CleanUp } } From 5b51810e9d87fbc4895bff5b6df383c0ca0ac1df Mon Sep 17 00:00:00 2001 From: Violet Date: Thu, 16 Nov 2023 20:43:41 +0000 Subject: [PATCH 17/34] Added BitLocker MFA to compliance checking --- .../Main files/Confirm-SystemCompliance.psm1 | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 index 298f0c209..af1d29e25 100644 --- a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 +++ b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 @@ -559,27 +559,50 @@ function Confirm-SystemCompliance { } # OS Drive encryption verifications + # Check if BitLocker is on for the OS Drive if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { - [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector.keyprotectortype - # check if TPM+PIN and recovery password are being used with Bitlocker which are the safest settings + + # Get the key protectors of the OS Drive + [System.String[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector.keyprotectortype + + # Check if TPM+PIN and recovery password are being used - Normal encryption level if (($KeyProtectors -contains 'Tpmpin') -and ($KeyProtectors -contains 'RecoveryPassword')) { - $IndividualItemResult = $True + + $NestedObjectArray += [PSCustomObject]@{ + FriendlyName = 'Secure OS Drive encryption' + Compliant = $True + Value = 'Normal Encryption Level' + Name = 'Secure OS Drive encryption' + Category = $CatName + Method = 'Cmdlet' + + } } - else { - $IndividualItemResult = $false + + # Check if TPM+PIN+StartupKey and recovery password are being used - Enhanced encryption level + elseif (($KeyProtectors -contains 'TpmPinStartupKey') -and ($KeyProtectors -contains 'RecoveryPassword')) { + + $NestedObjectArray += [PSCustomObject]@{ + FriendlyName = 'Secure OS Drive encryption' + Compliant = $True + Value = 'Enhanced Encryption Level' + Name = 'Secure OS Drive encryption' + Category = $CatName + Method = 'Cmdlet' + + } } - } + else { - $IndividualItemResult = $false - } - $NestedObjectArray += [PSCustomObject]@{ + $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Secure OS Drive encryption' - Compliant = $IndividualItemResult - Value = $IndividualItemResult + Compliant = $false + Value = $false Name = 'Secure OS Drive encryption' Category = $CatName Method = 'Cmdlet' - } + } + } #region Non-OS-Drive-BitLocker-Drives-Encryption-Verification # Get the list of non OS volumes @@ -618,7 +641,7 @@ function Confirm-SystemCompliance { # If status is unknown, that means the non-OS volume is encrypted and locked, if it's on then it's on if ((Get-BitLockerVolume -MountPoint $MountPoint).ProtectionStatus -in 'on', 'Unknown') { - # Check 1: if Recovery Password and Auto Unlock key protectors are available on the drive + # Check 1: if Recovery Password or Auto Unlock key protectors are available on the drive [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector.keyprotectortype if (($KeyProtectors -contains 'RecoveryPassword') -or ($KeyProtectors -contains 'Password')) { From ec217391f7429dfb9147bee4546fccdafbcf47a8 Mon Sep 17 00:00:00 2001 From: Violet Date: Thu, 16 Nov 2023 20:47:39 +0000 Subject: [PATCH 18/34] Added steps recorder removal verification to Compliance checking --- .../Main files/Confirm-SystemCompliance.psm1 | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 index af1d29e25..59f555ab6 100644 --- a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 +++ b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 @@ -934,8 +934,9 @@ function Confirm-SystemCompliance { [string]$LegacyNotepad = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*Microsoft.Windows.Notepad.System*' }).state [string]$LegacyWordPad = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*Microsoft.Windows.WordPad*' }).state [string]$PowerShellISE = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*Microsoft.Windows.PowerShell.ISE*' }).state + [string]$StepsRecorder = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*App.StepsRecorder*' }).state # returning the output of the script block as an array - Return $PowerShell1, $PowerShell2, $WorkFoldersClient, $InternetPrintingClient, $WindowsMediaPlayer, $MDAG, $WindowsSandbox, $HyperV, $VMPlatform, $WMIC, $IEMode, $LegacyNotepad, $LegacyWordPad, $PowerShellISE + Return $PowerShell1, $PowerShell2, $WorkFoldersClient, $InternetPrintingClient, $WindowsMediaPlayer, $MDAG, $WindowsSandbox, $HyperV, $VMPlatform, $WMIC, $IEMode, $LegacyNotepad, $LegacyWordPad, $PowerShellISE, $StepsRecorder } # Verify PowerShell v2 is disabled $NestedObjectArray += [PSCustomObject]@{ @@ -1066,6 +1067,16 @@ function Confirm-SystemCompliance { Category = $CatName Method = 'Optional Windows Features' } + + # Verify Steps Recorder is not present + $NestedObjectArray += [PSCustomObject]@{ + FriendlyName = 'Steps Recorder is not present' + Compliant = [bool]($Results[14] -eq 'NotPresent') + Value = [string]$Results[14] + Name = 'Steps Recorder is not present' + Category = $CatName + Method = 'Optional Windows Features' + } # Add the array of custom objects as a property to the $FinalMegaObject object outside the loop Add-Member -InputObject $FinalMegaObject -MemberType NoteProperty -Name $CatName -Value $NestedObjectArray From 30877a580247fcb32bc75207a55c7c03f876fac2 Mon Sep 17 00:00:00 2001 From: Violet Date: Fri, 17 Nov 2023 07:36:28 +0000 Subject: [PATCH 19/34] Revert "Added BitLocker MFA to compliance checking" This reverts commit 5b51810e9d87fbc4895bff5b6df383c0ca0ac1df. --- .../Main files/Confirm-SystemCompliance.psm1 | 49 +++++-------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 index 59f555ab6..d83d7c12a 100644 --- a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 +++ b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 @@ -559,50 +559,27 @@ function Confirm-SystemCompliance { } # OS Drive encryption verifications - # Check if BitLocker is on for the OS Drive if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { - - # Get the key protectors of the OS Drive - [System.String[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector.keyprotectortype - - # Check if TPM+PIN and recovery password are being used - Normal encryption level + [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector.keyprotectortype + # check if TPM+PIN and recovery password are being used with Bitlocker which are the safest settings if (($KeyProtectors -contains 'Tpmpin') -and ($KeyProtectors -contains 'RecoveryPassword')) { - - $NestedObjectArray += [PSCustomObject]@{ - FriendlyName = 'Secure OS Drive encryption' - Compliant = $True - Value = 'Normal Encryption Level' - Name = 'Secure OS Drive encryption' - Category = $CatName - Method = 'Cmdlet' - - } + $IndividualItemResult = $True } - - # Check if TPM+PIN+StartupKey and recovery password are being used - Enhanced encryption level - elseif (($KeyProtectors -contains 'TpmPinStartupKey') -and ($KeyProtectors -contains 'RecoveryPassword')) { - - $NestedObjectArray += [PSCustomObject]@{ - FriendlyName = 'Secure OS Drive encryption' - Compliant = $True - Value = 'Enhanced Encryption Level' - Name = 'Secure OS Drive encryption' - Category = $CatName - Method = 'Cmdlet' - - } + else { + $IndividualItemResult = $false } - + } else { - $NestedObjectArray += [PSCustomObject]@{ + $IndividualItemResult = $false + } + $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Secure OS Drive encryption' - Compliant = $false - Value = $false + Compliant = $IndividualItemResult + Value = $IndividualItemResult Name = 'Secure OS Drive encryption' Category = $CatName Method = 'Cmdlet' - } - } + } #region Non-OS-Drive-BitLocker-Drives-Encryption-Verification # Get the list of non OS volumes @@ -641,7 +618,7 @@ function Confirm-SystemCompliance { # If status is unknown, that means the non-OS volume is encrypted and locked, if it's on then it's on if ((Get-BitLockerVolume -MountPoint $MountPoint).ProtectionStatus -in 'on', 'Unknown') { - # Check 1: if Recovery Password or Auto Unlock key protectors are available on the drive + # Check 1: if Recovery Password and Auto Unlock key protectors are available on the drive [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector.keyprotectortype if (($KeyProtectors -contains 'RecoveryPassword') -or ($KeyProtectors -contains 'Password')) { From b96f5df9f4a8e65b9d6e0343e44ad64e8365fa5a Mon Sep 17 00:00:00 2001 From: Violet Date: Fri, 17 Nov 2023 07:42:09 +0000 Subject: [PATCH 20/34] Fixed the previous commit Was missing a closing curly bracket --- .../Main files/Confirm-SystemCompliance.psm1 | 57 +++++++++++++------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 index d83d7c12a..0c2cc178d 100644 --- a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 +++ b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 @@ -559,28 +559,51 @@ function Confirm-SystemCompliance { } # OS Drive encryption verifications + # Check if BitLocker is on for the OS Drive if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { - [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector.keyprotectortype - # check if TPM+PIN and recovery password are being used with Bitlocker which are the safest settings + + # Get the key protectors of the OS Drive + [System.String[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector.keyprotectortype + + # Check if TPM+PIN and recovery password are being used - Normal encryption level if (($KeyProtectors -contains 'Tpmpin') -and ($KeyProtectors -contains 'RecoveryPassword')) { - $IndividualItemResult = $True + + $NestedObjectArray += [PSCustomObject]@{ + FriendlyName = 'Secure OS Drive encryption' + Compliant = $True + Value = 'Normal Encryption Level' + Name = 'Secure OS Drive encryption' + Category = $CatName + Method = 'Cmdlet' + + } } - else { - $IndividualItemResult = $false + + # Check if TPM+PIN+StartupKey and recovery password are being used - Enhanced encryption level + elseif (($KeyProtectors -contains 'TpmPinStartupKey') -and ($KeyProtectors -contains 'RecoveryPassword')) { + + $NestedObjectArray += [PSCustomObject]@{ + FriendlyName = 'Secure OS Drive encryption' + Compliant = $True + Value = 'Enhanced Encryption Level' + Name = 'Secure OS Drive encryption' + Category = $CatName + Method = 'Cmdlet' + + } } - } - else { - $IndividualItemResult = $false - } - $NestedObjectArray += [PSCustomObject]@{ - FriendlyName = 'Secure OS Drive encryption' - Compliant = $IndividualItemResult - Value = $IndividualItemResult - Name = 'Secure OS Drive encryption' - Category = $CatName - Method = 'Cmdlet' - } + else { + $NestedObjectArray += [PSCustomObject]@{ + FriendlyName = 'Secure OS Drive encryption' + Compliant = $false + Value = $false + Name = 'Secure OS Drive encryption' + Category = $CatName + Method = 'Cmdlet' + } + } + } #region Non-OS-Drive-BitLocker-Drives-Encryption-Verification # Get the list of non OS volumes [System.Object[]]$NonOSBitLockerVolumes = Get-BitLockerVolume | Where-Object { From 1330bd88ca2ada1c2fcff64be638ae91c3085618 Mon Sep 17 00:00:00 2001 From: Violet Date: Fri, 17 Nov 2023 08:41:02 +0000 Subject: [PATCH 21/34] Fixed Hibernate file size detection --- .../Main files/Confirm-SystemCompliance.psm1 | 11 +++++------ .../Main files/Harden-Windows-Security.ps1 | 4 ++-- Harden-Windows-Security.ps1 | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 index 0c2cc178d..f32fb37d7 100644 --- a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 +++ b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 @@ -539,17 +539,16 @@ function Confirm-SystemCompliance { # To detect if Hibernate is enabled and set to full if (-NOT ($MDAVConfigCurrent.IsVirtualMachine)) { try { - $IndividualItemResult1 = $($((Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Power' -Name 'HibernateEnabled' -ErrorAction SilentlyContinue).hibernateEnabled) -eq 1 ? $True : $False) - $IndividualItemResult2 = $($((Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Power' -Name 'HiberFileType' -ErrorAction SilentlyContinue).HiberFileType) -eq 2 ? $True : $False) + $IndividualItemResult = $($((Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Power' -Name 'HiberFileType' -ErrorAction SilentlyContinue).HiberFileType) -eq 2 ? $True : $False) } catch { # suppress the errors if any } $NestedObjectArray += [PSCustomObject]@{ - FriendlyName = 'Hibernate is enabled and set to full' - Compliant = ($IndividualItemResult1 -and $IndividualItemResult2) - Value = ($IndividualItemResult1 -and $IndividualItemResult2) - Name = 'Hibernate is enabled and set to full' + FriendlyName = 'Hibernate is set to full' + Compliant = [bool]($IndividualItemResult) + Value = [bool]($IndividualItemResult) + Name = 'Hibernate is set to full' Category = $CatName Method = 'Cmdlet' } diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index cd89f9477..c284d2e89 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -1331,7 +1331,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va 'Exit' { &$CleanUp } } - # Enabling Hibernate after making sure OS drive is property encrypted for holding hibernate data + # Setting Hibernate file size to full after making sure OS drive is property encrypted for holding hibernate data # Making sure the system is not a VM because Hibernate on VM doesn't work and VMs have other/better options than Hibernation if (-NOT ((Get-MpComputerStatus).IsVirtualMachine)) { @@ -1342,7 +1342,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va catch { # Do nothing if the key doesn't exist } - if ($HiberFileType -and $HiberFileType -ne 2) { + if ($HiberFileType -ne 2) { # doing this so Controlled Folder Access won't bitch about powercfg.exe Add-MpPreference -ControlledFolderAccessAllowedApplications 'C:\Windows\System32\powercfg.exe' Start-Sleep 5 diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index 432794e04..0445d0116 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -1331,7 +1331,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va 'Exit' { &$CleanUp } } - # Enabling Hibernate after making sure OS drive is property encrypted for holding hibernate data + # Setting Hibernate file size to full after making sure OS drive is property encrypted for holding hibernate data # Making sure the system is not a VM because Hibernate on VM doesn't work and VMs have other/better options than Hibernation if (-NOT ((Get-MpComputerStatus).IsVirtualMachine)) { @@ -1342,7 +1342,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va catch { # Do nothing if the key doesn't exist } - if ($HiberFileType -and $HiberFileType -ne 2) { + if ($HiberFileType -ne 2) { # doing this so Controlled Folder Access won't bitch about powercfg.exe Add-MpPreference -ControlledFolderAccessAllowedApplications 'C:\Windows\System32\powercfg.exe' Start-Sleep 5 From 91b042b0ec70f2df9955098fae3842734b1f230b Mon Sep 17 00:00:00 2001 From: Violet Date: Fri, 17 Nov 2023 08:43:57 +0000 Subject: [PATCH 22/34] Version bump --- .../Main files/Harden-Windows-Security.ps1 | 4 ++-- Harden-Windows-Security.ps1 | 4 ++-- Version.txt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index c284d2e89..d356a12c2 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -1,6 +1,6 @@ <#PSScriptInfo -.VERSION 2023.11.8 +.VERSION 2023.11.17 .GUID d435a293-c9ee-4217-8dc1-4ad2318a5770 @@ -91,7 +91,7 @@ Set-ExecutionPolicy Bypass -Scope Process # Defining global script variables # Current script's version, the same as the version at the top in the script info section -[datetime]$CurrentVersion = '2023.11.8' +[datetime]$CurrentVersion = '2023.11.17' # Minimum OS build number required for the hardening measures used in this script [decimal]$Requiredbuild = '22621.2428' # Fetching Temp Directory diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index 0445d0116..8c4bddaae 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -1,6 +1,6 @@ <#PSScriptInfo -.VERSION 2023.11.8 +.VERSION 2023.11.17 .GUID d435a293-c9ee-4217-8dc1-4ad2318a5770 @@ -91,7 +91,7 @@ Set-ExecutionPolicy Bypass -Scope Process # Defining global script variables # Current script's version, the same as the version at the top in the script info section -[datetime]$CurrentVersion = '2023.11.8' +[datetime]$CurrentVersion = '2023.11.17' # Minimum OS build number required for the hardening measures used in this script [decimal]$Requiredbuild = '22621.2428' # Fetching Temp Directory diff --git a/Version.txt b/Version.txt index b155a132a..f0821a3cb 100644 --- a/Version.txt +++ b/Version.txt @@ -1 +1 @@ -2023.11.8 \ No newline at end of file +2023.11.17 \ No newline at end of file From 07f279be060e30771c74bea275b18d909b4eb591 Mon Sep 17 00:00:00 2001 From: Violet Date: Fri, 17 Nov 2023 12:49:21 +0000 Subject: [PATCH 23/34] Updated Scheduled task code The Scheduled task for updating Microsoft recommended driver block rules now only runs if there is a network connection, which makes sense because it needs to download the latest block list from the official MSFT servers. It also restarts itself if it fails, every 6 hours, up to 4 tries. --- .../Main files/Harden-Windows-Security.ps1 | 59 +++++++++++-------- Harden-Windows-Security.ps1 | 59 +++++++++++-------- 2 files changed, 68 insertions(+), 50 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index d356a12c2..e018c34de 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -335,7 +335,7 @@ function Get-AvailableRemovableDrives { Sort-Object -Property DriveLetter | Select-Object DriveLetter, FileSystemType, DriveType, @{Name = 'Size'; Expression = { '{0:N2}' -f ($_.Size / 1GB) + ' GB' } } } - 'Skip encryptions altogether' { break BitLockerCategoryLabel } + 'Skip encryptions altogether' { break BitLockerCategoryLabel } # Breaks from the BitLocker category and won't process Non-OS Drives 'Exit' { &$CleanUp } } } @@ -343,10 +343,10 @@ function Get-AvailableRemovableDrives { } # Initialize the maximum length variables but make sure the column widths are at least as wide as their titles such as 'DriveLetter' or 'FileSystemType' etc. - [int]$DriveLetterLength = 10 - [int]$FileSystemTypeLength = 13 - [int]$DriveTypeLength = 8 - [int]$SizeLength = 3 + [System.Int64]$DriveLetterLength = 10 + [System.Int64]$FileSystemTypeLength = 13 + [System.Int64]$DriveTypeLength = 8 + [System.Int64]$SizeLength = 3 # Loop through each element in the array foreach ($drive in $AvailableRemovableDrives) { @@ -400,7 +400,7 @@ function Get-AvailableRemovableDrives { } # Get the max count of available network drives and add 1 to it, assign the number as exit value to break the loop when selected - [int]$ExitCodeRemovableDriveSelection = $AvailableRemovableDrives.Count + 1 + [System.Int64]$ExitCodeRemovableDriveSelection = $AvailableRemovableDrives.Count + 1 # Write an exit option at the end of the table Write-Host ('{0,-4}' -f "$ExitCodeRemovableDriveSelection") -NoNewline -ForegroundColor DarkRed @@ -412,10 +412,10 @@ function Get-AvailableRemovableDrives { # Initialize a flag to indicate if the input is valid or not [bool]$IsValid = $false # Initialize a variable to store the parsed integer value - [int]$ParsedChoice = 0 + [System.Int64]$ParsedChoice = 0 # Try to parse the input as an integer # If the parsing succeeded, check if the input is within the range - if ([int]::TryParse($Choice, [ref]$ParsedChoice)) { + if ([System.Int64]::TryParse($Choice, [ref]$ParsedChoice)) { if ($ParsedChoice -in 1..$ExitCodeRemovableDriveSelection) { $IsValid = $true break @@ -554,7 +554,7 @@ try { # create our working directory New-Item -ItemType Directory -Path "$global:UserTempDirectoryPath\HardeningXStuff\" -Force | Out-Null # working directory assignment - [System.String]$WorkingDir = "$global:UserTempDirectoryPath\HardeningXStuff\" + [System.IO.DirectoryInfo]$WorkingDir = "$global:UserTempDirectoryPath\HardeningXStuff\" # change location to the new directory Set-Location $WorkingDir @@ -701,7 +701,7 @@ try { #region Microsoft-Security-Baseline # ================================================Microsoft Security Baseline============================================== $CurrentMainStep++ - switch (Select-Option -Options 'Yes', 'Yes, With the Optional Overrides (Recommended)' , 'No', 'Exit' -Message "`nApply Microsoft Security Baseline ?") { + :MicrosoftSecurityBaselinesCategoryLabel switch (Select-Option -Options 'Yes', 'Yes, With the Optional Overrides (Recommended)' , 'No', 'Exit' -Message "`nApply Microsoft Security Baseline ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Microsoft Security Baseline' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -735,7 +735,7 @@ try { # Re-enables the XblGameSave Standby Task that gets disabled by Microsoft Security Baselines SCHTASKS.EXE /Change /TN \Microsoft\XblGameSave\XblGameSaveTask /Enable } - 'No' { break } + 'No' { break MicrosoftSecurityBaselinesCategoryLabel } 'Exit' { &$CleanUp } } # ==============================================End of Microsoft Security Baselines============================================ @@ -744,7 +744,7 @@ try { #region Microsoft-365-Apps-Security-Baseline # ================================================Microsoft 365 Apps Security Baseline============================================== $CurrentMainStep++ - switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nApply Microsoft 365 Apps Security Baseline ?") { + :Microsoft365AppsSecurityBaselinesCategoryLabel switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nApply Microsoft 365 Apps Security Baseline ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Microsoft 365 Apps Security Baseline' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -757,7 +757,7 @@ try { # Run the official PowerShell script included in the Microsoft Security Baseline file we downloaded from Microsoft servers .\Baseline-LocalInstall.ps1 - } 'No' { break } + } 'No' { break Microsoft365AppsSecurityBaselinesCategoryLabel } 'Exit' { &$CleanUp } } # ================================================End of Microsoft 365 Apps Security Baseline============================================== @@ -888,20 +888,29 @@ try { if (-NOT (($BlockListScheduledTaskState -eq 'Ready' -or $BlockListScheduledTaskState -eq 'Running'))) { switch (Select-Option -SubCategory -Options 'Yes', 'No', 'Exit' -Message "`nCreate scheduled task for fast weekly Microsoft recommended driver block list update ?") { 'Yes' { + # Get the SID of the SYSTEM account. It is a well-known SID, but still querying it, going to use it to create the scheduled task - $SYSTEMSID = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null) - # create a scheduled task that runs every 7 days - $Action = New-ScheduledTaskAction -Execute 'Powershell.exe' ` + [System.Security.Principal.SecurityIdentifier]$SYSTEMSID = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null) + + # Create a scheduled task action, this defines how to download and install the latest Microsoft Recommended Driver Block Rules + [Microsoft.Management.Infrastructure.CimInstance]$Action = New-ScheduledTaskAction -Execute 'Powershell.exe' ` -Argument '-NoProfile -WindowStyle Hidden -command "& {try {Invoke-WebRequest -Uri "https://aka.ms/VulnerableDriverBlockList" -OutFile VulnerableDriverBlockList.zip -ErrorAction Stop}catch{exit};Expand-Archive .\VulnerableDriverBlockList.zip -DestinationPath "VulnerableDriverBlockList" -Force;Rename-Item .\VulnerableDriverBlockList\SiPolicy_Enforced.p7b -NewName "SiPolicy.p7b" -Force;Copy-Item .\VulnerableDriverBlockList\SiPolicy.p7b -Destination "C:\Windows\System32\CodeIntegrity";citool --refresh -json;Remove-Item .\VulnerableDriverBlockList -Recurse -Force;Remove-Item .\VulnerableDriverBlockList.zip -Force;}"' - $TaskPrincipal = New-ScheduledTaskPrincipal -LogonType S4U -UserId $($SYSTEMSID.Value) -RunLevel Highest - # trigger - $Time = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours(1) -RepetitionInterval (New-TimeSpan -Days 7) - # register the task + + # Create a scheduled task principal and assign the SYSTEM account's SID to it so that the task will run under its context + [Microsoft.Management.Infrastructure.CimInstance]$TaskPrincipal = New-ScheduledTaskPrincipal -LogonType S4U -UserId $($SYSTEMSID.Value) -RunLevel Highest + + # Create a trigger for the scheduled task. The task will first run one hour after its creation and from then on will run every 7 days, indefinitely + [Microsoft.Management.Infrastructure.CimInstance]$Time = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours(1) -RepetitionInterval (New-TimeSpan -Days 7) + + # Register the scheduled task Register-ScheduledTask -Action $Action -Trigger $Time -Principal $TaskPrincipal -TaskPath 'MSFT Driver Block list update' -TaskName 'MSFT Driver Block list update' -Description 'Microsoft Recommended Driver Block List update' - # define advanced settings for the task - $TaskSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -Compatibility Win8 -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 3) - # add advanced settings we defined to the task + + # Define advanced settings for the scheduled task + [Microsoft.Management.Infrastructure.CimInstance]$TaskSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -Compatibility 'Win8' -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 3) -RestartCount 4 -RestartInterval (New-TimeSpan -Hours 6) -RunOnlyIfNetworkAvailable + + # Add the advanced settings we defined above to the scheduled task Set-ScheduledTask -TaskName 'MSFT Driver Block list update' -TaskPath 'MSFT Driver Block list update' -Settings $TaskSettings + } 'No' { break } 'Exit' { &$CleanUp } } @@ -928,7 +937,7 @@ try { #region Attack-Surface-Reduction-Rules # =========================================Attack Surface Reduction Rules================================================== $CurrentMainStep++ - switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Attack Surface Reduction Rules category ?") { + :ASRRulesCategoryLabel switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Attack Surface Reduction Rules category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Attack Surface Reduction Rules' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -936,7 +945,7 @@ try { Set-Location "$WorkingDir\LGPO_30" .\LGPO.exe /q /m '..\Security-Baselines-X\Attack Surface Reduction Rules Policies\registry.pol' - } 'No' { break } + } 'No' { break ASRRulesCategoryLabel } 'Exit' { &$CleanUp } } # =========================================End of Attack Surface Reduction Rules=========================================== diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index 8c4bddaae..3a1134fd3 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -335,7 +335,7 @@ function Get-AvailableRemovableDrives { Sort-Object -Property DriveLetter | Select-Object DriveLetter, FileSystemType, DriveType, @{Name = 'Size'; Expression = { '{0:N2}' -f ($_.Size / 1GB) + ' GB' } } } - 'Skip encryptions altogether' { break BitLockerCategoryLabel } + 'Skip encryptions altogether' { break BitLockerCategoryLabel } # Breaks from the BitLocker category and won't process Non-OS Drives 'Exit' { &$CleanUp } } } @@ -343,10 +343,10 @@ function Get-AvailableRemovableDrives { } # Initialize the maximum length variables but make sure the column widths are at least as wide as their titles such as 'DriveLetter' or 'FileSystemType' etc. - [int]$DriveLetterLength = 10 - [int]$FileSystemTypeLength = 13 - [int]$DriveTypeLength = 8 - [int]$SizeLength = 3 + [System.Int64]$DriveLetterLength = 10 + [System.Int64]$FileSystemTypeLength = 13 + [System.Int64]$DriveTypeLength = 8 + [System.Int64]$SizeLength = 3 # Loop through each element in the array foreach ($drive in $AvailableRemovableDrives) { @@ -400,7 +400,7 @@ function Get-AvailableRemovableDrives { } # Get the max count of available network drives and add 1 to it, assign the number as exit value to break the loop when selected - [int]$ExitCodeRemovableDriveSelection = $AvailableRemovableDrives.Count + 1 + [System.Int64]$ExitCodeRemovableDriveSelection = $AvailableRemovableDrives.Count + 1 # Write an exit option at the end of the table Write-Host ('{0,-4}' -f "$ExitCodeRemovableDriveSelection") -NoNewline -ForegroundColor DarkRed @@ -412,10 +412,10 @@ function Get-AvailableRemovableDrives { # Initialize a flag to indicate if the input is valid or not [bool]$IsValid = $false # Initialize a variable to store the parsed integer value - [int]$ParsedChoice = 0 + [System.Int64]$ParsedChoice = 0 # Try to parse the input as an integer # If the parsing succeeded, check if the input is within the range - if ([int]::TryParse($Choice, [ref]$ParsedChoice)) { + if ([System.Int64]::TryParse($Choice, [ref]$ParsedChoice)) { if ($ParsedChoice -in 1..$ExitCodeRemovableDriveSelection) { $IsValid = $true break @@ -554,7 +554,7 @@ try { # create our working directory New-Item -ItemType Directory -Path "$global:UserTempDirectoryPath\HardeningXStuff\" -Force | Out-Null # working directory assignment - [System.String]$WorkingDir = "$global:UserTempDirectoryPath\HardeningXStuff\" + [System.IO.DirectoryInfo]$WorkingDir = "$global:UserTempDirectoryPath\HardeningXStuff\" # change location to the new directory Set-Location $WorkingDir @@ -701,7 +701,7 @@ try { #region Microsoft-Security-Baseline # ================================================Microsoft Security Baseline============================================== $CurrentMainStep++ - switch (Select-Option -Options 'Yes', 'Yes, With the Optional Overrides (Recommended)' , 'No', 'Exit' -Message "`nApply Microsoft Security Baseline ?") { + :MicrosoftSecurityBaselinesCategoryLabel switch (Select-Option -Options 'Yes', 'Yes, With the Optional Overrides (Recommended)' , 'No', 'Exit' -Message "`nApply Microsoft Security Baseline ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Microsoft Security Baseline' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -735,7 +735,7 @@ try { # Re-enables the XblGameSave Standby Task that gets disabled by Microsoft Security Baselines SCHTASKS.EXE /Change /TN \Microsoft\XblGameSave\XblGameSaveTask /Enable } - 'No' { break } + 'No' { break MicrosoftSecurityBaselinesCategoryLabel } 'Exit' { &$CleanUp } } # ==============================================End of Microsoft Security Baselines============================================ @@ -744,7 +744,7 @@ try { #region Microsoft-365-Apps-Security-Baseline # ================================================Microsoft 365 Apps Security Baseline============================================== $CurrentMainStep++ - switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nApply Microsoft 365 Apps Security Baseline ?") { + :Microsoft365AppsSecurityBaselinesCategoryLabel switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nApply Microsoft 365 Apps Security Baseline ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Microsoft 365 Apps Security Baseline' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -757,7 +757,7 @@ try { # Run the official PowerShell script included in the Microsoft Security Baseline file we downloaded from Microsoft servers .\Baseline-LocalInstall.ps1 - } 'No' { break } + } 'No' { break Microsoft365AppsSecurityBaselinesCategoryLabel } 'Exit' { &$CleanUp } } # ================================================End of Microsoft 365 Apps Security Baseline============================================== @@ -888,20 +888,29 @@ try { if (-NOT (($BlockListScheduledTaskState -eq 'Ready' -or $BlockListScheduledTaskState -eq 'Running'))) { switch (Select-Option -SubCategory -Options 'Yes', 'No', 'Exit' -Message "`nCreate scheduled task for fast weekly Microsoft recommended driver block list update ?") { 'Yes' { + # Get the SID of the SYSTEM account. It is a well-known SID, but still querying it, going to use it to create the scheduled task - $SYSTEMSID = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null) - # create a scheduled task that runs every 7 days - $Action = New-ScheduledTaskAction -Execute 'Powershell.exe' ` + [System.Security.Principal.SecurityIdentifier]$SYSTEMSID = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null) + + # Create a scheduled task action, this defines how to download and install the latest Microsoft Recommended Driver Block Rules + [Microsoft.Management.Infrastructure.CimInstance]$Action = New-ScheduledTaskAction -Execute 'Powershell.exe' ` -Argument '-NoProfile -WindowStyle Hidden -command "& {try {Invoke-WebRequest -Uri "https://aka.ms/VulnerableDriverBlockList" -OutFile VulnerableDriverBlockList.zip -ErrorAction Stop}catch{exit};Expand-Archive .\VulnerableDriverBlockList.zip -DestinationPath "VulnerableDriverBlockList" -Force;Rename-Item .\VulnerableDriverBlockList\SiPolicy_Enforced.p7b -NewName "SiPolicy.p7b" -Force;Copy-Item .\VulnerableDriverBlockList\SiPolicy.p7b -Destination "C:\Windows\System32\CodeIntegrity";citool --refresh -json;Remove-Item .\VulnerableDriverBlockList -Recurse -Force;Remove-Item .\VulnerableDriverBlockList.zip -Force;}"' - $TaskPrincipal = New-ScheduledTaskPrincipal -LogonType S4U -UserId $($SYSTEMSID.Value) -RunLevel Highest - # trigger - $Time = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours(1) -RepetitionInterval (New-TimeSpan -Days 7) - # register the task + + # Create a scheduled task principal and assign the SYSTEM account's SID to it so that the task will run under its context + [Microsoft.Management.Infrastructure.CimInstance]$TaskPrincipal = New-ScheduledTaskPrincipal -LogonType S4U -UserId $($SYSTEMSID.Value) -RunLevel Highest + + # Create a trigger for the scheduled task. The task will first run one hour after its creation and from then on will run every 7 days, indefinitely + [Microsoft.Management.Infrastructure.CimInstance]$Time = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours(1) -RepetitionInterval (New-TimeSpan -Days 7) + + # Register the scheduled task Register-ScheduledTask -Action $Action -Trigger $Time -Principal $TaskPrincipal -TaskPath 'MSFT Driver Block list update' -TaskName 'MSFT Driver Block list update' -Description 'Microsoft Recommended Driver Block List update' - # define advanced settings for the task - $TaskSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -Compatibility Win8 -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 3) - # add advanced settings we defined to the task + + # Define advanced settings for the scheduled task + [Microsoft.Management.Infrastructure.CimInstance]$TaskSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -Compatibility 'Win8' -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 3) -RestartCount 4 -RestartInterval (New-TimeSpan -Hours 6) -RunOnlyIfNetworkAvailable + + # Add the advanced settings we defined above to the scheduled task Set-ScheduledTask -TaskName 'MSFT Driver Block list update' -TaskPath 'MSFT Driver Block list update' -Settings $TaskSettings + } 'No' { break } 'Exit' { &$CleanUp } } @@ -928,7 +937,7 @@ try { #region Attack-Surface-Reduction-Rules # =========================================Attack Surface Reduction Rules================================================== $CurrentMainStep++ - switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Attack Surface Reduction Rules category ?") { + :ASRRulesCategoryLabel switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Attack Surface Reduction Rules category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Attack Surface Reduction Rules' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -936,7 +945,7 @@ try { Set-Location "$WorkingDir\LGPO_30" .\LGPO.exe /q /m '..\Security-Baselines-X\Attack Surface Reduction Rules Policies\registry.pol' - } 'No' { break } + } 'No' { break ASRRulesCategoryLabel } 'Exit' { &$CleanUp } } # =========================================End of Attack Surface Reduction Rules=========================================== From 9dfc42660b609d439ff7d3f87bb9e1525048d1cb Mon Sep 17 00:00:00 2001 From: Violet Date: Fri, 17 Nov 2023 18:36:07 +0000 Subject: [PATCH 24/34] Overall improvements to the code Improved variable types to be more explicit and safe. Using full variable type names instead of their aliases. Using 'Unrestricted' instead of 'Bypass' when setting the execution policy for the current process. Unrestricted is more secure than Bypass because if a script is code signed then tampered, you will see an error, but in bypass mode, no code sign tamper detection happens Fixed visual issues and also added new ones --- .../Main files/Harden-Windows-Security.ps1 | 274 ++++++++++++------ Harden-Windows-Security.ps1 | 274 ++++++++++++------ 2 files changed, 374 insertions(+), 174 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index e018c34de..d8dcdc7c3 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -87,19 +87,26 @@ #> # Change the execution policy temporarily only for the current PowerShell session -Set-ExecutionPolicy Bypass -Scope Process +# Unrestricted is more secure than Bypass because if a script is code signed then tampered, you will see an error, but in bypass mode, no code sign tamper detection happens +Set-ExecutionPolicy -ExecutionPolicy 'Unrestricted' -Scope Process -Force + +# Get the current title of the PowerShell +[string]$CurrentPowerShellTitle = $Host.UI.RawUI.WindowTitle + +# Change the title of the Windows Terminal for PowerShell tab +$Host.UI.RawUI.WindowTitle = '❤️‍🔥Harden Windows Security❤️‍🔥' # Defining global script variables # Current script's version, the same as the version at the top in the script info section -[datetime]$CurrentVersion = '2023.11.17' +[System.DateTime]$CurrentVersion = '2023.11.17' # Minimum OS build number required for the hardening measures used in this script -[decimal]$Requiredbuild = '22621.2428' +[System.Decimal]$Requiredbuild = '22621.2428' # Fetching Temp Directory [System.String]$global:UserTempDirectoryPath = [System.IO.Path]::GetTempPath() # The total number of the main categories for the parent/main progress bar to render [System.Int64]$TotalMainSteps = 18 # Defining a global boolean variable to determine whether optional diagnostic data should be enabled for Smart App Control or not -[bool]$ShouldEnableOptionalDiagnosticData = $false +[System.Boolean]$ShouldEnableOptionalDiagnosticData = $false #region Functions # Questions function @@ -107,7 +114,7 @@ function Select-Option { param( [parameter(Mandatory = $True)][System.String]$Message, # Contains the main prompt message [parameter(Mandatory = $True)][System.String[]]$Options, - [parameter(Mandatory = $false)][switch]$SubCategory, + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SubCategory, [parameter(Mandatory = $false)][System.String]$ExtraMessage # Contains any extra notes for sub-categories ) @@ -254,68 +261,65 @@ Function Write-SmartText { [parameter(Mandatory = $True)] [Alias('I')] - [System.String]$InputText - ) + [System.String]$InputText, - begin { - - # Determining if PowerShell is core to use modern styling - [bool]$IsCore = $false - if ([version]$PSVersionTable.PSVersion -ge [version]7.3) { - [bool]$IsCore = $True - } - - } - - process { + [parameter(Mandatory = $false)] + [Alias('N')] + [System.Management.Automation.SwitchParameter]$NoNewLineLegacy # Only used with Legacy colors to write them on the same line, used by the function that gets the removable drives for BitLocker Enhanced security level encryption + ) - # Check if the current PowerShell is Core using the global variable - if ($IsCore) { - - switch ($CustomColor) { - 'Fuchsia' { Write-Host "$($PSStyle.Foreground.FromRGB(236,68,155))$InputText$($PSStyle.Reset)"; break } - 'Orange' { Write-Host "$($PSStyle.Foreground.FromRGB(255,165,0))$InputText$($PSStyle.Reset)"; break } - 'NeonGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(153,244,67))$InputText$($PSStyle.Reset)"; break } - 'MintGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(152,255,152))$InputText$($PSStyle.Reset)"; break } - 'PinkBoldBlink' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Blink)$InputText$($PSStyle.Reset)"; break } - 'PinkBold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Reverse)$InputText$($PSStyle.Reset)"; break } - 'Gold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,215,0))$InputText$($PSStyle.Reset)"; break } - 'VioletNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRGB(153,0,255))$InputText$($PSStyle.Reset)" -NoNewline } - 'PinkNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRGB(255,0,230))$InputText$($PSStyle.Reset)" -NoNewline } - 'Violet' { Write-Host "$($PSStyle.Foreground.FromRGB(153,0,255))$InputText$($PSStyle.Reset)" -NoNewline } - 'Pink' { Write-Host "$($PSStyle.Foreground.FromRGB(255,0,230))$InputText$($PSStyle.Reset)" -NoNewline } - 'LavenderNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(255,179,255))$InputText$($PSStyle.Reset)" -NoNewline } - 'TeaGreenNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(133, 222, 119))$InputText$($PSStyle.Reset)" -NoNewline } - 'Rainbow' { - $colors = @( - [System.Drawing.Color]::Pink, - [System.Drawing.Color]::HotPink, - [System.Drawing.Color]::SkyBlue, - [System.Drawing.Color]::HotPink, - [System.Drawing.Color]::SkyBlue, - [System.Drawing.Color]::LightSkyBlue, - [System.Drawing.Color]::LightGreen, - [System.Drawing.Color]::Coral, - [System.Drawing.Color]::Plum, - [System.Drawing.Color]::Gold - ) + # Determining if PowerShell edition is Core to use modern styling + if ($PSVersionTable.PSEdition -eq 'Core') { + + switch ($CustomColor) { + 'Fuchsia' { Write-Host "$($PSStyle.Foreground.FromRGB(236,68,155))$InputText$($PSStyle.Reset)"; break } + 'Orange' { Write-Host "$($PSStyle.Foreground.FromRGB(255,165,0))$InputText$($PSStyle.Reset)"; break } + 'NeonGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(153,244,67))$InputText$($PSStyle.Reset)"; break } + 'MintGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(152,255,152))$InputText$($PSStyle.Reset)"; break } + 'PinkBoldBlink' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Blink)$InputText$($PSStyle.Reset)"; break } + 'PinkBold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Reverse)$InputText$($PSStyle.Reset)"; break } + 'Gold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,215,0))$InputText$($PSStyle.Reset)"; break } + 'VioletNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRGB(153,0,255))$InputText$($PSStyle.Reset)" -NoNewline; break } + 'PinkNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRGB(255,0,230))$InputText$($PSStyle.Reset)" -NoNewline; break } + 'Violet' { Write-Host "$($PSStyle.Foreground.FromRGB(153,0,255))$InputText$($PSStyle.Reset)" -NoNewline; break } + 'Pink' { Write-Host "$($PSStyle.Foreground.FromRGB(255,0,230))$InputText$($PSStyle.Reset)" -NoNewline; break } + 'LavenderNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(255,179,255))$InputText$($PSStyle.Reset)" -NoNewline; break } + 'TeaGreenNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(133, 222, 119))$InputText$($PSStyle.Reset)" -NoNewline; break } + 'Rainbow' { + $colors = @( + [System.Drawing.Color]::Pink, + [System.Drawing.Color]::HotPink, + [System.Drawing.Color]::SkyBlue, + [System.Drawing.Color]::HotPink, + [System.Drawing.Color]::SkyBlue, + [System.Drawing.Color]::LightSkyBlue, + [System.Drawing.Color]::LightGreen, + [System.Drawing.Color]::Coral, + [System.Drawing.Color]::Plum, + [System.Drawing.Color]::Gold + ) - $output = '' - for ($i = 0; $i -lt $InputText.Length; $i++) { - $color = $colors[$i % $colors.Length] - $output += "$($PSStyle.Foreground.FromRGB($color.R, $color.G, $color.B))$($PSStyle.Blink)$($InputText[$i])$($PSStyle.BlinkOff)$($PSStyle.Reset)" - } - Write-Output $output - break + $output = '' + for ($i = 0; $i -lt $InputText.Length; $i++) { + $color = $colors[$i % $colors.Length] + $output += "$($PSStyle.Foreground.FromRGB($color.R, $color.G, $color.B))$($PSStyle.Blink)$($InputText[$i])$($PSStyle.BlinkOff)$($PSStyle.Reset)" } - - Default { Throw 'Unspecified Color' } + Write-Output $output + break } + + Default { Throw 'Unspecified Color' } + } + } + else { + if ($NoNewLineLegacy) { + Write-Host $InputText -ForegroundColor $GenericColor -NoNewline } else { Write-Host $InputText -ForegroundColor $GenericColor } } + } # Function to get a removable drive to be used by BitLocker category @@ -375,26 +379,26 @@ function Get-AvailableRemovableDrives { # Creating a heading for the columns # Write the index of the drive - Write-SmartText -C LavenderNoNewLine -G Blue -I ('{0,-4}' -f '#') + Write-SmartText -C LavenderNoNewLine -G Blue -N -I ('{0,-4}' -f '#') # Write the name of the drive - Write-SmartText -C TeaGreenNoNewLine -G Yellow -I ("|{0,-$DriveLetterLength}" -f 'DriveLetter') + Write-SmartText -C TeaGreenNoNewLine -G Yellow -N -I ("|{0,-$DriveLetterLength}" -f 'DriveLetter') # Write the File System Type of the drive - Write-SmartText -C PinkNoNewLine -G Magenta -I ("|{0,-$FileSystemTypeLength}" -f 'FileSystemType') + Write-SmartText -C PinkNoNewLine -G Magenta -N -I ("|{0,-$FileSystemTypeLength}" -f 'FileSystemType') # Write the Drive Type of the drive - Write-SmartText -C VioletNoNewLine -G Green -I ("|{0,-$DriveTypeLength}" -f 'DriveType') + Write-SmartText -C VioletNoNewLine -G Green -N -I ("|{0,-$DriveTypeLength}" -f 'DriveType') # Write the Size of the drive Write-SmartText -C Gold -G Cyan ("|{0,-$SizeLength}" -f 'Size') # Loop through the drives and display them in a table with colors for ($i = 0; $i -lt $AvailableRemovableDrives.Count; $i++) { # Write the index of the drive - Write-SmartText -C LavenderNoNewLine -G Blue -I ('{0,-4}' -f ($i + 1)) + Write-SmartText -C LavenderNoNewLine -N -G Blue -I ('{0,-4}' -f ($i + 1)) # Write the name of the drive - Write-SmartText -C TeaGreenNoNewLine -G Yellow -I ("|{0,-$DriveLetterLength}" -f $AvailableRemovableDrives[$i].DriveLetter) + Write-SmartText -C TeaGreenNoNewLine -N -G Yellow -I ("|{0,-$DriveLetterLength}" -f $AvailableRemovableDrives[$i].DriveLetter) # Write the File System Type of the drive - Write-SmartText -C PinkNoNewLine -G Magenta -I ("|{0,-$FileSystemTypeLength}" -f $AvailableRemovableDrives[$i].FileSystemType) + Write-SmartText -C PinkNoNewLine -N -G Magenta -I ("|{0,-$FileSystemTypeLength}" -f $AvailableRemovableDrives[$i].FileSystemType) # Write the Drive Type of the drive - Write-SmartText -C VioletNoNewLine -G Green -I ("|{0,-$DriveTypeLength}" -f $AvailableRemovableDrives[$i].DriveType) + Write-SmartText -C VioletNoNewLine -N -G Green -I ("|{0,-$DriveTypeLength}" -f $AvailableRemovableDrives[$i].DriveType) # Write the Size of the drive Write-SmartText -C Gold -G Cyan ("|{0,-$SizeLength}" -f $AvailableRemovableDrives[$i].Size) } @@ -410,7 +414,7 @@ function Get-AvailableRemovableDrives { function Confirm-Choice { param([string]$Choice) # Initialize a flag to indicate if the input is valid or not - [bool]$IsValid = $false + [System.Boolean]$IsValid = $false # Initialize a variable to store the parsed integer value [System.Int64]$ParsedChoice = 0 # Try to parse the input as an integer @@ -471,7 +475,7 @@ if (Test-IsAdmin) { try { try { Invoke-WithoutProgress { - [datetime]$global:LatestVersion = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/Version.txt' + [System.DateTime]$global:LatestVersion = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/Version.txt' } } catch { @@ -492,6 +496,9 @@ try { Write-SmartText -CustomColor Rainbow -GenericColor Cyan -InputText "############################################################################################################`r`n" Write-SmartText -CustomColor MintGreen -GenericColor Cyan -InputText "### Please read the Readme in the GitHub repository: https://github.com/HotCakeX/Harden-Windows-Security ###`r`n" Write-SmartText -CustomColor Rainbow -GenericColor Cyan -InputText "############################################################################################################`r`n" + + # Show a prompt to the user if they're using the old PowerShell + if ($PSVersionTable.PSEdition -eq 'Desktop') { Write-Host "You're using old PowerShell. Use the new PowerShell Core for much better styling and performance:`nhttps://apps.microsoft.com/detail/powershell/9MZ1SNWT0N5D" -ForegroundColor Yellow } #region RequirementsCheck # check if user's OS is Windows Home edition @@ -502,11 +509,11 @@ try { # check if user's OS is the latest build # Get OS build version - [decimal]$OSBuild = [System.Environment]::OSVersion.Version.Build + [System.Decimal]$OSBuild = [System.Environment]::OSVersion.Version.Build # Get Update Build Revision (UBR) number - [decimal]$UBR = Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'UBR' + [System.Decimal]$UBR = Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'UBR' # Create full OS build number as seen in Windows Settings - [decimal]$FullOSBuild = "$OSBuild.$UBR" + [System.Decimal]$FullOSBuild = "$OSBuild.$UBR" # Make sure the current OS build is equal or greater than the required build if (-NOT ($FullOSBuild -ge $Requiredbuild)) { Write-Error "You're not using the latest build of the Windows OS. A minimum build of $Requiredbuild is required but your OS build is $FullOSBuild`nPlease go to Windows Update to install the updates and then try again." @@ -521,8 +528,8 @@ try { } # check to make sure TPM is available and enabled - [bool]$TPMFlag1 = (Get-Tpm).tpmpresent - [bool]$TPMFlag2 = (Get-Tpm).tpmenabled + [System.Boolean]$TPMFlag1 = (Get-Tpm).tpmpresent + [System.Boolean]$TPMFlag2 = (Get-Tpm).tpmenabled if (!$TPMFlag1 -or !$TPMFlag2) { Write-Error 'TPM is not available or enabled, please go to your UEFI settings to enable it and then try again.' break @@ -563,7 +570,7 @@ try { Set-Location $HOME Remove-Item -Recurse -Path "$global:UserTempDirectoryPath\HardeningXStuff\" -Force # Disable progress bars - 0..5 | ForEach-Object { Write-Progress -Id $_ -Activity 'Done' -Completed } + 0..6 | ForEach-Object { Write-Progress -Id $_ -Activity 'Done' -Completed } exit } @@ -574,7 +581,8 @@ try { [System.Int64]$CurrentMainStep = 0 Write-Progress -Id 0 -Activity 'Downloading the required files' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete 1 - + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Downloading' try { # Create an array of files to download @@ -684,6 +692,10 @@ try { #region Windows-Boot-Manager-revocations-for-Secure-Boot KB5025885 # ============================May 9 2023 Windows Boot Manager revocations for Secure Boot ================================= $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = '🫶 Category 0' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nApply May 9 2023 Windows Boot Manager Security measures ? (If you've already run this category, don't need to do it again)") { 'Yes' { Write-Progress -Id 0 -Activity 'Windows Boot Manager revocations for Secure Boot' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -701,10 +713,14 @@ try { #region Microsoft-Security-Baseline # ================================================Microsoft Security Baseline============================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Security Baselines' + :MicrosoftSecurityBaselinesCategoryLabel switch (Select-Option -Options 'Yes', 'Yes, With the Optional Overrides (Recommended)' , 'No', 'Exit' -Message "`nApply Microsoft Security Baseline ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Microsoft Security Baseline' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) - + # Copy LGPO.exe from its folder to Microsoft Security Baseline folder in order to get it ready to be used by PowerShell script Copy-Item -Path '.\LGPO_30\LGPO.exe' -Destination "$MicrosoftSecurityBaselinePath\Scripts\Tools" @@ -744,6 +760,10 @@ try { #region Microsoft-365-Apps-Security-Baseline # ================================================Microsoft 365 Apps Security Baseline============================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'M365 Apps Security' + :Microsoft365AppsSecurityBaselinesCategoryLabel switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nApply Microsoft 365 Apps Security Baseline ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Microsoft 365 Apps Security Baseline' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -766,6 +786,10 @@ try { #region Microsoft-Defender # ================================================Microsoft Defender======================================================= $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'MSFT Defender' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Microsoft Defender category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Microsoft Defender' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -847,7 +871,7 @@ try { } # Turn on Data Execution Prevention (DEP) for all applications, including 32-bit programs - bcdedit.exe /set '{current}' nx AlwaysOn + bcdedit.exe /set '{current}' nx AlwaysOn | Out-Null # Suggest turning on Smart App Control only if it's in Eval mode if ((Get-MpComputerStatus).SmartAppControlState -eq 'Eval') { @@ -937,6 +961,10 @@ try { #region Attack-Surface-Reduction-Rules # =========================================Attack Surface Reduction Rules================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'ASR Rules' + :ASRRulesCategoryLabel switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Attack Surface Reduction Rules category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Attack Surface Reduction Rules' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -954,6 +982,10 @@ try { #region Bitlocker-Settings # ==========================================Bitlocker Settings============================================================= $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'BitLocker' + :BitLockerCategoryLabel switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Bitlocker category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Bitlocker Settings' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -1013,7 +1045,7 @@ try { '@ Add-Type -TypeDefinition $BootDMAProtectionCheck # returns true or false depending on whether Kernel DMA Protection is on or off - [bool]$BootDMAProtection = ([SystemInfo.NativeMethods]::BootDmaCheck()) -ne 0 + [System.Boolean]$BootDMAProtection = ([SystemInfo.NativeMethods]::BootDmaCheck()) -ne 0 # Change current working directory to the LGPO's folder Set-Location "$WorkingDir\LGPO_30" @@ -1073,7 +1105,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va "@ } - switch (Select-Option -SubCategory -Options 'Normal: TPM + Startup PIN + Recovery Password', 'Enhanced: TPM + Startup PIN + Startup Key + Recovery Password', 'Skip encryptions altogether', 'Exit' -Message "`nPlease select your desired security level" -ExtraMessage "If you are not sure, refer to the BitLocker category in the GitHub Readme`n") { + :OSDriveEncryptionLabel switch (Select-Option -SubCategory -Options 'Normal: TPM + Startup PIN + Recovery Password', 'Enhanced: TPM + Startup PIN + Startup Key + Recovery Password', 'Skip encryptions altogether', 'Exit' -Message "`nPlease select your desired security level" -ExtraMessage "If you are not sure, refer to the BitLocker category in the GitHub Readme`n") { 'Normal: TPM + Startup PIN + Recovery Password' { # check if Bitlocker is enabled for the system drive with Normal security level @@ -1083,6 +1115,14 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector # Get the key protector types of the OS Drive [System.String[]]$KeyProtectorTypesOSDrive = $KeyProtectorsOSDrive.keyprotectortype + + if ($KeyProtectorTypesOSDrive -contains 'TpmPinStartupKey' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { + + switch (Select-Option -SubCategory -Options 'Yes', 'Skip OS Drive' , 'Exit' -Message "`nThe OS Drive is already encrypted with Enhanced Security level." -ExtraMessage "Are you sure you want to change it to Normal Security level?`n" ) { + 'Skip OS Drive' { break OSDriveEncryptionLabel } + 'Exit' { &$CleanUp } + } + } # check if TPM + PIN + recovery password are being used as key protectors for the OS Drive if ($KeyProtectorTypesOSDrive -contains 'Tpmpin' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { @@ -1123,7 +1163,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) # Compare the PINs and make sure they match - [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 + [System.Boolean]$TheyMatch = Compare-SecureString $Pin1 $Pin2 # If the PINs match and they are at least 10 characters long, max 20 characters if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { [securestring]$Pin = $Pin1 @@ -1163,7 +1203,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I 'Enter a Pin for Bitlocker startup (between 10 to 20 characters)'; Read-Host -AsSecureString) [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) - [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 + [System.Boolean]$TheyMatch = Compare-SecureString $Pin1 $Pin2 if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { [securestring]$Pin = $Pin1 @@ -1239,7 +1279,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # If the OS Drive doesn't have (TpmPinStartupKey) key protector if ($KeyProtectorTypesOSDrive -notcontains 'TpmPinStartupKey') { - Write-SmartText -C Violet -G Cyan -I "`nTpm And Pin And StartupKey Protector is missing from the OS Drive, adding it now`n" + Write-SmartText -C Violet -G Cyan -I "`nTpm And Pin And StartupKey Protector is missing from the OS Drive, adding it now" # Check if the OS drive has ExternalKey key protector and if it does remove it # It's the standalone Startup Key protector which isn't secure on its own for the OS Drive @@ -1256,7 +1296,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) # Compare the PINs and make sure they match - [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 + [System.Boolean]$TheyMatch = Compare-SecureString $Pin1 $Pin2 # If the PINs match and they are at least 10 characters long, max 20 characters if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { [securestring]$Pin = $Pin1 @@ -1299,7 +1339,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) # Compare the PINs and make sure they match - [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 + [System.Boolean]$TheyMatch = Compare-SecureString $Pin1 $Pin2 # If the PINs match and they are at least 10 characters long, max 20 characters if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { [securestring]$Pin = $Pin1 @@ -1351,14 +1391,24 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va catch { # Do nothing if the key doesn't exist } - if ($HiberFileType -ne 2) { + if ($HiberFileType -ne 2) { + + Write-Progress -Id 6 -ParentId 0 -Activity 'Hibernate' -Status 'Setting Hibernate file size to full' -PercentComplete 50 + # doing this so Controlled Folder Access won't bitch about powercfg.exe Add-MpPreference -ControlledFolderAccessAllowedApplications 'C:\Windows\System32\powercfg.exe' + Start-Sleep 5 + # Set Hibernate mode to full - powercfg /h /type full + powercfg /h /type full | Out-Null + Start-Sleep 3 + Remove-MpPreference -ControlledFolderAccessAllowedApplications 'C:\Windows\System32\powercfg.exe' + + Write-Progress -Id 6 -Activity 'Setting Hibernate file size to full' -Completed + } else { Write-SmartText -C Pink -G Magenta -I "`nHibernate is already set to full.`n" @@ -1571,6 +1621,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region TLS-Security # ==============================================TLS Security=============================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'TLS' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun TLS Security category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'TLS Security' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -1610,6 +1664,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Lock-Screen # ==========================================Lock Screen==================================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Lock Screen' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Lock Screen category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Lock Screen' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -1640,6 +1698,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region User-Account-Control # ==========================================User Account Control=========================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'UAC' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun User Account Control category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'User Account Control' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -1693,6 +1755,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Windows-Firewall # ====================================================Windows Firewall===================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = '🔥 Firewall' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Windows Firewall category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Windows Firewall' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -1714,6 +1780,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Optional-Windows-Features # =================================================Optional Windows Features=============================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Optional Features' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Optional Windows Features category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Optional Windows Features' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -2008,6 +2078,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Windows-Networking # ====================================================Windows Networking=================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Networking' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Windows Networking category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Windows Networking' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -2031,6 +2105,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Miscellaneous-Configurations # ==============================================Miscellaneous Configurations=============================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Miscellaneous' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Miscellaneous Configurations category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Miscellaneous Configurations' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -2063,7 +2141,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # For tracking Lock screen unlocks and locks # auditpol /set /subcategory:"Other Logon/Logoff Events" /success:enable /failure:enable # Using GUID - auditpol /set /subcategory:"{0CCE921C-69AE-11D9-BED3-505054503030}" /success:enable /failure:enable + auditpol /set /subcategory:"{0CCE921C-69AE-11D9-BED3-505054503030}" /success:enable /failure:enable | Out-Null # Query all Audits status # auditpol /get /category:* @@ -2091,6 +2169,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Windows-Update-Configurations # ====================================================Windows Update Configurations============================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Windows Update' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nApply Windows Update Policies ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Windows Update Configurations' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -2109,6 +2191,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Edge-Browser-Configurations # ====================================================Edge Browser Configurations==================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Edge' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nApply Edge Browser Configurations ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Edge Browser Configurations' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -2130,6 +2216,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Certificate-Checking-Commands # ====================================================Certificate Checking Commands======================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Certificates' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Certificate Checking category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Certificate Checking Commands' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -2158,6 +2248,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Country-IP-Blocking # ====================================================Country IP Blocking================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Country IPs' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Country IP Blocking category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Country IP Blocking' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -2212,6 +2306,9 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Non-Admin-Commands # ====================================================Non-Admin Commands=================================================== + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Non-Admins' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Non-Admin category ?") { 'Yes' { $CurrentMainStep = $TotalMainSteps @@ -2252,7 +2349,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } finally { # Disable progress bars - 0..5 | ForEach-Object { Write-Progress -Id $_ -Activity 'Done' -Completed } + 0..6 | ForEach-Object { Write-Progress -Id $_ -Activity 'Done' -Completed } + + # Restore the title of the PowerShell back to what it was prior to running the script/module + $Host.UI.RawUI.WindowTitle = $CurrentPowerShellTitle if (Test-IsAdmin) { # Reverting the PowerShell executables allow listings in Controlled folder access diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index 3a1134fd3..1c9cd3607 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -87,19 +87,26 @@ #> # Change the execution policy temporarily only for the current PowerShell session -Set-ExecutionPolicy Bypass -Scope Process +# Unrestricted is more secure than Bypass because if a script is code signed then tampered, you will see an error, but in bypass mode, no code sign tamper detection happens +Set-ExecutionPolicy -ExecutionPolicy 'Unrestricted' -Scope Process -Force + +# Get the current title of the PowerShell +[string]$CurrentPowerShellTitle = $Host.UI.RawUI.WindowTitle + +# Change the title of the Windows Terminal for PowerShell tab +$Host.UI.RawUI.WindowTitle = '❤️‍🔥Harden Windows Security❤️‍🔥' # Defining global script variables # Current script's version, the same as the version at the top in the script info section -[datetime]$CurrentVersion = '2023.11.17' +[System.DateTime]$CurrentVersion = '2023.11.17' # Minimum OS build number required for the hardening measures used in this script -[decimal]$Requiredbuild = '22621.2428' +[System.Decimal]$Requiredbuild = '22621.2428' # Fetching Temp Directory [System.String]$global:UserTempDirectoryPath = [System.IO.Path]::GetTempPath() # The total number of the main categories for the parent/main progress bar to render [System.Int64]$TotalMainSteps = 18 # Defining a global boolean variable to determine whether optional diagnostic data should be enabled for Smart App Control or not -[bool]$ShouldEnableOptionalDiagnosticData = $false +[System.Boolean]$ShouldEnableOptionalDiagnosticData = $false #region Functions # Questions function @@ -107,7 +114,7 @@ function Select-Option { param( [parameter(Mandatory = $True)][System.String]$Message, # Contains the main prompt message [parameter(Mandatory = $True)][System.String[]]$Options, - [parameter(Mandatory = $false)][switch]$SubCategory, + [parameter(Mandatory = $false)][System.Management.Automation.SwitchParameter]$SubCategory, [parameter(Mandatory = $false)][System.String]$ExtraMessage # Contains any extra notes for sub-categories ) @@ -254,68 +261,65 @@ Function Write-SmartText { [parameter(Mandatory = $True)] [Alias('I')] - [System.String]$InputText - ) + [System.String]$InputText, - begin { - - # Determining if PowerShell is core to use modern styling - [bool]$IsCore = $false - if ([version]$PSVersionTable.PSVersion -ge [version]7.3) { - [bool]$IsCore = $True - } - - } - - process { + [parameter(Mandatory = $false)] + [Alias('N')] + [System.Management.Automation.SwitchParameter]$NoNewLineLegacy # Only used with Legacy colors to write them on the same line, used by the function that gets the removable drives for BitLocker Enhanced security level encryption + ) - # Check if the current PowerShell is Core using the global variable - if ($IsCore) { - - switch ($CustomColor) { - 'Fuchsia' { Write-Host "$($PSStyle.Foreground.FromRGB(236,68,155))$InputText$($PSStyle.Reset)"; break } - 'Orange' { Write-Host "$($PSStyle.Foreground.FromRGB(255,165,0))$InputText$($PSStyle.Reset)"; break } - 'NeonGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(153,244,67))$InputText$($PSStyle.Reset)"; break } - 'MintGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(152,255,152))$InputText$($PSStyle.Reset)"; break } - 'PinkBoldBlink' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Blink)$InputText$($PSStyle.Reset)"; break } - 'PinkBold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Reverse)$InputText$($PSStyle.Reset)"; break } - 'Gold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,215,0))$InputText$($PSStyle.Reset)"; break } - 'VioletNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRGB(153,0,255))$InputText$($PSStyle.Reset)" -NoNewline } - 'PinkNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRGB(255,0,230))$InputText$($PSStyle.Reset)" -NoNewline } - 'Violet' { Write-Host "$($PSStyle.Foreground.FromRGB(153,0,255))$InputText$($PSStyle.Reset)" -NoNewline } - 'Pink' { Write-Host "$($PSStyle.Foreground.FromRGB(255,0,230))$InputText$($PSStyle.Reset)" -NoNewline } - 'LavenderNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(255,179,255))$InputText$($PSStyle.Reset)" -NoNewline } - 'TeaGreenNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(133, 222, 119))$InputText$($PSStyle.Reset)" -NoNewline } - 'Rainbow' { - $colors = @( - [System.Drawing.Color]::Pink, - [System.Drawing.Color]::HotPink, - [System.Drawing.Color]::SkyBlue, - [System.Drawing.Color]::HotPink, - [System.Drawing.Color]::SkyBlue, - [System.Drawing.Color]::LightSkyBlue, - [System.Drawing.Color]::LightGreen, - [System.Drawing.Color]::Coral, - [System.Drawing.Color]::Plum, - [System.Drawing.Color]::Gold - ) + # Determining if PowerShell edition is Core to use modern styling + if ($PSVersionTable.PSEdition -eq 'Core') { + + switch ($CustomColor) { + 'Fuchsia' { Write-Host "$($PSStyle.Foreground.FromRGB(236,68,155))$InputText$($PSStyle.Reset)"; break } + 'Orange' { Write-Host "$($PSStyle.Foreground.FromRGB(255,165,0))$InputText$($PSStyle.Reset)"; break } + 'NeonGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(153,244,67))$InputText$($PSStyle.Reset)"; break } + 'MintGreen' { Write-Host "$($PSStyle.Foreground.FromRGB(152,255,152))$InputText$($PSStyle.Reset)"; break } + 'PinkBoldBlink' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Blink)$InputText$($PSStyle.Reset)"; break } + 'PinkBold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,192,203))$($PSStyle.Bold)$($PSStyle.Reverse)$InputText$($PSStyle.Reset)"; break } + 'Gold' { Write-Host "$($PSStyle.Foreground.FromRgb(255,215,0))$InputText$($PSStyle.Reset)"; break } + 'VioletNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRGB(153,0,255))$InputText$($PSStyle.Reset)" -NoNewline; break } + 'PinkNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRGB(255,0,230))$InputText$($PSStyle.Reset)" -NoNewline; break } + 'Violet' { Write-Host "$($PSStyle.Foreground.FromRGB(153,0,255))$InputText$($PSStyle.Reset)" -NoNewline; break } + 'Pink' { Write-Host "$($PSStyle.Foreground.FromRGB(255,0,230))$InputText$($PSStyle.Reset)" -NoNewline; break } + 'LavenderNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(255,179,255))$InputText$($PSStyle.Reset)" -NoNewline; break } + 'TeaGreenNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(133, 222, 119))$InputText$($PSStyle.Reset)" -NoNewline; break } + 'Rainbow' { + $colors = @( + [System.Drawing.Color]::Pink, + [System.Drawing.Color]::HotPink, + [System.Drawing.Color]::SkyBlue, + [System.Drawing.Color]::HotPink, + [System.Drawing.Color]::SkyBlue, + [System.Drawing.Color]::LightSkyBlue, + [System.Drawing.Color]::LightGreen, + [System.Drawing.Color]::Coral, + [System.Drawing.Color]::Plum, + [System.Drawing.Color]::Gold + ) - $output = '' - for ($i = 0; $i -lt $InputText.Length; $i++) { - $color = $colors[$i % $colors.Length] - $output += "$($PSStyle.Foreground.FromRGB($color.R, $color.G, $color.B))$($PSStyle.Blink)$($InputText[$i])$($PSStyle.BlinkOff)$($PSStyle.Reset)" - } - Write-Output $output - break + $output = '' + for ($i = 0; $i -lt $InputText.Length; $i++) { + $color = $colors[$i % $colors.Length] + $output += "$($PSStyle.Foreground.FromRGB($color.R, $color.G, $color.B))$($PSStyle.Blink)$($InputText[$i])$($PSStyle.BlinkOff)$($PSStyle.Reset)" } - - Default { Throw 'Unspecified Color' } + Write-Output $output + break } + + Default { Throw 'Unspecified Color' } + } + } + else { + if ($NoNewLineLegacy) { + Write-Host $InputText -ForegroundColor $GenericColor -NoNewline } else { Write-Host $InputText -ForegroundColor $GenericColor } } + } # Function to get a removable drive to be used by BitLocker category @@ -375,26 +379,26 @@ function Get-AvailableRemovableDrives { # Creating a heading for the columns # Write the index of the drive - Write-SmartText -C LavenderNoNewLine -G Blue -I ('{0,-4}' -f '#') + Write-SmartText -C LavenderNoNewLine -G Blue -N -I ('{0,-4}' -f '#') # Write the name of the drive - Write-SmartText -C TeaGreenNoNewLine -G Yellow -I ("|{0,-$DriveLetterLength}" -f 'DriveLetter') + Write-SmartText -C TeaGreenNoNewLine -G Yellow -N -I ("|{0,-$DriveLetterLength}" -f 'DriveLetter') # Write the File System Type of the drive - Write-SmartText -C PinkNoNewLine -G Magenta -I ("|{0,-$FileSystemTypeLength}" -f 'FileSystemType') + Write-SmartText -C PinkNoNewLine -G Magenta -N -I ("|{0,-$FileSystemTypeLength}" -f 'FileSystemType') # Write the Drive Type of the drive - Write-SmartText -C VioletNoNewLine -G Green -I ("|{0,-$DriveTypeLength}" -f 'DriveType') + Write-SmartText -C VioletNoNewLine -G Green -N -I ("|{0,-$DriveTypeLength}" -f 'DriveType') # Write the Size of the drive Write-SmartText -C Gold -G Cyan ("|{0,-$SizeLength}" -f 'Size') # Loop through the drives and display them in a table with colors for ($i = 0; $i -lt $AvailableRemovableDrives.Count; $i++) { # Write the index of the drive - Write-SmartText -C LavenderNoNewLine -G Blue -I ('{0,-4}' -f ($i + 1)) + Write-SmartText -C LavenderNoNewLine -N -G Blue -I ('{0,-4}' -f ($i + 1)) # Write the name of the drive - Write-SmartText -C TeaGreenNoNewLine -G Yellow -I ("|{0,-$DriveLetterLength}" -f $AvailableRemovableDrives[$i].DriveLetter) + Write-SmartText -C TeaGreenNoNewLine -N -G Yellow -I ("|{0,-$DriveLetterLength}" -f $AvailableRemovableDrives[$i].DriveLetter) # Write the File System Type of the drive - Write-SmartText -C PinkNoNewLine -G Magenta -I ("|{0,-$FileSystemTypeLength}" -f $AvailableRemovableDrives[$i].FileSystemType) + Write-SmartText -C PinkNoNewLine -N -G Magenta -I ("|{0,-$FileSystemTypeLength}" -f $AvailableRemovableDrives[$i].FileSystemType) # Write the Drive Type of the drive - Write-SmartText -C VioletNoNewLine -G Green -I ("|{0,-$DriveTypeLength}" -f $AvailableRemovableDrives[$i].DriveType) + Write-SmartText -C VioletNoNewLine -N -G Green -I ("|{0,-$DriveTypeLength}" -f $AvailableRemovableDrives[$i].DriveType) # Write the Size of the drive Write-SmartText -C Gold -G Cyan ("|{0,-$SizeLength}" -f $AvailableRemovableDrives[$i].Size) } @@ -410,7 +414,7 @@ function Get-AvailableRemovableDrives { function Confirm-Choice { param([string]$Choice) # Initialize a flag to indicate if the input is valid or not - [bool]$IsValid = $false + [System.Boolean]$IsValid = $false # Initialize a variable to store the parsed integer value [System.Int64]$ParsedChoice = 0 # Try to parse the input as an integer @@ -471,7 +475,7 @@ if (Test-IsAdmin) { try { try { Invoke-WithoutProgress { - [datetime]$global:LatestVersion = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/Version.txt' + [System.DateTime]$global:LatestVersion = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/Version.txt' } } catch { @@ -492,6 +496,9 @@ try { Write-SmartText -CustomColor Rainbow -GenericColor Cyan -InputText "############################################################################################################`r`n" Write-SmartText -CustomColor MintGreen -GenericColor Cyan -InputText "### Please read the Readme in the GitHub repository: https://github.com/HotCakeX/Harden-Windows-Security ###`r`n" Write-SmartText -CustomColor Rainbow -GenericColor Cyan -InputText "############################################################################################################`r`n" + + # Show a prompt to the user if they're using the old PowerShell + if ($PSVersionTable.PSEdition -eq 'Desktop') { Write-Host "You're using old PowerShell. Use the new PowerShell Core for much better styling and performance:`nhttps://apps.microsoft.com/detail/powershell/9MZ1SNWT0N5D" -ForegroundColor Yellow } #region RequirementsCheck # check if user's OS is Windows Home edition @@ -502,11 +509,11 @@ try { # check if user's OS is the latest build # Get OS build version - [decimal]$OSBuild = [System.Environment]::OSVersion.Version.Build + [System.Decimal]$OSBuild = [System.Environment]::OSVersion.Version.Build # Get Update Build Revision (UBR) number - [decimal]$UBR = Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'UBR' + [System.Decimal]$UBR = Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'UBR' # Create full OS build number as seen in Windows Settings - [decimal]$FullOSBuild = "$OSBuild.$UBR" + [System.Decimal]$FullOSBuild = "$OSBuild.$UBR" # Make sure the current OS build is equal or greater than the required build if (-NOT ($FullOSBuild -ge $Requiredbuild)) { Write-Error "You're not using the latest build of the Windows OS. A minimum build of $Requiredbuild is required but your OS build is $FullOSBuild`nPlease go to Windows Update to install the updates and then try again." @@ -521,8 +528,8 @@ try { } # check to make sure TPM is available and enabled - [bool]$TPMFlag1 = (Get-Tpm).tpmpresent - [bool]$TPMFlag2 = (Get-Tpm).tpmenabled + [System.Boolean]$TPMFlag1 = (Get-Tpm).tpmpresent + [System.Boolean]$TPMFlag2 = (Get-Tpm).tpmenabled if (!$TPMFlag1 -or !$TPMFlag2) { Write-Error 'TPM is not available or enabled, please go to your UEFI settings to enable it and then try again.' break @@ -563,7 +570,7 @@ try { Set-Location $HOME Remove-Item -Recurse -Path "$global:UserTempDirectoryPath\HardeningXStuff\" -Force # Disable progress bars - 0..5 | ForEach-Object { Write-Progress -Id $_ -Activity 'Done' -Completed } + 0..6 | ForEach-Object { Write-Progress -Id $_ -Activity 'Done' -Completed } exit } @@ -574,7 +581,8 @@ try { [System.Int64]$CurrentMainStep = 0 Write-Progress -Id 0 -Activity 'Downloading the required files' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete 1 - + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Downloading' try { # Create an array of files to download @@ -684,6 +692,10 @@ try { #region Windows-Boot-Manager-revocations-for-Secure-Boot KB5025885 # ============================May 9 2023 Windows Boot Manager revocations for Secure Boot ================================= $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = '🫶 Category 0' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nApply May 9 2023 Windows Boot Manager Security measures ? (If you've already run this category, don't need to do it again)") { 'Yes' { Write-Progress -Id 0 -Activity 'Windows Boot Manager revocations for Secure Boot' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -701,10 +713,14 @@ try { #region Microsoft-Security-Baseline # ================================================Microsoft Security Baseline============================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Security Baselines' + :MicrosoftSecurityBaselinesCategoryLabel switch (Select-Option -Options 'Yes', 'Yes, With the Optional Overrides (Recommended)' , 'No', 'Exit' -Message "`nApply Microsoft Security Baseline ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Microsoft Security Baseline' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) - + # Copy LGPO.exe from its folder to Microsoft Security Baseline folder in order to get it ready to be used by PowerShell script Copy-Item -Path '.\LGPO_30\LGPO.exe' -Destination "$MicrosoftSecurityBaselinePath\Scripts\Tools" @@ -744,6 +760,10 @@ try { #region Microsoft-365-Apps-Security-Baseline # ================================================Microsoft 365 Apps Security Baseline============================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'M365 Apps Security' + :Microsoft365AppsSecurityBaselinesCategoryLabel switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nApply Microsoft 365 Apps Security Baseline ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Microsoft 365 Apps Security Baseline' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -766,6 +786,10 @@ try { #region Microsoft-Defender # ================================================Microsoft Defender======================================================= $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'MSFT Defender' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Microsoft Defender category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Microsoft Defender' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -847,7 +871,7 @@ try { } # Turn on Data Execution Prevention (DEP) for all applications, including 32-bit programs - bcdedit.exe /set '{current}' nx AlwaysOn + bcdedit.exe /set '{current}' nx AlwaysOn | Out-Null # Suggest turning on Smart App Control only if it's in Eval mode if ((Get-MpComputerStatus).SmartAppControlState -eq 'Eval') { @@ -937,6 +961,10 @@ try { #region Attack-Surface-Reduction-Rules # =========================================Attack Surface Reduction Rules================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'ASR Rules' + :ASRRulesCategoryLabel switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Attack Surface Reduction Rules category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Attack Surface Reduction Rules' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -954,6 +982,10 @@ try { #region Bitlocker-Settings # ==========================================Bitlocker Settings============================================================= $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'BitLocker' + :BitLockerCategoryLabel switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Bitlocker category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Bitlocker Settings' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -1013,7 +1045,7 @@ try { '@ Add-Type -TypeDefinition $BootDMAProtectionCheck # returns true or false depending on whether Kernel DMA Protection is on or off - [bool]$BootDMAProtection = ([SystemInfo.NativeMethods]::BootDmaCheck()) -ne 0 + [System.Boolean]$BootDMAProtection = ([SystemInfo.NativeMethods]::BootDmaCheck()) -ne 0 # Change current working directory to the LGPO's folder Set-Location "$WorkingDir\LGPO_30" @@ -1073,7 +1105,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va "@ } - switch (Select-Option -SubCategory -Options 'Normal: TPM + Startup PIN + Recovery Password', 'Enhanced: TPM + Startup PIN + Startup Key + Recovery Password', 'Skip encryptions altogether', 'Exit' -Message "`nPlease select your desired security level" -ExtraMessage "If you are not sure, refer to the BitLocker category in the GitHub Readme`n") { + :OSDriveEncryptionLabel switch (Select-Option -SubCategory -Options 'Normal: TPM + Startup PIN + Recovery Password', 'Enhanced: TPM + Startup PIN + Startup Key + Recovery Password', 'Skip encryptions altogether', 'Exit' -Message "`nPlease select your desired security level" -ExtraMessage "If you are not sure, refer to the BitLocker category in the GitHub Readme`n") { 'Normal: TPM + Startup PIN + Recovery Password' { # check if Bitlocker is enabled for the system drive with Normal security level @@ -1083,6 +1115,14 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [System.Object[]]$KeyProtectorsOSDrive = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector # Get the key protector types of the OS Drive [System.String[]]$KeyProtectorTypesOSDrive = $KeyProtectorsOSDrive.keyprotectortype + + if ($KeyProtectorTypesOSDrive -contains 'TpmPinStartupKey' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { + + switch (Select-Option -SubCategory -Options 'Yes', 'Skip OS Drive' , 'Exit' -Message "`nThe OS Drive is already encrypted with Enhanced Security level." -ExtraMessage "Are you sure you want to change it to Normal Security level?`n" ) { + 'Skip OS Drive' { break OSDriveEncryptionLabel } + 'Exit' { &$CleanUp } + } + } # check if TPM + PIN + recovery password are being used as key protectors for the OS Drive if ($KeyProtectorTypesOSDrive -contains 'Tpmpin' -and $KeyProtectorTypesOSDrive -contains 'recoveryPassword') { @@ -1123,7 +1163,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) # Compare the PINs and make sure they match - [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 + [System.Boolean]$TheyMatch = Compare-SecureString $Pin1 $Pin2 # If the PINs match and they are at least 10 characters long, max 20 characters if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { [securestring]$Pin = $Pin1 @@ -1163,7 +1203,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [securestring]$Pin1 = $(Write-SmartText -C PinkBold -G Magenta -I 'Enter a Pin for Bitlocker startup (between 10 to 20 characters)'; Read-Host -AsSecureString) [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) - [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 + [System.Boolean]$TheyMatch = Compare-SecureString $Pin1 $Pin2 if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { [securestring]$Pin = $Pin1 @@ -1239,7 +1279,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # If the OS Drive doesn't have (TpmPinStartupKey) key protector if ($KeyProtectorTypesOSDrive -notcontains 'TpmPinStartupKey') { - Write-SmartText -C Violet -G Cyan -I "`nTpm And Pin And StartupKey Protector is missing from the OS Drive, adding it now`n" + Write-SmartText -C Violet -G Cyan -I "`nTpm And Pin And StartupKey Protector is missing from the OS Drive, adding it now" # Check if the OS drive has ExternalKey key protector and if it does remove it # It's the standalone Startup Key protector which isn't secure on its own for the OS Drive @@ -1256,7 +1296,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) # Compare the PINs and make sure they match - [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 + [System.Boolean]$TheyMatch = Compare-SecureString $Pin1 $Pin2 # If the PINs match and they are at least 10 characters long, max 20 characters if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { [securestring]$Pin = $Pin1 @@ -1299,7 +1339,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [securestring]$Pin2 = $(Write-SmartText -C PinkBold -G Magenta -I 'Confirm your Bitlocker Startup Pin (between 10 to 20 characters)'; Read-Host -AsSecureString) # Compare the PINs and make sure they match - [bool]$TheyMatch = Compare-SecureString $Pin1 $Pin2 + [System.Boolean]$TheyMatch = Compare-SecureString $Pin1 $Pin2 # If the PINs match and they are at least 10 characters long, max 20 characters if ( $TheyMatch -and ($Pin1.Length -in 10..20) -and ($Pin2.Length -in 10..20) ) { [securestring]$Pin = $Pin1 @@ -1351,14 +1391,24 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va catch { # Do nothing if the key doesn't exist } - if ($HiberFileType -ne 2) { + if ($HiberFileType -ne 2) { + + Write-Progress -Id 6 -ParentId 0 -Activity 'Hibernate' -Status 'Setting Hibernate file size to full' -PercentComplete 50 + # doing this so Controlled Folder Access won't bitch about powercfg.exe Add-MpPreference -ControlledFolderAccessAllowedApplications 'C:\Windows\System32\powercfg.exe' + Start-Sleep 5 + # Set Hibernate mode to full - powercfg /h /type full + powercfg /h /type full | Out-Null + Start-Sleep 3 + Remove-MpPreference -ControlledFolderAccessAllowedApplications 'C:\Windows\System32\powercfg.exe' + + Write-Progress -Id 6 -Activity 'Setting Hibernate file size to full' -Completed + } else { Write-SmartText -C Pink -G Magenta -I "`nHibernate is already set to full.`n" @@ -1571,6 +1621,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region TLS-Security # ==============================================TLS Security=============================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'TLS' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun TLS Security category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'TLS Security' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -1610,6 +1664,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Lock-Screen # ==========================================Lock Screen==================================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Lock Screen' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Lock Screen category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Lock Screen' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -1640,6 +1698,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region User-Account-Control # ==========================================User Account Control=========================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'UAC' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun User Account Control category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'User Account Control' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -1693,6 +1755,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Windows-Firewall # ====================================================Windows Firewall===================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = '🔥 Firewall' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Windows Firewall category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Windows Firewall' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -1714,6 +1780,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Optional-Windows-Features # =================================================Optional Windows Features=============================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Optional Features' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Optional Windows Features category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Optional Windows Features' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -2008,6 +2078,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Windows-Networking # ====================================================Windows Networking=================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Networking' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Windows Networking category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Windows Networking' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -2031,6 +2105,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Miscellaneous-Configurations # ==============================================Miscellaneous Configurations=============================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Miscellaneous' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Miscellaneous Configurations category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Miscellaneous Configurations' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -2063,7 +2141,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va # For tracking Lock screen unlocks and locks # auditpol /set /subcategory:"Other Logon/Logoff Events" /success:enable /failure:enable # Using GUID - auditpol /set /subcategory:"{0CCE921C-69AE-11D9-BED3-505054503030}" /success:enable /failure:enable + auditpol /set /subcategory:"{0CCE921C-69AE-11D9-BED3-505054503030}" /success:enable /failure:enable | Out-Null # Query all Audits status # auditpol /get /category:* @@ -2091,6 +2169,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Windows-Update-Configurations # ====================================================Windows Update Configurations============================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Windows Update' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nApply Windows Update Policies ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Windows Update Configurations' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -2109,6 +2191,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Edge-Browser-Configurations # ====================================================Edge Browser Configurations==================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Edge' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nApply Edge Browser Configurations ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Edge Browser Configurations' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -2130,6 +2216,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Certificate-Checking-Commands # ====================================================Certificate Checking Commands======================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Certificates' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Certificate Checking category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Certificate Checking Commands' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -2158,6 +2248,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Country-IP-Blocking # ====================================================Country IP Blocking================================================== $CurrentMainStep++ + + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Country IPs' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Country IP Blocking category ?") { 'Yes' { Write-Progress -Id 0 -Activity 'Country IP Blocking' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) @@ -2212,6 +2306,9 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va #region Non-Admin-Commands # ====================================================Non-Admin Commands=================================================== + # Change the title of the Windows Terminal for PowerShell tab + $Host.UI.RawUI.WindowTitle = 'Non-Admins' + switch (Select-Option -Options 'Yes', 'No', 'Exit' -Message "`nRun Non-Admin category ?") { 'Yes' { $CurrentMainStep = $TotalMainSteps @@ -2252,7 +2349,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } finally { # Disable progress bars - 0..5 | ForEach-Object { Write-Progress -Id $_ -Activity 'Done' -Completed } + 0..6 | ForEach-Object { Write-Progress -Id $_ -Activity 'Done' -Completed } + + # Restore the title of the PowerShell back to what it was prior to running the script/module + $Host.UI.RawUI.WindowTitle = $CurrentPowerShellTitle if (Test-IsAdmin) { # Reverting the PowerShell executables allow listings in Controlled folder access From 9c3a2e0db2791565e71ca991701c072eccb616cc Mon Sep 17 00:00:00 2001 From: Violet Date: Fri, 17 Nov 2023 18:44:03 +0000 Subject: [PATCH 25/34] Execution policy adjustment Restoring execution policy for the current process after the script is finished --- Harden-Windows-Security.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index 1c9cd3607..9c070b660 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -86,6 +86,8 @@ #> +[string]$CurrentExecutionPolicy = Get-ExecutionPolicy -Scope Process + # Change the execution policy temporarily only for the current PowerShell session # Unrestricted is more secure than Bypass because if a script is code signed then tampered, you will see an error, but in bypass mode, no code sign tamper detection happens Set-ExecutionPolicy -ExecutionPolicy 'Unrestricted' -Scope Process -Force @@ -2354,6 +2356,9 @@ finally { # Restore the title of the PowerShell back to what it was prior to running the script/module $Host.UI.RawUI.WindowTitle = $CurrentPowerShellTitle + # Set the execution policy back to what it was prior to running the script + Set-ExecutionPolicy -ExecutionPolicy "$CurrentExecutionPolicy" -Scope Process -Force + if (Test-IsAdmin) { # Reverting the PowerShell executables allow listings in Controlled folder access Get-ChildItem -Path "$PSHOME\*.exe" | ForEach-Object { From bc1fad06f83636822775611113a64b01765ade5b Mon Sep 17 00:00:00 2001 From: Violet Date: Fri, 17 Nov 2023 18:46:59 +0000 Subject: [PATCH 26/34] Adding changes to the module script Adding changes from the main script to the module directory. The changes related to execution policy restoration at the end. --- .../Main files/Harden-Windows-Security.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index d8dcdc7c3..da343df9a 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -86,6 +86,8 @@ #> +[string]$CurrentExecutionPolicy = Get-ExecutionPolicy -Scope Process + # Change the execution policy temporarily only for the current PowerShell session # Unrestricted is more secure than Bypass because if a script is code signed then tampered, you will see an error, but in bypass mode, no code sign tamper detection happens Set-ExecutionPolicy -ExecutionPolicy 'Unrestricted' -Scope Process -Force @@ -2354,6 +2356,9 @@ finally { # Restore the title of the PowerShell back to what it was prior to running the script/module $Host.UI.RawUI.WindowTitle = $CurrentPowerShellTitle + # Set the execution policy back to what it was prior to running the script + Set-ExecutionPolicy -ExecutionPolicy "$CurrentExecutionPolicy" -Scope Process -Force + if (Test-IsAdmin) { # Reverting the PowerShell executables allow listings in Controlled folder access Get-ChildItem -Path "$PSHOME\*.exe" | ForEach-Object { From 7a5eb0ba8855039bc6a24eb109c5a227de619675 Mon Sep 17 00:00:00 2001 From: Violet Date: Fri, 17 Nov 2023 19:17:02 +0000 Subject: [PATCH 27/34] Minor improvement to the code --- .../Main files/Harden-Windows-Security.ps1 | 63 ++++++++++--------- Harden-Windows-Security.ps1 | 63 ++++++++++--------- 2 files changed, 64 insertions(+), 62 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index da343df9a..588a95656 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -86,6 +86,7 @@ #> +# Get the execution policy for the current process [string]$CurrentExecutionPolicy = Get-ExecutionPolicy -Scope Process # Change the execution policy temporarily only for the current PowerShell session @@ -137,14 +138,14 @@ function Select-Option { } } - for ($i = 0; $i -lt $Options.Length; $i++) { - Write-SmartText -C MintGreen -G White -I "$($i+1): $($Options[$i])" + for ($I = 0; $I -lt $Options.Length; $I++) { + Write-SmartText -C MintGreen -G White -I "$($I+1): $($Options[$I])" } # Make sure user only inputs a positive integer [System.Int64]$SelectedIndex = 0 - $isValid = [System.Int64]::TryParse((Read-Host 'Select an option'), [ref]$SelectedIndex) - if ($isValid) { + $IsValid = [System.Int64]::TryParse((Read-Host 'Select an option'), [ref]$SelectedIndex) + if ($IsValid) { if ($SelectedIndex -gt 0 -and $SelectedIndex -le $Options.Length) { $Selected = $Options[$SelectedIndex - 1] } @@ -161,12 +162,12 @@ function Select-Option { # Function to modify registry function Edit-Registry { - param ($Path, $Key, $Value, $Type, $Action) - If (-NOT (Test-Path $Path)) { + param ([System.String]$Path, [System.String]$Key, [System.String]$Value, [System.String]$Type, [System.String]$Action) + If (-NOT (Test-Path -Path $Path)) { New-Item -Path $Path -Force | Out-Null } if ($Action -eq 'AddOrModify') { - New-ItemProperty -Path $Path -Name $Key -Value $Value -PropertyType $Type -Force + New-ItemProperty -Path $Path -Name $Key -Value $Value -PropertyType $Type -Force | Out-Null } elseif ($Action -eq 'Delete') { Remove-ItemProperty -Path $Path -Name $Key -Force -ErrorAction SilentlyContinue | Out-Null @@ -226,10 +227,10 @@ function Compare-SecureString { if ( $Length1 -ne $Length2 ) { return $false } - for ( $i = 0; $i -lt $Length1; ++$i ) { - $b1 = [Runtime.InteropServices.Marshal]::ReadByte($Bstr1, $i) - $b2 = [Runtime.InteropServices.Marshal]::ReadByte($Bstr2, $i) - if ( $b1 -ne $b2 ) { + for ( $I = 0; $I -lt $Length1; ++$I ) { + $B1 = [Runtime.InteropServices.Marshal]::ReadByte($Bstr1, $I) + $B2 = [Runtime.InteropServices.Marshal]::ReadByte($Bstr2, $I) + if ( $B1 -ne $B2 ) { return $false } } @@ -288,7 +289,7 @@ Function Write-SmartText { 'LavenderNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(255,179,255))$InputText$($PSStyle.Reset)" -NoNewline; break } 'TeaGreenNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(133, 222, 119))$InputText$($PSStyle.Reset)" -NoNewline; break } 'Rainbow' { - $colors = @( + $Colors = @( [System.Drawing.Color]::Pink, [System.Drawing.Color]::HotPink, [System.Drawing.Color]::SkyBlue, @@ -301,12 +302,12 @@ Function Write-SmartText { [System.Drawing.Color]::Gold ) - $output = '' - for ($i = 0; $i -lt $InputText.Length; $i++) { - $color = $colors[$i % $colors.Length] - $output += "$($PSStyle.Foreground.FromRGB($color.R, $color.G, $color.B))$($PSStyle.Blink)$($InputText[$i])$($PSStyle.BlinkOff)$($PSStyle.Reset)" + $Output = '' + for ($I = 0; $I -lt $InputText.Length; $I++) { + $Color = $Colors[$I % $Colors.Length] + $Output += "$($PSStyle.Foreground.FromRGB($Color.R, $Color.G, $Color.B))$($PSStyle.Blink)$($InputText[$I])$($PSStyle.BlinkOff)$($PSStyle.Reset)" } - Write-Output $output + Write-Output $Output break } @@ -392,17 +393,17 @@ function Get-AvailableRemovableDrives { Write-SmartText -C Gold -G Cyan ("|{0,-$SizeLength}" -f 'Size') # Loop through the drives and display them in a table with colors - for ($i = 0; $i -lt $AvailableRemovableDrives.Count; $i++) { + for ($I = 0; $I -lt $AvailableRemovableDrives.Count; $I++) { # Write the index of the drive - Write-SmartText -C LavenderNoNewLine -N -G Blue -I ('{0,-4}' -f ($i + 1)) + Write-SmartText -C LavenderNoNewLine -N -G Blue -I ('{0,-4}' -f ($I + 1)) # Write the name of the drive - Write-SmartText -C TeaGreenNoNewLine -N -G Yellow -I ("|{0,-$DriveLetterLength}" -f $AvailableRemovableDrives[$i].DriveLetter) + Write-SmartText -C TeaGreenNoNewLine -N -G Yellow -I ("|{0,-$DriveLetterLength}" -f $AvailableRemovableDrives[$I].DriveLetter) # Write the File System Type of the drive - Write-SmartText -C PinkNoNewLine -N -G Magenta -I ("|{0,-$FileSystemTypeLength}" -f $AvailableRemovableDrives[$i].FileSystemType) + Write-SmartText -C PinkNoNewLine -N -G Magenta -I ("|{0,-$FileSystemTypeLength}" -f $AvailableRemovableDrives[$I].FileSystemType) # Write the Drive Type of the drive - Write-SmartText -C VioletNoNewLine -N -G Green -I ("|{0,-$DriveTypeLength}" -f $AvailableRemovableDrives[$i].DriveType) + Write-SmartText -C VioletNoNewLine -N -G Green -I ("|{0,-$DriveTypeLength}" -f $AvailableRemovableDrives[$I].DriveType) # Write the Size of the drive - Write-SmartText -C Gold -G Cyan ("|{0,-$SizeLength}" -f $AvailableRemovableDrives[$i].Size) + Write-SmartText -C Gold -G Cyan ("|{0,-$SizeLength}" -f $AvailableRemovableDrives[$I].Size) } # Get the max count of available network drives and add 1 to it, assign the number as exit value to break the loop when selected @@ -500,7 +501,7 @@ try { Write-SmartText -CustomColor Rainbow -GenericColor Cyan -InputText "############################################################################################################`r`n" # Show a prompt to the user if they're using the old PowerShell - if ($PSVersionTable.PSEdition -eq 'Desktop') { Write-Host "You're using old PowerShell. Use the new PowerShell Core for much better styling and performance:`nhttps://apps.microsoft.com/detail/powershell/9MZ1SNWT0N5D" -ForegroundColor Yellow } + if ($PSVersionTable.PSEdition -eq 'Desktop') { Write-Host "You're using old PowerShell. Please use the new PowerShell Core for much better styling and performance:`nhttps://apps.microsoft.com/detail/powershell/9MZ1SNWT0N5D" -ForegroundColor Yellow } #region RequirementsCheck # check if user's OS is Windows Home edition @@ -879,7 +880,7 @@ try { if ((Get-MpComputerStatus).SmartAppControlState -eq 'Eval') { switch (Select-Option -SubCategory -Options 'Yes', 'No', 'Exit' -Message "`nTurn on Smart App Control ?") { 'Yes' { - Edit-Registry -path 'HKLM:\SYSTEM\CurrentControlSet\Control\CI\Policy' -key 'VerifiedAndReputablePolicyState' -value '1' -type 'DWORD' -Action 'AddOrModify' | Out-Null + Edit-Registry -path 'HKLM:\SYSTEM\CurrentControlSet\Control\CI\Policy' -key 'VerifiedAndReputablePolicyState' -value '1' -type 'DWORD' -Action 'AddOrModify' # Let the optional diagnostic data be enabled automatically $ShouldEnableOptionalDiagnosticData = $True } 'No' { break } @@ -1651,7 +1652,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [System.Object[]]$Items = Import-Csv '.\Registry.csv' -Delimiter ',' foreach ($Item in $Items) { if ($Item.category -eq 'TLS') { - Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action | Out-Null + Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action } } # Change current working directory to the LGPO's folder @@ -2094,7 +2095,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va .\LGPO.exe /q /s '..\Security-Baselines-X\Windows Networking Policies\GptTmpl.inf' # Disable LMHOSTS lookup protocol on all network adapters - Edit-Registry -path 'HKLM:\SYSTEM\CurrentControlSet\Services\NetBT\Parameters' -key 'EnableLMHOSTS' -value '0' -type 'DWORD' -Action 'AddOrModify' | Out-Null + Edit-Registry -path 'HKLM:\SYSTEM\CurrentControlSet\Services\NetBT\Parameters' -key 'EnableLMHOSTS' -value '0' -type 'DWORD' -Action 'AddOrModify' # Set the Network Location of all connections to Public Get-NetConnectionProfile | Set-NetConnectionProfile -NetworkCategory Public @@ -2120,7 +2121,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [System.Object[]]$Items = Import-Csv '.\Registry.csv' -Delimiter ',' foreach ($Item in $Items) { if ($Item.category -eq 'Miscellaneous') { - Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action | Out-Null + Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action } } # Change current working directory to the LGPO's folder @@ -2180,7 +2181,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va Write-Progress -Id 0 -Activity 'Windows Update Configurations' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) # Enable restart notification for Windows update - Edit-Registry -path 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings' -key 'RestartNotificationsAllowed2' -value '1' -type 'DWORD' -Action 'AddOrModify' | Out-Null + Edit-Registry -path 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings' -key 'RestartNotificationsAllowed2' -value '1' -type 'DWORD' -Action 'AddOrModify' # Change current working directory to the LGPO's folder Set-Location "$WorkingDir\LGPO_30" .\LGPO.exe /q /m '..\Security-Baselines-X\Windows Update Policies\registry.pol' @@ -2206,7 +2207,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [System.Object[]]$Items = Import-Csv '.\Registry.csv' -Delimiter ',' foreach ($Item in $Items) { if ($Item.category -eq 'Edge') { - Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action | Out-Null + Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action } } } 'No' { break } @@ -2331,7 +2332,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [System.Object[]]$Items = Import-Csv '.\Registry.csv' -Delimiter ',' foreach ($Item in $Items) { if ($Item.category -eq 'NonAdmin') { - Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action | Out-Null + Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action } } diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index 9c070b660..779aa94a6 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -86,6 +86,7 @@ #> +# Get the execution policy for the current process [string]$CurrentExecutionPolicy = Get-ExecutionPolicy -Scope Process # Change the execution policy temporarily only for the current PowerShell session @@ -137,14 +138,14 @@ function Select-Option { } } - for ($i = 0; $i -lt $Options.Length; $i++) { - Write-SmartText -C MintGreen -G White -I "$($i+1): $($Options[$i])" + for ($I = 0; $I -lt $Options.Length; $I++) { + Write-SmartText -C MintGreen -G White -I "$($I+1): $($Options[$I])" } # Make sure user only inputs a positive integer [System.Int64]$SelectedIndex = 0 - $isValid = [System.Int64]::TryParse((Read-Host 'Select an option'), [ref]$SelectedIndex) - if ($isValid) { + $IsValid = [System.Int64]::TryParse((Read-Host 'Select an option'), [ref]$SelectedIndex) + if ($IsValid) { if ($SelectedIndex -gt 0 -and $SelectedIndex -le $Options.Length) { $Selected = $Options[$SelectedIndex - 1] } @@ -161,12 +162,12 @@ function Select-Option { # Function to modify registry function Edit-Registry { - param ($Path, $Key, $Value, $Type, $Action) - If (-NOT (Test-Path $Path)) { + param ([System.String]$Path, [System.String]$Key, [System.String]$Value, [System.String]$Type, [System.String]$Action) + If (-NOT (Test-Path -Path $Path)) { New-Item -Path $Path -Force | Out-Null } if ($Action -eq 'AddOrModify') { - New-ItemProperty -Path $Path -Name $Key -Value $Value -PropertyType $Type -Force + New-ItemProperty -Path $Path -Name $Key -Value $Value -PropertyType $Type -Force | Out-Null } elseif ($Action -eq 'Delete') { Remove-ItemProperty -Path $Path -Name $Key -Force -ErrorAction SilentlyContinue | Out-Null @@ -226,10 +227,10 @@ function Compare-SecureString { if ( $Length1 -ne $Length2 ) { return $false } - for ( $i = 0; $i -lt $Length1; ++$i ) { - $b1 = [Runtime.InteropServices.Marshal]::ReadByte($Bstr1, $i) - $b2 = [Runtime.InteropServices.Marshal]::ReadByte($Bstr2, $i) - if ( $b1 -ne $b2 ) { + for ( $I = 0; $I -lt $Length1; ++$I ) { + $B1 = [Runtime.InteropServices.Marshal]::ReadByte($Bstr1, $I) + $B2 = [Runtime.InteropServices.Marshal]::ReadByte($Bstr2, $I) + if ( $B1 -ne $B2 ) { return $false } } @@ -288,7 +289,7 @@ Function Write-SmartText { 'LavenderNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(255,179,255))$InputText$($PSStyle.Reset)" -NoNewline; break } 'TeaGreenNoNewLine' { Write-Host "$($PSStyle.Foreground.FromRgb(133, 222, 119))$InputText$($PSStyle.Reset)" -NoNewline; break } 'Rainbow' { - $colors = @( + $Colors = @( [System.Drawing.Color]::Pink, [System.Drawing.Color]::HotPink, [System.Drawing.Color]::SkyBlue, @@ -301,12 +302,12 @@ Function Write-SmartText { [System.Drawing.Color]::Gold ) - $output = '' - for ($i = 0; $i -lt $InputText.Length; $i++) { - $color = $colors[$i % $colors.Length] - $output += "$($PSStyle.Foreground.FromRGB($color.R, $color.G, $color.B))$($PSStyle.Blink)$($InputText[$i])$($PSStyle.BlinkOff)$($PSStyle.Reset)" + $Output = '' + for ($I = 0; $I -lt $InputText.Length; $I++) { + $Color = $Colors[$I % $Colors.Length] + $Output += "$($PSStyle.Foreground.FromRGB($Color.R, $Color.G, $Color.B))$($PSStyle.Blink)$($InputText[$I])$($PSStyle.BlinkOff)$($PSStyle.Reset)" } - Write-Output $output + Write-Output $Output break } @@ -392,17 +393,17 @@ function Get-AvailableRemovableDrives { Write-SmartText -C Gold -G Cyan ("|{0,-$SizeLength}" -f 'Size') # Loop through the drives and display them in a table with colors - for ($i = 0; $i -lt $AvailableRemovableDrives.Count; $i++) { + for ($I = 0; $I -lt $AvailableRemovableDrives.Count; $I++) { # Write the index of the drive - Write-SmartText -C LavenderNoNewLine -N -G Blue -I ('{0,-4}' -f ($i + 1)) + Write-SmartText -C LavenderNoNewLine -N -G Blue -I ('{0,-4}' -f ($I + 1)) # Write the name of the drive - Write-SmartText -C TeaGreenNoNewLine -N -G Yellow -I ("|{0,-$DriveLetterLength}" -f $AvailableRemovableDrives[$i].DriveLetter) + Write-SmartText -C TeaGreenNoNewLine -N -G Yellow -I ("|{0,-$DriveLetterLength}" -f $AvailableRemovableDrives[$I].DriveLetter) # Write the File System Type of the drive - Write-SmartText -C PinkNoNewLine -N -G Magenta -I ("|{0,-$FileSystemTypeLength}" -f $AvailableRemovableDrives[$i].FileSystemType) + Write-SmartText -C PinkNoNewLine -N -G Magenta -I ("|{0,-$FileSystemTypeLength}" -f $AvailableRemovableDrives[$I].FileSystemType) # Write the Drive Type of the drive - Write-SmartText -C VioletNoNewLine -N -G Green -I ("|{0,-$DriveTypeLength}" -f $AvailableRemovableDrives[$i].DriveType) + Write-SmartText -C VioletNoNewLine -N -G Green -I ("|{0,-$DriveTypeLength}" -f $AvailableRemovableDrives[$I].DriveType) # Write the Size of the drive - Write-SmartText -C Gold -G Cyan ("|{0,-$SizeLength}" -f $AvailableRemovableDrives[$i].Size) + Write-SmartText -C Gold -G Cyan ("|{0,-$SizeLength}" -f $AvailableRemovableDrives[$I].Size) } # Get the max count of available network drives and add 1 to it, assign the number as exit value to break the loop when selected @@ -500,7 +501,7 @@ try { Write-SmartText -CustomColor Rainbow -GenericColor Cyan -InputText "############################################################################################################`r`n" # Show a prompt to the user if they're using the old PowerShell - if ($PSVersionTable.PSEdition -eq 'Desktop') { Write-Host "You're using old PowerShell. Use the new PowerShell Core for much better styling and performance:`nhttps://apps.microsoft.com/detail/powershell/9MZ1SNWT0N5D" -ForegroundColor Yellow } + if ($PSVersionTable.PSEdition -eq 'Desktop') { Write-Host "You're using old PowerShell. Please use the new PowerShell Core for much better styling and performance:`nhttps://apps.microsoft.com/detail/powershell/9MZ1SNWT0N5D" -ForegroundColor Yellow } #region RequirementsCheck # check if user's OS is Windows Home edition @@ -879,7 +880,7 @@ try { if ((Get-MpComputerStatus).SmartAppControlState -eq 'Eval') { switch (Select-Option -SubCategory -Options 'Yes', 'No', 'Exit' -Message "`nTurn on Smart App Control ?") { 'Yes' { - Edit-Registry -path 'HKLM:\SYSTEM\CurrentControlSet\Control\CI\Policy' -key 'VerifiedAndReputablePolicyState' -value '1' -type 'DWORD' -Action 'AddOrModify' | Out-Null + Edit-Registry -path 'HKLM:\SYSTEM\CurrentControlSet\Control\CI\Policy' -key 'VerifiedAndReputablePolicyState' -value '1' -type 'DWORD' -Action 'AddOrModify' # Let the optional diagnostic data be enabled automatically $ShouldEnableOptionalDiagnosticData = $True } 'No' { break } @@ -1651,7 +1652,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [System.Object[]]$Items = Import-Csv '.\Registry.csv' -Delimiter ',' foreach ($Item in $Items) { if ($Item.category -eq 'TLS') { - Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action | Out-Null + Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action } } # Change current working directory to the LGPO's folder @@ -2094,7 +2095,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va .\LGPO.exe /q /s '..\Security-Baselines-X\Windows Networking Policies\GptTmpl.inf' # Disable LMHOSTS lookup protocol on all network adapters - Edit-Registry -path 'HKLM:\SYSTEM\CurrentControlSet\Services\NetBT\Parameters' -key 'EnableLMHOSTS' -value '0' -type 'DWORD' -Action 'AddOrModify' | Out-Null + Edit-Registry -path 'HKLM:\SYSTEM\CurrentControlSet\Services\NetBT\Parameters' -key 'EnableLMHOSTS' -value '0' -type 'DWORD' -Action 'AddOrModify' # Set the Network Location of all connections to Public Get-NetConnectionProfile | Set-NetConnectionProfile -NetworkCategory Public @@ -2120,7 +2121,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [System.Object[]]$Items = Import-Csv '.\Registry.csv' -Delimiter ',' foreach ($Item in $Items) { if ($Item.category -eq 'Miscellaneous') { - Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action | Out-Null + Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action } } # Change current working directory to the LGPO's folder @@ -2180,7 +2181,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va Write-Progress -Id 0 -Activity 'Windows Update Configurations' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) # Enable restart notification for Windows update - Edit-Registry -path 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings' -key 'RestartNotificationsAllowed2' -value '1' -type 'DWORD' -Action 'AddOrModify' | Out-Null + Edit-Registry -path 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings' -key 'RestartNotificationsAllowed2' -value '1' -type 'DWORD' -Action 'AddOrModify' # Change current working directory to the LGPO's folder Set-Location "$WorkingDir\LGPO_30" .\LGPO.exe /q /m '..\Security-Baselines-X\Windows Update Policies\registry.pol' @@ -2206,7 +2207,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [System.Object[]]$Items = Import-Csv '.\Registry.csv' -Delimiter ',' foreach ($Item in $Items) { if ($Item.category -eq 'Edge') { - Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action | Out-Null + Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action } } } 'No' { break } @@ -2331,7 +2332,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va [System.Object[]]$Items = Import-Csv '.\Registry.csv' -Delimiter ',' foreach ($Item in $Items) { if ($Item.category -eq 'NonAdmin') { - Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action | Out-Null + Edit-Registry -path $Item.Path -key $Item.Key -value $Item.Value -type $Item.Type -Action $Item.Action } } From 14585999ac5a133d49cc21657523cb9471d24085 Mon Sep 17 00:00:00 2001 From: Violet Date: Fri, 17 Nov 2023 20:20:15 +0000 Subject: [PATCH 28/34] Improved Harden Windows Security module The required PowerShell module is now the latest version which is 7.4.0. It has many new features, one of which is the having -ProgressAction common parameter. Using this new common parameter and setting it to SilentlyContinue for Invoke-WebRequest and Invoke-restMethod cmdlets allows for the removal of Invoke-WithoutProgress function since it renders it unnecessary. --- .../Main files/Confirm-SystemCompliance.psm1 | 301 +++++++++--------- .../Main files/Functions.ps1 | 45 +-- .../Harden-Windows-Security-Module.psd1 | 2 +- .../Main files/Harden-Windows-Security.ps1 | 5 +- .../Main files/Unprotect-WindowsSecurity.psm1 | 46 ++- Harden-Windows-Security.ps1 | 5 +- 6 files changed, 188 insertions(+), 216 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 index f32fb37d7..70ac0d21c 100644 --- a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 +++ b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 @@ -4,13 +4,14 @@ $PSStyle.Progress.Style = "$($PSStyle.Foreground.FromRGB(255,255,49))$($PSStyle. # To parse the ini file from the output of the "Secedit /export /cfg .\security_policy.inf" function ConvertFrom-IniFile { [CmdletBinding()] - Param ([string]$IniFile) + Param ([System.String]$IniFile) # Don't prompt to continue if '-Debug' is specified. $DebugPreference = 'Continue' - [hashtable]$IniObject = @{} - [string]$SectionName = '' + [System.Collections.Hashtable]$IniObject = @{} + [System.String]$SectionName = '' + switch -regex -file $IniFile { '^\[(.+)\]$' { # Header of the section @@ -21,7 +22,7 @@ function ConvertFrom-IniFile { } '^(.+?)\s*=\s*(.*)$' { # Name/value pair - [string]$KeyName, [string]$KeyValue = $matches[1..2] + [System.String]$KeyName, [System.String]$KeyValue = $matches[1..2] #Write-Debug "Name: $KeyName" # Write-Debug "Value: $KeyValue" $IniObject[$SectionName][$KeyName] = $KeyValue @@ -40,11 +41,11 @@ function Confirm-SystemCompliance { [CmdletBinding()] param ( [parameter(Mandatory = $false)] - [switch]$ExportToCSV, + [System.Management.Automation.SwitchParameter]$ExportToCSV, [parameter(Mandatory = $false)] - [switch]$ShowAsObjectsOnly, + [System.Management.Automation.SwitchParameter]$ShowAsObjectsOnly, [parameter(Mandatory = $false)] - [switch]$DetailedDisplay, + [System.Management.Automation.SwitchParameter]$DetailedDisplay, [Parameter(Mandatory = $false, DontShow = $True)] # To hide PowerShell common parameters that clutter parameter auto completion menu $DummyParam ) @@ -55,7 +56,7 @@ function Confirm-SystemCompliance { Write-Progress -Activity 'Starting' -Status 'Processing...' -PercentComplete 5 # Makes sure this cmdlet is invoked with Admin privileges - if (![bool]([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + if (![System.Boolean]([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Throw [System.Security.AccessControl.PrivilegeNotHeldException] 'Administrator' } @@ -66,7 +67,7 @@ function Confirm-SystemCompliance { Write-Progress -Activity 'Gathering Security Policy Information' -Status 'Processing...' -PercentComplete 15 # Total number of Compliant values not equal to N/A - [int]$global:TotalNumberOfTrueCompliantValues = 230 + [System.Int64]$global:TotalNumberOfTrueCompliantValues = 231 # Get the security group policies Secedit /export /cfg .\security_policy.inf | Out-Null @@ -106,35 +107,35 @@ function Confirm-SystemCompliance { # Function for processing each item in $AllRegistryItems for each category function Invoke-CategoryProcessing { param( - [string]$CatName, [string]$Method + [System.String]$CatName, [System.String]$Method ) # an array to hold the output - [System.Object[]]$output = @() + [System.Object[]]$Output = @() - foreach ($item in $AllRegistryItems | Where-Object { $_.category -eq $CatName } | Where-Object { $_.Method -eq $Method }) { + foreach ($Item in $AllRegistryItems | Where-Object { $_.category -eq $CatName } | Where-Object { $_.Method -eq $Method }) { # Initialize a flag to indicate if the key exists - [bool]$keyExists = $false + [System.Boolean]$keyExists = $false # Initialize a flag to indicate if the value exists and matches the type - [bool]$valueMatches = $false + [System.Boolean]$ValueMatches = $false # Try to get the registry key try { - $regKey = Get-Item -Path $item.regPath + $regKey = Get-Item -Path $Item.regPath # If no error is thrown, the key exists $keyExists = $true # Try to get the registry value and type try { - $regValue = Get-ItemPropertyValue -Path $item.regPath -Name $item.name + $RegValue = Get-ItemPropertyValue -Path $Item.regPath -Name $Item.name # If no error is thrown, the value exists # Check if the value matches the expected one - if ($regValue -eq $item.value) { + if ($RegValue -eq $Item.value) { # If it matches, set the flag to true - $valueMatches = $true + $ValueMatches = $true } } catch { @@ -148,24 +149,24 @@ function Confirm-SystemCompliance { } # Create a custom object with the results for this row - $output += [PSCustomObject]@{ - # Category = $item.category - # Key = $item.key - # Name = $item.name + $Output += [PSCustomObject]@{ + # Category = $Item.category + # Key = $Item.key + # Name = $Item.name # KeyExists = $keyExists - # ValueMatches = $valueMatches - # Type = $item.type - # Value = $item.value + # ValueMatches = $ValueMatches + # Type = $Item.type + # Value = $Item.value - FriendlyName = $item.FriendlyName - Compliant = $valueMatches - Value = $item.value - Name = $item.name + FriendlyName = $Item.FriendlyName + Compliant = $ValueMatches + Value = $Item.value + Name = $Item.name Category = $CatName Method = $Method } } - return $output + return $Output } } @@ -176,7 +177,7 @@ function Confirm-SystemCompliance { # An array to store the nested custom objects, inside the main output object [System.Object[]]$NestedObjectArray = @() - [String]$CatName = 'Microsoft Defender' + [System.String]$CatName = 'Microsoft Defender' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects $NestedObjectArray += [PSCustomObject](Invoke-CategoryProcessing -catname $CatName -Method 'Group Policy') @@ -246,7 +247,7 @@ function Confirm-SystemCompliance { if ($propAndVal[0] -ne '') { # [start of] new property; initialize list of values $currProp = $propAndVal[0] - $Entries[-1].Properties[$currProp] = New-Object Collections.Generic.List[string] + $Entries[-1].Properties[$currProp] = New-Object Collections.Generic.List[System.String] } $Entries[-1].Properties[$currProp].Add($propAndVal[1]) # add the value }) @@ -296,7 +297,7 @@ function Confirm-SystemCompliance { } - [hashtable]$DefenderPlatformUpdatesChannels = @{ + [System.Collections.Hashtable]$DefenderPlatformUpdatesChannels = @{ 0 = 'NotConfigured' 2 = 'Beta' 3 = 'Preview' @@ -308,14 +309,14 @@ function Confirm-SystemCompliance { $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Microsoft Defender Platform Updates Channel' Compliant = 'N/A' - Value = $($DefenderPlatformUpdatesChannels[[int]($MDAVPreferencesCurrent).PlatformUpdatesChannel]) + Value = $($DefenderPlatformUpdatesChannels[[System.Int64]($MDAVPreferencesCurrent).PlatformUpdatesChannel]) Name = 'Microsoft Defender Platform Updates Channel' Category = $CatName Method = 'Cmdlet' } - [hashtable]$DefenderEngineUpdatesChannels = @{ + [System.Collections.Hashtable]$DefenderEngineUpdatesChannels = @{ 0 = 'NotConfigured' 2 = 'Beta' 3 = 'Preview' @@ -327,7 +328,7 @@ function Confirm-SystemCompliance { $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Microsoft Defender Engine Updates Channel' Compliant = 'N/A' - Value = $($DefenderEngineUpdatesChannels[[int]($MDAVPreferencesCurrent).EngineUpdatesChannel]) + Value = $($DefenderEngineUpdatesChannels[[System.Int64]($MDAVPreferencesCurrent).EngineUpdatesChannel]) Name = 'Microsoft Defender Engine Updates Channel' Category = $CatName Method = 'Cmdlet' @@ -361,7 +362,7 @@ function Confirm-SystemCompliance { $IndividualItemResult = $MDAVPreferencesCurrent.PerformanceModeStatus $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'PerformanceModeStatus' - Compliant = [bool]($IndividualItemResult -eq '0') + Compliant = [System.Boolean]($IndividualItemResult -eq '0') Value = $IndividualItemResult Name = 'PerformanceModeStatus' Category = $CatName @@ -385,21 +386,21 @@ function Confirm-SystemCompliance { #Region Attack-Surface-Reduction-Rules-Category Write-Progress -Activity 'Validating Attack Surface Reduction Rules Category' -Status 'Processing...' -PercentComplete 40 [System.Object[]]$NestedObjectArray = @() - [String]$CatName = 'ASR' + [System.String]$CatName = 'ASR' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects $NestedObjectArray += [PSCustomObject](Invoke-CategoryProcessing -catname $CatName -Method 'Group Policy') # Individual ASR rules verification - [string[]]$Ids = $MDAVPreferencesCurrent.AttackSurfaceReductionRules_Ids - [string[]]$Actions = $MDAVPreferencesCurrent.AttackSurfaceReductionRules_Actions + [System.String[]]$Ids = $MDAVPreferencesCurrent.AttackSurfaceReductionRules_Ids + [System.String[]]$Actions = $MDAVPreferencesCurrent.AttackSurfaceReductionRules_Actions # If $Ids variable is not empty, convert them to lower case because some IDs can be in upper case and result in inaccurate comparison if ($Ids) { $Ids = $Ids.tolower() } # Hashtable to store the descriptions for each ID - [hashtable]$ASRsTable = @{ + [System.Collections.Hashtable]$ASRsTable = @{ '26190899-1602-49e8-8b27-eb1d0a1ce869' = 'Block Office communication application from creating child processes' 'd1e49aac-8f56-4280-b9ba-993a6d77406c' = 'Block process creations originating from PSExec and WMI commands' 'b2b3f03d-6a65-4f7b-a9c7-1c7ef74a9ba4' = 'Block untrusted and unsigned processes that run from USB' @@ -441,7 +442,7 @@ function Confirm-SystemCompliance { # Create a custom object with properties $NestedObjectArray += [PSCustomObject]@{ FriendlyName = $ASRsTable[$name] - Compliant = [bool]($Action -eq 1) # Compare action value with 1 and cast to boolean + Compliant = [System.Boolean]($Action -eq 1) # Compare action value with 1 and cast to boolean Value = $Action Name = $Name Category = $CatName @@ -456,13 +457,13 @@ function Confirm-SystemCompliance { #Region Bitlocker-Category Write-Progress -Activity 'Validating Bitlocker Category' -Status 'Processing...' -PercentComplete 45 [System.Object[]]$NestedObjectArray = @() - [String]$CatName = 'Bitlocker' + [System.String]$CatName = 'Bitlocker' # This PowerShell script can be used to find out if the DMA Protection is ON \ OFF. # The Script will show this by emitting True \ False for On \ Off respectively. # bootDMAProtection check - checks for Kernel DMA Protection status in System information or msinfo32 - [string]$BootDMAProtectionCheck = + [System.String]$BootDMAProtectionCheck = @' namespace SystemInfo { @@ -508,11 +509,11 @@ function Confirm-SystemCompliance { '@ Add-Type -TypeDefinition $BootDMAProtectionCheck # Returns true or false depending on whether Kernel DMA Protection is on or off - [bool]$BootDMAProtection = ([SystemInfo.NativeMethods]::BootDmaCheck()) -ne 0 + [System.Boolean]$BootDMAProtection = ([SystemInfo.NativeMethods]::BootDmaCheck()) -ne 0 # Get the status of Bitlocker DMA protection try { - [int]$BitlockerDMAProtectionStatus = Get-ItemPropertyValue -Path 'Registry::HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE' -Name 'DisableExternalDMAUnderLock' -ErrorAction SilentlyContinue + [System.Int64]$BitlockerDMAProtectionStatus = Get-ItemPropertyValue -Path 'Registry::HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE' -Name 'DisableExternalDMAUnderLock' -ErrorAction SilentlyContinue } catch { # -ErrorAction SilentlyContinue wouldn't suppress the error if the path exists but property doesn't, so using try-catch @@ -520,7 +521,7 @@ function Confirm-SystemCompliance { # Bitlocker DMA counter measure status # Returns true if only either Kernel DMA protection is on and Bitlocker DMA protection if off # or Kernel DMA protection is off and Bitlocker DMA protection is on - [bool]$ItemState = ($BootDMAProtection -xor ($BitlockerDMAProtectionStatus -eq '1')) ? $True : $False + [System.Boolean]$ItemState = ($BootDMAProtection -xor ($BitlockerDMAProtectionStatus -eq '1')) ? $True : $False # Create a custom object with 5 properties to store them as nested objects inside the main output object $NestedObjectArray += [PSCustomObject]@{ @@ -546,8 +547,8 @@ function Confirm-SystemCompliance { } $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Hibernate is set to full' - Compliant = [bool]($IndividualItemResult) - Value = [bool]($IndividualItemResult) + Compliant = [System.Boolean]($IndividualItemResult) + Value = [System.Boolean]($IndividualItemResult) Name = 'Hibernate is set to full' Category = $CatName Method = 'Cmdlet' @@ -685,7 +686,7 @@ function Confirm-SystemCompliance { #Region TLS-Category Write-Progress -Activity 'Validating TLS Category' -Status 'Processing...' -PercentComplete 50 [System.Object[]]$NestedObjectArray = @() - [String]$CatName = 'TLS' + [System.String]$CatName = 'TLS' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects $NestedObjectArray += [PSCustomObject](Invoke-CategoryProcessing -catname $CatName -Method 'Group Policy') @@ -699,7 +700,7 @@ function Confirm-SystemCompliance { $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'ECC Curves and their positions' - Compliant = [bool]($IndividualItemResult ? $false : $True) + Compliant = [System.Boolean]($IndividualItemResult ? $false : $True) Value = $list Name = 'ECC Curves and their positions' Category = $CatName @@ -716,13 +717,13 @@ function Confirm-SystemCompliance { #Region LockScreen-Category Write-Progress -Activity 'Validating Lock Screen Category' -Status 'Processing...' -PercentComplete 55 [System.Object[]]$NestedObjectArray = @() - [String]$CatName = 'LockScreen' + [System.String]$CatName = 'LockScreen' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects $NestedObjectArray += [PSCustomObject](Invoke-CategoryProcessing -catname $CatName -Method 'Group Policy') # Verify a Security Group Policy setting - $IndividualItemResult = [bool]$($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\InactivityTimeoutSecs'] -eq '4,120') ? $True : $False + $IndividualItemResult = [System.Boolean]$($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\InactivityTimeoutSecs'] -eq '4,120') ? $True : $False $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Machine inactivity limit' Compliant = $IndividualItemResult @@ -733,7 +734,7 @@ function Confirm-SystemCompliance { } # Verify a Security Group Policy setting - $IndividualItemResult = [bool]$($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DisableCAD'] -eq '4,0') ? $True : $False + $IndividualItemResult = [System.Boolean]$($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DisableCAD'] -eq '4,0') ? $True : $False $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Interactive logon: Do not require CTRL+ALT+DEL' Compliant = $IndividualItemResult @@ -744,7 +745,7 @@ function Confirm-SystemCompliance { } # Verify a Security Group Policy setting - $IndividualItemResult = [bool]$($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\MaxDevicePasswordFailedAttempts'] -eq '4,5') ? $True : $False + $IndividualItemResult = [System.Boolean]$($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\MaxDevicePasswordFailedAttempts'] -eq '4,5') ? $True : $False $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Interactive logon: Machine account lockout threshold' Compliant = $IndividualItemResult @@ -755,7 +756,7 @@ function Confirm-SystemCompliance { } # Verify a Security Group Policy setting - $IndividualItemResult = [bool]$($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DontDisplayLockedUserId'] -eq '4,4') ? $True : $False + $IndividualItemResult = [System.Boolean]$($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DontDisplayLockedUserId'] -eq '4,4') ? $True : $False $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Interactive logon: Display user information when the session is locked' Compliant = $IndividualItemResult @@ -766,7 +767,7 @@ function Confirm-SystemCompliance { } # Verify a Security Group Policy setting - $IndividualItemResult = [bool]$($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DontDisplayUserName'] -eq '4,1') ? $True : $False + $IndividualItemResult = [System.Boolean]$($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DontDisplayUserName'] -eq '4,1') ? $True : $False $NestedObjectArray += [PSCustomObject]@{ FriendlyName = "Interactive logon: Don't display username at sign-in" Compliant = $IndividualItemResult @@ -777,7 +778,7 @@ function Confirm-SystemCompliance { } # Verify a Security Group Policy setting - $IndividualItemResult = [bool]$($SecurityPoliciesIni.'System Access'['LockoutBadCount'] -eq '5') ? $True : $False + $IndividualItemResult = [System.Boolean]$($SecurityPoliciesIni.'System Access'['LockoutBadCount'] -eq '5') ? $True : $False $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Account lockout threshold' Compliant = $IndividualItemResult @@ -788,7 +789,7 @@ function Confirm-SystemCompliance { } # Verify a Security Group Policy setting - $IndividualItemResult = [bool]$($SecurityPoliciesIni.'System Access'['LockoutDuration'] -eq '1440') ? $True : $False + $IndividualItemResult = [System.Boolean]$($SecurityPoliciesIni.'System Access'['LockoutDuration'] -eq '1440') ? $True : $False $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Account lockout duration' Compliant = $IndividualItemResult @@ -799,7 +800,7 @@ function Confirm-SystemCompliance { } # Verify a Security Group Policy setting - $IndividualItemResult = [bool]$($SecurityPoliciesIni.'System Access'['ResetLockoutCount'] -eq '1440') ? $True : $False + $IndividualItemResult = [System.Boolean]$($SecurityPoliciesIni.'System Access'['ResetLockoutCount'] -eq '1440') ? $True : $False $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Reset account lockout counter after' Compliant = $IndividualItemResult @@ -810,7 +811,7 @@ function Confirm-SystemCompliance { } # Verify a Security Group Policy setting - $IndividualItemResult = [bool]$($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DontDisplayLastUserName'] -eq '4,1') ? $True : $False + $IndividualItemResult = [System.Boolean]$($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DontDisplayLastUserName'] -eq '4,1') ? $True : $False $NestedObjectArray += [PSCustomObject]@{ FriendlyName = "Interactive logon: Don't display last signed-in" Compliant = $IndividualItemResult @@ -827,13 +828,13 @@ function Confirm-SystemCompliance { #Region User-Account-Control-Category Write-Progress -Activity 'Validating User Account Control Category' -Status 'Processing...' -PercentComplete 60 [System.Object[]]$NestedObjectArray = @() - [String]$CatName = 'UAC' + [System.String]$CatName = 'UAC' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects $NestedObjectArray += [PSCustomObject](Invoke-CategoryProcessing -catname $CatName -Method 'Group Policy') # Verify a Security Group Policy setting - $IndividualItemResult = [bool]$($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ConsentPromptBehaviorAdmin'] -eq '4,2') ? $True : $False + $IndividualItemResult = [System.Boolean]$($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ConsentPromptBehaviorAdmin'] -eq '4,2') ? $True : $False $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'UAC: Behavior of the elevation prompt for administrators in Admin Approval Mode' Compliant = $IndividualItemResult @@ -845,7 +846,7 @@ function Confirm-SystemCompliance { # This particular policy can have 2 values and they are both acceptable depending on whichever user selects - [string]$ConsentPromptBehaviorUserValue = $SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ConsentPromptBehaviorUser'] + [System.String]$ConsentPromptBehaviorUserValue = $SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ConsentPromptBehaviorUser'] # This option is automatically applied when UAC category is run if ($ConsentPromptBehaviorUserValue -eq '4,1') { $ConsentPromptBehaviorUserCompliance = $true @@ -873,7 +874,7 @@ function Confirm-SystemCompliance { } # Verify a Security Group Policy setting - $IndividualItemResult = [bool]($($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ValidateAdminCodeSignatures'] -eq '4,1') ? $True : $False) + $IndividualItemResult = [System.Boolean]($($SecurityPoliciesIni.'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ValidateAdminCodeSignatures'] -eq '4,1') ? $True : $False) $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'UAC: Only elevate executables that are signed and validated' Compliant = $IndividualItemResult @@ -890,7 +891,7 @@ function Confirm-SystemCompliance { #Region Device-Guard-Category Write-Progress -Activity 'Validating Device Guard Category' -Status 'Processing...' -PercentComplete 65 [System.Object[]]$NestedObjectArray = @() - [String]$CatName = 'Device Guard' + [System.String]$CatName = 'Device Guard' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects $NestedObjectArray += [PSCustomObject](Invoke-CategoryProcessing -catname $CatName -Method 'Group Policy') @@ -902,7 +903,7 @@ function Confirm-SystemCompliance { #Region Windows-Firewall-Category Write-Progress -Activity 'Validating Windows Firewall Category' -Status 'Processing...' -PercentComplete 70 [System.Object[]]$NestedObjectArray = @() - [String]$CatName = 'Windows Firewall' + [System.String]$CatName = 'Windows Firewall' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects $NestedObjectArray += [PSCustomObject](Invoke-CategoryProcessing -catname $CatName -Method 'Group Policy') @@ -914,26 +915,26 @@ function Confirm-SystemCompliance { #Region Optional-Windows-Features-Category Write-Progress -Activity 'Validating Optional Windows Features Category' -Status 'Processing...' -PercentComplete 75 [System.Object[]]$NestedObjectArray = @() - [String]$CatName = 'Optional Windows Features' + [System.String]$CatName = 'Optional Windows Features' # Windows PowerShell handling Windows optional features verifications [System.Object[]]$Results = @() $Results = powershell.exe { - [bool]$PowerShell1 = (Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2).State -eq 'Disabled' - [bool]$PowerShell2 = (Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root).State -eq 'Disabled' - [string]$WorkFoldersClient = (Get-WindowsOptionalFeature -Online -FeatureName WorkFolders-Client).state - [string]$InternetPrintingClient = (Get-WindowsOptionalFeature -Online -FeatureName Printing-Foundation-Features).state - [string]$WindowsMediaPlayer = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*Media.WindowsMediaPlayer*' }).state - [string]$MDAG = (Get-WindowsOptionalFeature -Online -FeatureName Windows-Defender-ApplicationGuard).state - [string]$WindowsSandbox = (Get-WindowsOptionalFeature -Online -FeatureName Containers-DisposableClientVM).state - [string]$HyperV = (Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V).state - [string]$VMPlatform = (Get-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform).state - [string]$WMIC = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*wmic*' }).state - [string]$IEMode = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*Browser.InternetExplorer*' }).state - [string]$LegacyNotepad = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*Microsoft.Windows.Notepad.System*' }).state - [string]$LegacyWordPad = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*Microsoft.Windows.WordPad*' }).state - [string]$PowerShellISE = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*Microsoft.Windows.PowerShell.ISE*' }).state - [string]$StepsRecorder = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*App.StepsRecorder*' }).state + [System.Boolean]$PowerShell1 = (Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2).State -eq 'Disabled' + [System.Boolean]$PowerShell2 = (Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root).State -eq 'Disabled' + [System.String]$WorkFoldersClient = (Get-WindowsOptionalFeature -Online -FeatureName WorkFolders-Client).state + [System.String]$InternetPrintingClient = (Get-WindowsOptionalFeature -Online -FeatureName Printing-Foundation-Features).state + [System.String]$WindowsMediaPlayer = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*Media.WindowsMediaPlayer*' }).state + [System.String]$MDAG = (Get-WindowsOptionalFeature -Online -FeatureName Windows-Defender-ApplicationGuard).state + [System.String]$WindowsSandbox = (Get-WindowsOptionalFeature -Online -FeatureName Containers-DisposableClientVM).state + [System.String]$HyperV = (Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V).state + [System.String]$VMPlatform = (Get-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform).state + [System.String]$WMIC = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*wmic*' }).state + [System.String]$IEMode = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*Browser.InternetExplorer*' }).state + [System.String]$LegacyNotepad = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*Microsoft.Windows.Notepad.System*' }).state + [System.String]$LegacyWordPad = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*Microsoft.Windows.WordPad*' }).state + [System.String]$PowerShellISE = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*Microsoft.Windows.PowerShell.ISE*' }).state + [System.String]$StepsRecorder = (Get-WindowsCapability -Online | Where-Object { $_.Name -like '*App.StepsRecorder*' }).state # returning the output of the script block as an array Return $PowerShell1, $PowerShell2, $WorkFoldersClient, $InternetPrintingClient, $WindowsMediaPlayer, $MDAG, $WindowsSandbox, $HyperV, $VMPlatform, $WMIC, $IEMode, $LegacyNotepad, $LegacyWordPad, $PowerShellISE, $StepsRecorder } @@ -950,8 +951,8 @@ function Confirm-SystemCompliance { # Verify Work folders is disabled $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Work Folders client is disabled' - Compliant = [bool]($Results[2] -eq 'Disabled') - Value = [string]$Results[2] + Compliant = [System.Boolean]($Results[2] -eq 'Disabled') + Value = [System.String]$Results[2] Name = 'Work Folders client is disabled' Category = $CatName Method = 'Optional Windows Features' @@ -960,8 +961,8 @@ function Confirm-SystemCompliance { # Verify Internet Printing Client is disabled $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Internet Printing Client is disabled' - Compliant = [bool]($Results[3] -eq 'Disabled') - Value = [string]$Results[3] + Compliant = [System.Boolean]($Results[3] -eq 'Disabled') + Value = [System.String]$Results[3] Name = 'Internet Printing Client is disabled' Category = $CatName Method = 'Optional Windows Features' @@ -970,8 +971,8 @@ function Confirm-SystemCompliance { # Verify the old Windows Media Player is disabled $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Windows Media Player (legacy) is disabled' - Compliant = [bool]($Results[4] -eq 'NotPresent') - Value = [string]$Results[4] + Compliant = [System.Boolean]($Results[4] -eq 'NotPresent') + Value = [System.String]$Results[4] Name = 'Windows Media Player (legacy) is disabled' Category = $CatName Method = 'Optional Windows Features' @@ -980,8 +981,8 @@ function Confirm-SystemCompliance { # Verify MDAG is enabled $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Microsoft Defender Application Guard is enabled' - Compliant = [bool]($Results[5] -eq 'Enabled') - Value = [string]$Results[5] + Compliant = [System.Boolean]($Results[5] -eq 'Enabled') + Value = [System.String]$Results[5] Name = 'Microsoft Defender Application Guard is enabled' Category = $CatName Method = 'Optional Windows Features' @@ -990,8 +991,8 @@ function Confirm-SystemCompliance { # Verify Windows Sandbox is enabled $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Windows Sandbox is enabled' - Compliant = [bool]($Results[6] -eq 'Enabled') - Value = [string]$Results[6] + Compliant = [System.Boolean]($Results[6] -eq 'Enabled') + Value = [System.String]$Results[6] Name = 'Windows Sandbox is enabled' Category = $CatName Method = 'Optional Windows Features' @@ -1000,8 +1001,8 @@ function Confirm-SystemCompliance { # Verify Hyper-V is enabled $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Hyper-V is enabled' - Compliant = [bool]($Results[7] -eq 'Enabled') - Value = [string]$Results[7] + Compliant = [System.Boolean]($Results[7] -eq 'Enabled') + Value = [System.String]$Results[7] Name = 'Hyper-V is enabled' Category = $CatName Method = 'Optional Windows Features' @@ -1010,8 +1011,8 @@ function Confirm-SystemCompliance { # Verify Virtual Machine Platform is enabled $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Virtual Machine Platform is enabled' - Compliant = [bool]($Results[8] -eq 'Enabled') - Value = [string]$Results[8] + Compliant = [System.Boolean]($Results[8] -eq 'Enabled') + Value = [System.String]$Results[8] Name = 'Virtual Machine Platform is enabled' Category = $CatName Method = 'Optional Windows Features' @@ -1020,8 +1021,8 @@ function Confirm-SystemCompliance { # Verify WMIC is not present $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'WMIC is not present' - Compliant = [bool]($Results[9] -eq 'NotPresent') - Value = [string]$Results[9] + Compliant = [System.Boolean]($Results[9] -eq 'NotPresent') + Value = [System.String]$Results[9] Name = 'WMIC is not present' Category = $CatName Method = 'Optional Windows Features' @@ -1030,8 +1031,8 @@ function Confirm-SystemCompliance { # Verify Internet Explorer mode functionality for Edge is not present $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Internet Explorer mode functionality for Edge is not present' - Compliant = [bool]($Results[10] -eq 'NotPresent') - Value = [string]$Results[10] + Compliant = [System.Boolean]($Results[10] -eq 'NotPresent') + Value = [System.String]$Results[10] Name = 'Internet Explorer mode functionality for Edge is not present' Category = $CatName Method = 'Optional Windows Features' @@ -1040,8 +1041,8 @@ function Confirm-SystemCompliance { # Verify Legacy Notepad is not present $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Legacy Notepad is not present' - Compliant = [bool]($Results[11] -eq 'NotPresent') - Value = [string]$Results[11] + Compliant = [System.Boolean]($Results[11] -eq 'NotPresent') + Value = [System.String]$Results[11] Name = 'Legacy Notepad is not present' Category = $CatName Method = 'Optional Windows Features' @@ -1050,8 +1051,8 @@ function Confirm-SystemCompliance { # Verify Legacy WordPad is not present $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'WordPad is not present' - Compliant = [bool]($Results[12] -eq 'NotPresent') - Value = [string]$Results[12] + Compliant = [System.Boolean]($Results[12] -eq 'NotPresent') + Value = [System.String]$Results[12] Name = 'WordPad is not present' Category = $CatName Method = 'Optional Windows Features' @@ -1060,8 +1061,8 @@ function Confirm-SystemCompliance { # Verify PowerShell ISE is not present $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'PowerShell ISE is not present' - Compliant = [bool]($Results[13] -eq 'NotPresent') - Value = [string]$Results[13] + Compliant = [System.Boolean]($Results[13] -eq 'NotPresent') + Value = [System.String]$Results[13] Name = 'PowerShell ISE is not present' Category = $CatName Method = 'Optional Windows Features' @@ -1070,8 +1071,8 @@ function Confirm-SystemCompliance { # Verify Steps Recorder is not present $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Steps Recorder is not present' - Compliant = [bool]($Results[14] -eq 'NotPresent') - Value = [string]$Results[14] + Compliant = [System.Boolean]($Results[14] -eq 'NotPresent') + Value = [System.String]$Results[14] Name = 'Steps Recorder is not present' Category = $CatName Method = 'Optional Windows Features' @@ -1084,14 +1085,14 @@ function Confirm-SystemCompliance { #Region Windows-Networking-Category Write-Progress -Activity 'Validating Windows Networking Category' -Status 'Processing...' -PercentComplete 80 [System.Object[]]$NestedObjectArray = @() - [String]$CatName = 'Windows Networking' + [System.String]$CatName = 'Windows Networking' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects $NestedObjectArray += [PSCustomObject](Invoke-CategoryProcessing -catname $CatName -Method 'Group Policy') # Check network location of all connections to see if they are public $Condition = Get-NetConnectionProfile | ForEach-Object { $_.NetworkCategory -eq 'public' } - [bool]$IndividualItemResult = -not ($condition -contains $false) ? $True : $false + [System.Boolean]$IndividualItemResult = -not ($condition -contains $false) ? $True : $false # Verify a Security setting using Cmdlet $NestedObjectArray += [PSCustomObject]@{ @@ -1105,7 +1106,7 @@ function Confirm-SystemCompliance { # Verify a Security setting using registry try { - $IndividualItemResult = [bool]((Get-ItemPropertyValue -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\NetBT\Parameters' -Name 'EnableLMHOSTS' -ErrorAction SilentlyContinue) -eq '0') + $IndividualItemResult = [System.Boolean]((Get-ItemPropertyValue -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\NetBT\Parameters' -Name 'EnableLMHOSTS' -ErrorAction SilentlyContinue) -eq '0') } catch { # -ErrorAction SilentlyContinue wouldn't suppress the error if the path exists but property doesn't, so using try-catch @@ -1120,7 +1121,7 @@ function Confirm-SystemCompliance { } # Verify a Security Group Policy setting - $IndividualItemResult = [bool]$($SecurityPoliciesIni.'Registry Values'['MACHINE\System\CurrentControlSet\Control\SecurePipeServers\Winreg\AllowedExactPaths\Machine'] -eq '7,') ? $True : $False + $IndividualItemResult = [System.Boolean]$($SecurityPoliciesIni.'Registry Values'['MACHINE\System\CurrentControlSet\Control\SecurePipeServers\Winreg\AllowedExactPaths\Machine'] -eq '7,') ? $True : $False $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Network access: Remotely accessible registry paths' Compliant = $IndividualItemResult @@ -1131,7 +1132,7 @@ function Confirm-SystemCompliance { } # Verify a Security Group Policy setting - $IndividualItemResult = [bool]$($SecurityPoliciesIni.'Registry Values'['MACHINE\System\CurrentControlSet\Control\SecurePipeServers\Winreg\AllowedPaths\Machine'] -eq '7,') ? $True : $False + $IndividualItemResult = [System.Boolean]$($SecurityPoliciesIni.'Registry Values'['MACHINE\System\CurrentControlSet\Control\SecurePipeServers\Winreg\AllowedPaths\Machine'] -eq '7,') ? $True : $False $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Network access: Remotely accessible registry paths and subpaths' Compliant = $IndividualItemResult @@ -1148,14 +1149,14 @@ function Confirm-SystemCompliance { #Region Miscellaneous-Category Write-Progress -Activity 'Validating Miscellaneous Category' -Status 'Processing...' -PercentComplete 85 [System.Object[]]$NestedObjectArray = @() - [String]$CatName = 'Miscellaneous' + [System.String]$CatName = 'Miscellaneous' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects $NestedObjectArray += [PSCustomObject](Invoke-CategoryProcessing -catname $CatName -Method 'Group Policy') # Verify an Audit policy is enabled - only supports systems with English-US language if ((Get-Culture).name -eq 'en-US') { - $IndividualItemResult = [bool](((auditpol /get /subcategory:"Other Logon/Logoff Events" /r | ConvertFrom-Csv).'Inclusion Setting' -eq 'Success and Failure') ? $True : $False) + $IndividualItemResult = [System.Boolean](((auditpol /get /subcategory:"Other Logon/Logoff Events" /r | ConvertFrom-Csv).'Inclusion Setting' -eq 'Success and Failure') ? $True : $False) $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Audit policy for Other Logon/Logoff Events' Compliant = $IndividualItemResult @@ -1171,9 +1172,9 @@ function Confirm-SystemCompliance { # Checking if all user accounts are part of the Hyper-V security Group # Get all the enabled user accounts - [string[]]$enabledUsers = (Get-LocalUser | Where-Object { $_.Enabled -eq 'True' }).Name | Sort-Object + [System.String[]]$enabledUsers = (Get-LocalUser | Where-Object { $_.Enabled -eq 'True' }).Name | Sort-Object # Get the members of the Hyper-V Administrators security group using their SID - [string[]]$groupMembers = (Get-LocalGroupMember -SID 'S-1-5-32-578').Name -replace "$($env:COMPUTERNAME)\\" | Sort-Object + [System.String[]]$groupMembers = (Get-LocalGroupMember -SID 'S-1-5-32-578').Name -replace "$($env:COMPUTERNAME)\\" | Sort-Object # Set the $MatchHyperVUsers variable to $True only if all enabled user accounts are part of the Hyper-V Security group, if one of them isn't part of the group then returns false [System.Object[]]$MatchHyperVUsers = @() # An array of bool values @@ -1184,8 +1185,8 @@ function Confirm-SystemCompliance { # Saving the results of the Hyper-V administrators members group to the array as an object $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'All users are part of the Hyper-V Administrators group' - Compliant = [bool]($MatchHyperVUsers -notcontains $false) - Value = [bool]($MatchHyperVUsers -notcontains $false) + Compliant = [System.Boolean]($MatchHyperVUsers -notcontains $false) + Value = [System.Boolean]($MatchHyperVUsers -notcontains $false) Name = 'All users are part of the Hyper-V Administrators group' Category = $CatName Method = 'Cmdlet' @@ -1201,14 +1202,14 @@ function Confirm-SystemCompliance { #Region Windows-Update-Category Write-Progress -Activity 'Validating Windows Update Category' -Status 'Processing...' -PercentComplete 90 [System.Object[]]$NestedObjectArray = @() - [String]$CatName = 'Windows Update' + [System.String]$CatName = 'Windows Update' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects $NestedObjectArray += [PSCustomObject](Invoke-CategoryProcessing -catname $CatName -Method 'Group Policy') # Verify a Security setting using registry try { - $IndividualItemResult = [bool]((Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings' -Name 'RestartNotificationsAllowed2' -ErrorAction SilentlyContinue) -eq '1') + $IndividualItemResult = [System.Boolean]((Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings' -Name 'RestartNotificationsAllowed2' -ErrorAction SilentlyContinue) -eq '1') } catch { # -ErrorAction SilentlyContinue wouldn't suppress the error if the path exists but property doesn't, so using try-catch @@ -1229,7 +1230,7 @@ function Confirm-SystemCompliance { #Region Edge-Category Write-Progress -Activity 'Validating Edge Browser Category' -Status 'Processing...' -PercentComplete 95 [System.Object[]]$NestedObjectArray = @() - [String]$CatName = 'Edge' + [System.String]$CatName = 'Edge' # Process items in Registry resources.csv file with "Registry Keys" origin and add them to the $NestedObjectArray array as custom objects $NestedObjectArray += [PSCustomObject](Invoke-CategoryProcessing -catname $CatName -Method 'Registry Keys') @@ -1241,7 +1242,7 @@ function Confirm-SystemCompliance { #Region Non-Admin-Category Write-Progress -Activity 'Validating Non-Admin Category' -Status 'Processing...' -PercentComplete 100 [System.Object[]]$NestedObjectArray = @() - [String]$CatName = 'Non-Admin' + [System.String]$CatName = 'Non-Admin' # Process items in Registry resources.csv file with "Registry Keys" origin and add them to the $NestedObjectArray array as custom objects $NestedObjectArray += [PSCustomObject](Invoke-CategoryProcessing -catname $CatName -Method 'Registry Keys') @@ -1293,12 +1294,12 @@ function Confirm-SystemCompliance { [System.Drawing.Color]::Pink ) - $output = '' + $Output = '' for ($i = 0; $i -lt $text.Length; $i++) { $color = $colors[$i % $colors.Length] - $output += "$($PSStyle.Foreground.FromRGB($color.R, $color.G, $color.B))$($text[$i])$($PSStyle.Reset)" + $Output += "$($PSStyle.Foreground.FromRGB($color.R, $color.G, $color.B))$($text[$i])$($PSStyle.Reset)" } - Write-Output $output + Write-Output $Output } [scriptblock]$WriteRainbow2 = { @@ -1317,12 +1318,12 @@ function Confirm-SystemCompliance { [System.Drawing.Color]::Gold ) - $output = '' + $Output = '' for ($i = 0; $i -lt $text.Length; $i++) { $color = $colors[$i % $colors.Length] - $output += "$($PSStyle.Foreground.FromRGB($color.R, $color.G, $color.B))$($text[$i])$($PSStyle.Reset)" + $Output += "$($PSStyle.Foreground.FromRGB($color.R, $color.G, $color.B))$($text[$i])$($PSStyle.Reset)" } - Write-Output $output + Write-Output $Output } #Endregion Colors @@ -1797,24 +1798,24 @@ function Confirm-SystemCompliance { } # Counting the number of $True Compliant values in the Final Output Object - [int]$TotalTrueCompliantValuesInOutPut = ($FinalMegaObject.'Microsoft Defender' | Where-Object { $_.Compliant -eq $True }).Count + # 49 - 4x(N/A) = 45 - [int]($FinalMegaObject.ASR | Where-Object { $_.Compliant -eq $True }).Count + # 17 - [int]($FinalMegaObject.Bitlocker | Where-Object { $_.Compliant -eq $True }).Count + # 22 + Number of Non-OS drives which are dynamicly increased - [int]($FinalMegaObject.TLS | Where-Object { $_.Compliant -eq $True }).Count + # 21 - [int]($FinalMegaObject.LockScreen | Where-Object { $_.Compliant -eq $True }).Count + # 14 - [int]($FinalMegaObject.UAC | Where-Object { $_.Compliant -eq $True }).Count + # 4 - [int]($FinalMegaObject.'Device Guard' | Where-Object { $_.Compliant -eq $True }).Count + # 8 - [int]($FinalMegaObject.'Windows Firewall' | Where-Object { $_.Compliant -eq $True }).Count + # 19 - [int]($FinalMegaObject.'Optional Windows Features' | Where-Object { $_.Compliant -eq $True }).Count + # 13 - [int]($FinalMegaObject.'Windows Networking' | Where-Object { $_.Compliant -eq $True }).Count + # 9 - [int]($FinalMegaObject.Miscellaneous | Where-Object { $_.Compliant -eq $True }).Count + # 18 - [int]($FinalMegaObject.'Windows Update' | Where-Object { $_.Compliant -eq $True }).Count + # 14 - [int]($FinalMegaObject.Edge | Where-Object { $_.Compliant -eq $True }).Count + # 15 - [int]($FinalMegaObject.'Non-Admin' | Where-Object { $_.Compliant -eq $True }).Count # 11 + [System.Int64]$TotalTrueCompliantValuesInOutPut = ($FinalMegaObject.'Microsoft Defender' | Where-Object { $_.Compliant -eq $True }).Count + # 49 - 4x(N/A) = 45 + [System.Int64]($FinalMegaObject.ASR | Where-Object { $_.Compliant -eq $True }).Count + # 17 + [System.Int64]($FinalMegaObject.Bitlocker | Where-Object { $_.Compliant -eq $True }).Count + # 22 + Number of Non-OS drives which are dynamicly increased + [System.Int64]($FinalMegaObject.TLS | Where-Object { $_.Compliant -eq $True }).Count + # 21 + [System.Int64]($FinalMegaObject.LockScreen | Where-Object { $_.Compliant -eq $True }).Count + # 14 + [System.Int64]($FinalMegaObject.UAC | Where-Object { $_.Compliant -eq $True }).Count + # 4 + [System.Int64]($FinalMegaObject.'Device Guard' | Where-Object { $_.Compliant -eq $True }).Count + # 8 + [System.Int64]($FinalMegaObject.'Windows Firewall' | Where-Object { $_.Compliant -eq $True }).Count + # 19 + [System.Int64]($FinalMegaObject.'Optional Windows Features' | Where-Object { $_.Compliant -eq $True }).Count + # 14 + [System.Int64]($FinalMegaObject.'Windows Networking' | Where-Object { $_.Compliant -eq $True }).Count + # 9 + [System.Int64]($FinalMegaObject.Miscellaneous | Where-Object { $_.Compliant -eq $True }).Count + # 18 + [System.Int64]($FinalMegaObject.'Windows Update' | Where-Object { $_.Compliant -eq $True }).Count + # 14 + [System.Int64]($FinalMegaObject.Edge | Where-Object { $_.Compliant -eq $True }).Count + # 15 + [System.Int64]($FinalMegaObject.'Non-Admin' | Where-Object { $_.Compliant -eq $True }).Count # 11 #Region ASCII-Arts - [string]$WhenValue1To20 = @' + [System.String]$WhenValue1To20 = @' OH N @@ -1834,7 +1835,7 @@ function Confirm-SystemCompliance { '@ - [string]$WhenValue21To40 = @' + [System.String]$WhenValue21To40 = @' ‎‏‏‎‏‏‎⣿⣿⣷⡁⢆⠈⠕⢕⢂⢕⢂⢕⢂⢔⢂⢕⢄⠂⣂⠂⠆⢂⢕⢂⢕⢂⢕⢂⢕⢂ ‎‏‏‎‏‏‎⣿⣿⣿⡷⠊⡢⡹⣦⡑⢂⢕⢂⢕⢂⢕⢂⠕⠔⠌⠝⠛⠶⠶⢶⣦⣄⢂⢕⢂⢕ @@ -1854,7 +1855,7 @@ function Confirm-SystemCompliance { '@ - [string]$WhenValue41To60 = @' + [System.String]$WhenValue41To60 = @' ⣿⡟⠙⠛⠋⠩⠭⣉⡛⢛⠫⠭⠄⠒⠄⠄⠄⠈⠉⠛⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⡇⠄⠄⠄⠄⣠⠖⠋⣀⡤⠄⠒⠄⠄⠄⠄⠄⠄⠄⠄⠄⣈⡭⠭⠄⠄⠄⠉⠙ @@ -1875,7 +1876,7 @@ function Confirm-SystemCompliance { - [string]$WhenValue61To80 = @' + [System.String]$WhenValue61To80 = @' ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⡷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⡿⠋⠈⠻⣮⣳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ @@ -1901,7 +1902,7 @@ function Confirm-SystemCompliance { '@ - [string]$WhenValue81To88 = @' + [System.String]$WhenValue81To88 = @' ⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠔⠶⠒⠉⠈⠸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ @@ -1929,7 +1930,7 @@ function Confirm-SystemCompliance { '@ - [string]$WhenValueAbove88 = @' + [System.String]$WhenValueAbove88 = @' ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⢠⣶⣶⣶⣦⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⠟⠛⢿⣶⡄⠀⢀⣀⣤⣤⣦⣤⡀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⢠⣿⠋⠀⠀⠈⠙⠻⢿⣶⣶⣶⣶⣶⣶⣶⣿⠟⠀⠀⠀⠀⠹⣿⡿⠟⠋⠉⠁⠈⢻⣷⠀⠀⠀⠀⠀ diff --git a/Harden-Windows-Security Module/Main files/Functions.ps1 b/Harden-Windows-Security Module/Main files/Functions.ps1 index 5cd372a1c..b6bbc025b 100644 --- a/Harden-Windows-Security Module/Main files/Functions.ps1 +++ b/Harden-Windows-Security Module/Main files/Functions.ps1 @@ -3,45 +3,18 @@ $global:ErrorActionPreference = 'Stop' # Function to test if current session has administrator privileges Function Test-IsAdmin { - $identity = [Security.Principal.WindowsIdentity]::GetCurrent() - $principal = New-Object Security.Principal.WindowsPrincipal $identity - $principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) + $Identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $Principal = New-Object Security.Principal.WindowsPrincipal $Identity + $Principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) } -# Hiding Invoke-WebRequest progress because it creates lingering visual effect on PowerShell console for some reason -# https://github.com/PowerShell/PowerShell/issues/14348 - -# https://stackoverflow.com/questions/18770723/hide-progress-of-Invoke-WebRequest -# Create an in-memory module so $ScriptBlock doesn't run in new scope -$null = New-Module { - function Invoke-WithoutProgress { - [CmdletBinding()] - param ( - [Parameter(Mandatory)][scriptblock]$ScriptBlock - ) - # Save current progress preference and hide the progress - $prevProgressPreference = $global:ProgressPreference - $global:ProgressPreference = 'SilentlyContinue' - try { - # Run the script block in the scope of the caller of this module function - . $ScriptBlock - } - finally { - # Restore the original behavior - $global:ProgressPreference = $prevProgressPreference - } - } -} - # Make sure the latest version of the module is installed and if not, automatically update it, clean up any old versions function Update-self { - [version]$CurrentVersion = (Test-ModuleManifest "$psscriptroot\Harden-Windows-Security-Module.psd1").Version + [System.Version]$CurrentVersion = (Test-ModuleManifest -Path "$psscriptroot\Harden-Windows-Security-Module.psd1").Version try { - Invoke-WithoutProgress { - [version]$global:LatestVersion = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/Harden-Windows-Security%20Module/version.txt' - } + [System.Version]$global:LatestVersion = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/Harden-Windows-Security%20Module/version.txt' -ProgressAction SilentlyContinue } catch { Write-Error -Message "Couldn't verify if the latest version of the module is installed, please check your Internet connection." @@ -114,17 +87,17 @@ if ((Get-CimInstance -ClassName Win32_OperatingSystem).OperatingSystemSKU -eq '1 } # check if user's OS is latest version -if (-NOT ([System.Environment]::OSVersion.Version -ge [version]'10.0.22621')) { +if (-NOT ([System.Environment]::OSVersion.Version -ge [System.Version]'10.0.22621')) { Write-Error "You're not using the latest version of the Windows OS, exiting..." break } if (Test-IsAdmin) { # check to make sure TPM is available and enabled - [bool]$TPMFlag1 = (Get-Tpm).tpmpresent - [bool]$TPMFlag2 = (Get-Tpm).tpmenabled + [System.Boolean]$TPMFlag1 = (Get-Tpm).tpmpresent + [System.Boolean]$TPMFlag2 = (Get-Tpm).tpmenabled if (!$TPMFlag1 -or !$TPMFlag2) { - Write-Error 'TPM is not available or enabled, please go to your UEFI settings to enable it and then try again.' + Write-Error -Message 'TPM is not available or enabled, please go to your UEFI settings to enable it and then try again.' break } } \ No newline at end of file diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security-Module.psd1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security-Module.psd1 index c75baca37..a88f57d56 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security-Module.psd1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security-Module.psd1 @@ -81,7 +81,7 @@ Harden Windows Safely, Securely, only with Official Microsoft methods '@ # Minimum version of the PowerShell engine required by this module - PowerShellVersion = '7.3.8' + PowerShellVersion = '7.4.0' # Name of the PowerShell host required by this module # PowerShellHostName = '' diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index 588a95656..a7ffc183d 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -1902,7 +1902,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va powershell.exe { # Enable Windows Sandbox - Write-Host "`nEnabling Windows Sandbox" -ForegroundColor Yellow + Write-Host 'Enabling Windows Sandbox' -ForegroundColor Yellow if ((Get-WindowsOptionalFeature -Online -FeatureName Containers-DisposableClientVM).state -eq 'disabled') { try { Enable-WindowsOptionalFeature -Online -FeatureName Containers-DisposableClientVM -All -NoRestart -ErrorAction Stop @@ -2053,9 +2053,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } powershell.exe { + # The first Write-host after PowerShell.exe script block doesn't need the extra `n because it automatically has an extra new line # Uninstall Steps Recorder - Write-Host "`nUninstalling Steps Recorder" -ForegroundColor Yellow + Write-Host 'Uninstalling Steps Recorder' -ForegroundColor Yellow if ((Get-WindowsCapability -Online | Where-Object { $_.Name -like '*App.StepsRecorder*' }).state -ne 'NotPresent') { try { Get-WindowsCapability -Online | Where-Object { $_.Name -like '*App.StepsRecorder*' } | Remove-WindowsCapability -Online -ErrorAction Stop diff --git a/Harden-Windows-Security Module/Main files/Unprotect-WindowsSecurity.psm1 b/Harden-Windows-Security Module/Main files/Unprotect-WindowsSecurity.psm1 index 9cc5b36f6..80594036f 100644 --- a/Harden-Windows-Security Module/Main files/Unprotect-WindowsSecurity.psm1 +++ b/Harden-Windows-Security Module/Main files/Unprotect-WindowsSecurity.psm1 @@ -67,25 +67,23 @@ Function Unprotect-WindowsSecurity { Write-Progress -Activity 'Downloading the required files' -Status 'Processing' -PercentComplete 30 - try { - Invoke-WithoutProgress { - # Download Registry CSV file from GitHub or Azure DevOps - try { - Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/Payload/Registry.csv' -OutFile '.\Registry.csv' - } - catch { - Write-Host 'Using Azure DevOps...' -ForegroundColor Yellow - Invoke-WebRequest -Uri 'https://dev.azure.com/SpyNetGirl/011c178a-7b92-462b-bd23-2c014528a67e/_apis/git/repositories/5304fef0-07c0-4821-a613-79c01fb75657/items?path=/Payload/Registry.csv' -OutFile '.\Registry.csv' - } - - # Download Process Mitigations CSV file from GitHub or Azure DevOps - try { - Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/Payload/ProcessMitigations.csv' -OutFile '.\ProcessMitigations.csv' - } - catch { - Write-Host 'Using Azure DevOps...' -ForegroundColor Yellow - Invoke-WebRequest -Uri 'https://dev.azure.com/SpyNetGirl/011c178a-7b92-462b-bd23-2c014528a67e/_apis/git/repositories/5304fef0-07c0-4821-a613-79c01fb75657/items?path=/Payload/ProcessMitigations.csv' -OutFile '.\ProcessMitigations.csv' - } + try { + # Download Registry CSV file from GitHub or Azure DevOps + try { + Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/Payload/Registry.csv' -OutFile '.\Registry.csv' -ProgressAction SilentlyContinue + } + catch { + Write-Host 'Using Azure DevOps...' -ForegroundColor Yellow + Invoke-WebRequest -Uri 'https://dev.azure.com/SpyNetGirl/011c178a-7b92-462b-bd23-2c014528a67e/_apis/git/repositories/5304fef0-07c0-4821-a613-79c01fb75657/items?path=/Payload/Registry.csv' -OutFile '.\Registry.csv' -ProgressAction SilentlyContinue + } + + # Download Process Mitigations CSV file from GitHub or Azure DevOps + try { + Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/main/Payload/ProcessMitigations.csv' -OutFile '.\ProcessMitigations.csv' -ProgressAction SilentlyContinue + } + catch { + Write-Host 'Using Azure DevOps...' -ForegroundColor Yellow + Invoke-WebRequest -Uri 'https://dev.azure.com/SpyNetGirl/011c178a-7b92-462b-bd23-2c014528a67e/_apis/git/repositories/5304fef0-07c0-4821-a613-79c01fb75657/items?path=/Payload/ProcessMitigations.csv' -OutFile '.\ProcessMitigations.csv' -ProgressAction SilentlyContinue } } catch { @@ -117,12 +115,10 @@ Function Unprotect-WindowsSecurity { # Restore Security group policies back to their default states Write-Progress -Activity 'Restoring the default Security group policies' -Status 'Processing' -PercentComplete 70 - - Invoke-WithoutProgress { - # Download LGPO program from Microsoft servers - Invoke-WebRequest -Uri 'https://download.microsoft.com/download/8/5/C/85C25433-A1B0-4FFA-9429-7E023E7DA8D8/LGPO.zip' -OutFile '.\LGPO.zip' - } - + + # Download LGPO program from Microsoft servers + Invoke-WebRequest -Uri 'https://download.microsoft.com/download/8/5/C/85C25433-A1B0-4FFA-9429-7E023E7DA8D8/LGPO.zip' -OutFile '.\LGPO.zip' -ProgressAction SilentlyContinue + # unzip the LGPO file Expand-Archive -Path .\LGPO.zip -DestinationPath .\ -Force .\'LGPO_30\LGPO.exe' /q /s "$psscriptroot\Resources\Default Security Policy.inf" diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index 779aa94a6..f9e5bc76f 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -1902,7 +1902,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va powershell.exe { # Enable Windows Sandbox - Write-Host "`nEnabling Windows Sandbox" -ForegroundColor Yellow + Write-Host 'Enabling Windows Sandbox' -ForegroundColor Yellow if ((Get-WindowsOptionalFeature -Online -FeatureName Containers-DisposableClientVM).state -eq 'disabled') { try { Enable-WindowsOptionalFeature -Online -FeatureName Containers-DisposableClientVM -All -NoRestart -ErrorAction Stop @@ -2053,9 +2053,10 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } powershell.exe { + # The first Write-host after PowerShell.exe script block doesn't need the extra `n because it automatically has an extra new line # Uninstall Steps Recorder - Write-Host "`nUninstalling Steps Recorder" -ForegroundColor Yellow + Write-Host 'Uninstalling Steps Recorder' -ForegroundColor Yellow if ((Get-WindowsCapability -Online | Where-Object { $_.Name -like '*App.StepsRecorder*' }).state -ne 'NotPresent') { try { Get-WindowsCapability -Online | Where-Object { $_.Name -like '*App.StepsRecorder*' } | Remove-WindowsCapability -Online -ErrorAction Stop From 3a98b8f279d9c7b36adc26b4b386e0eeb7ae4dc9 Mon Sep 17 00:00:00 2001 From: Violet Date: Fri, 17 Nov 2023 20:24:56 +0000 Subject: [PATCH 29/34] Fixed a regression in one of the previous commits It was related to Confirm-SystemCompliance cmdlet. If OS Drive wasn't encrypted, it wouldn't show up in the results. It is now Fixed. --- .../Main files/Confirm-SystemCompliance.psm1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 index 70ac0d21c..dd0acfe4d 100644 --- a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 +++ b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 @@ -604,6 +604,16 @@ function Confirm-SystemCompliance { } } } + else { + $NestedObjectArray += [PSCustomObject]@{ + FriendlyName = 'Secure OS Drive encryption' + Compliant = $false + Value = $false + Name = 'Secure OS Drive encryption' + Category = $CatName + Method = 'Cmdlet' + } + } #region Non-OS-Drive-BitLocker-Drives-Encryption-Verification # Get the list of non OS volumes [System.Object[]]$NonOSBitLockerVolumes = Get-BitLockerVolume | Where-Object { From 6cc8e48793f4abc2201bc995a8bba23c7ecddc6e Mon Sep 17 00:00:00 2001 From: Violet Date: Fri, 17 Nov 2023 22:16:50 +0000 Subject: [PATCH 30/34] Added logic for detecting write-protected drives Added logic for detecting write-protected drives to be used with Enhanced security level of BitLocker which uses Startup Key as an additional key protector. Prior to adding the Startup key, we must ensure the drive where the partial key is going to be saved in is writable. This commit adds logic for it. --- .../Main files/Harden-Windows-Security.ps1 | 76 ++++++++++++++++--- Harden-Windows-Security.ps1 | 76 ++++++++++++++++--- 2 files changed, 128 insertions(+), 24 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index a7ffc183d..2943ab3a4 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -328,19 +328,71 @@ Function Write-SmartText { # Function to get a removable drive to be used by BitLocker category function Get-AvailableRemovableDrives { - # Grab the list of volumes that are removable and have drive letter, display their size in GBs instead of Bytes - [System.Object[]]$AvailableRemovableDrives = Get-Volume | Where-Object { $_.DriveLetter -and $_.DriveType -eq 'Removable' } | - Sort-Object -Property DriveLetter | - Select-Object DriveLetter, FileSystemType, DriveType, @{Name = 'Size'; Expression = { '{0:N2}' -f ($_.Size / 1GB) + ' GB' } } - - + # An empty array of objects that holds the final removable drives list + [System.Object[]]$AvailableRemovableDrives = @() + + Get-Volume | Where-Object { $_.DriveLetter -and $_.DriveType -eq 'Removable' } | + ForEach-Object { + + # Prepare to create an extremely random file name + [System.String]$Path = "$($_.DriveLetter + ':')\$(New-Guid).$(Get-Random -Maximum 400)" + + try { + # Create a test file on the drive to make sure it's not write-protected + New-Item -Path $Path -ItemType File -Value 'test' -Force -ErrorAction Stop | Out-Null + # If the drive wasn't write-protected then delete the test file + Remove-Item -Path $Path -Force + # Add the drive to the list only if it's writable + $AvailableRemovableDrives += $_ + } + catch { + # Drive is write protected, do nothing + } + + } + + # If there is any Writable removable drives, sort and prepare them and then add them to the array + if ($AvailableRemovableDrives) { + $AvailableRemovableDrives = $AvailableRemovableDrives | Sort-Object -Property DriveLetter | + Select-Object DriveLetter, FileSystemType, DriveType, @{Name = 'Size'; Expression = { '{0:N2}' -f ($_.Size / 1GB) + ' GB' } } + + } + if (!$AvailableRemovableDrives) { do { - switch (Select-Option -Options 'Check for removable flash drives again', 'Skip encryptions altogether', 'Exit' -Message "`nNo removable flash drives found. Please insert a USB flash drive") { + switch (Select-Option -Options 'Check for removable flash drives again', 'Skip encryptions altogether', 'Exit' -Message "`nNo removable writable flash drives found. Please insert a USB flash drive. If it's already attached to the system, try ejecting it and inserting it back in.") { 'Check for removable flash drives again' { - [System.Object[]]$AvailableRemovableDrives = Get-Volume | Where-Object { $_.DriveLetter -and $_.DriveType -eq 'Removable' } | - Sort-Object -Property DriveLetter | - Select-Object DriveLetter, FileSystemType, DriveType, @{Name = 'Size'; Expression = { '{0:N2}' -f ($_.Size / 1GB) + ' GB' } } + + # An empty array of objects that holds the final removable drives list + [System.Object[]]$AvailableRemovableDrives = @() + + Get-Volume | Where-Object { $_.DriveLetter -and $_.DriveType -eq 'Removable' } | + ForEach-Object { + + # Prepare to create an extremely random file name + [System.String]$Path = "$($_.DriveLetter + ':')\$(New-Guid).$(Get-Random -Maximum 400)" + + try { + # Create a test file on the drive to make sure it's not write-protected + New-Item -Path $Path -ItemType File -Value 'test' -Force -ErrorAction Stop | Out-Null + # If the drive wasn't write-protected then delete the test file + Remove-Item -Path $Path -Force + # Add the drive to the list only if it's writable + $AvailableRemovableDrives += $_ + } + catch { + # Drive is write protected, do nothing + } + + } + + # If there is any Writable removable drives, sort and prepare them and then add them to the array + if ($AvailableRemovableDrives) { + $AvailableRemovableDrives = $AvailableRemovableDrives | Sort-Object -Property DriveLetter | + Select-Object DriveLetter, FileSystemType, DriveType, @{Name = 'Size'; Expression = { '{0:N2}' -f ($_.Size / 1GB) + ' GB' } } + + } + } 'Skip encryptions altogether' { break BitLockerCategoryLabel } # Breaks from the BitLocker category and won't process Non-OS Drives 'Exit' { &$CleanUp } @@ -1316,7 +1368,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -TpmAndPinAndStartupKeyProtector -StartupKeyPath (Get-AvailableRemovableDrives) -Pin $Pin -ErrorAction Stop | Out-Null } catch { - Write-Error -Message 'There was a problem adding Startup Key to the removable drive, try ejecting and reinserting the flash drive into your device and run this category again.' + Write-Host 'There was a problem adding Startup Key to the removable drive, try ejecting and reinserting the flash drive into your device and run this category again.' -ForegroundColor Red $_ break BitLockerCategoryLabel } @@ -1359,7 +1411,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod 'XtsAes256' -TpmAndPinAndStartupKeyProtector -StartupKeyPath (Get-AvailableRemovableDrives) -Pin $Pin -SkipHardwareTest -ErrorAction Stop *> $null } catch { - Write-Error -Message 'There was a problem adding Startup Key to the removable drive, try ejecting and reinserting the flash drive into your device and run this category again.' + Write-Host 'There was a problem adding Startup Key to the removable drive, try ejecting and reinserting the flash drive into your device and run this category again.' -ForegroundColor Red $_ break BitLockerCategoryLabel } diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index f9e5bc76f..ed633c423 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -328,19 +328,71 @@ Function Write-SmartText { # Function to get a removable drive to be used by BitLocker category function Get-AvailableRemovableDrives { - # Grab the list of volumes that are removable and have drive letter, display their size in GBs instead of Bytes - [System.Object[]]$AvailableRemovableDrives = Get-Volume | Where-Object { $_.DriveLetter -and $_.DriveType -eq 'Removable' } | - Sort-Object -Property DriveLetter | - Select-Object DriveLetter, FileSystemType, DriveType, @{Name = 'Size'; Expression = { '{0:N2}' -f ($_.Size / 1GB) + ' GB' } } - - + # An empty array of objects that holds the final removable drives list + [System.Object[]]$AvailableRemovableDrives = @() + + Get-Volume | Where-Object { $_.DriveLetter -and $_.DriveType -eq 'Removable' } | + ForEach-Object { + + # Prepare to create an extremely random file name + [System.String]$Path = "$($_.DriveLetter + ':')\$(New-Guid).$(Get-Random -Maximum 400)" + + try { + # Create a test file on the drive to make sure it's not write-protected + New-Item -Path $Path -ItemType File -Value 'test' -Force -ErrorAction Stop | Out-Null + # If the drive wasn't write-protected then delete the test file + Remove-Item -Path $Path -Force + # Add the drive to the list only if it's writable + $AvailableRemovableDrives += $_ + } + catch { + # Drive is write protected, do nothing + } + + } + + # If there is any Writable removable drives, sort and prepare them and then add them to the array + if ($AvailableRemovableDrives) { + $AvailableRemovableDrives = $AvailableRemovableDrives | Sort-Object -Property DriveLetter | + Select-Object DriveLetter, FileSystemType, DriveType, @{Name = 'Size'; Expression = { '{0:N2}' -f ($_.Size / 1GB) + ' GB' } } + + } + if (!$AvailableRemovableDrives) { do { - switch (Select-Option -Options 'Check for removable flash drives again', 'Skip encryptions altogether', 'Exit' -Message "`nNo removable flash drives found. Please insert a USB flash drive") { + switch (Select-Option -Options 'Check for removable flash drives again', 'Skip encryptions altogether', 'Exit' -Message "`nNo removable writable flash drives found. Please insert a USB flash drive. If it's already attached to the system, try ejecting it and inserting it back in.") { 'Check for removable flash drives again' { - [System.Object[]]$AvailableRemovableDrives = Get-Volume | Where-Object { $_.DriveLetter -and $_.DriveType -eq 'Removable' } | - Sort-Object -Property DriveLetter | - Select-Object DriveLetter, FileSystemType, DriveType, @{Name = 'Size'; Expression = { '{0:N2}' -f ($_.Size / 1GB) + ' GB' } } + + # An empty array of objects that holds the final removable drives list + [System.Object[]]$AvailableRemovableDrives = @() + + Get-Volume | Where-Object { $_.DriveLetter -and $_.DriveType -eq 'Removable' } | + ForEach-Object { + + # Prepare to create an extremely random file name + [System.String]$Path = "$($_.DriveLetter + ':')\$(New-Guid).$(Get-Random -Maximum 400)" + + try { + # Create a test file on the drive to make sure it's not write-protected + New-Item -Path $Path -ItemType File -Value 'test' -Force -ErrorAction Stop | Out-Null + # If the drive wasn't write-protected then delete the test file + Remove-Item -Path $Path -Force + # Add the drive to the list only if it's writable + $AvailableRemovableDrives += $_ + } + catch { + # Drive is write protected, do nothing + } + + } + + # If there is any Writable removable drives, sort and prepare them and then add them to the array + if ($AvailableRemovableDrives) { + $AvailableRemovableDrives = $AvailableRemovableDrives | Sort-Object -Property DriveLetter | + Select-Object DriveLetter, FileSystemType, DriveType, @{Name = 'Size'; Expression = { '{0:N2}' -f ($_.Size / 1GB) + ' GB' } } + + } + } 'Skip encryptions altogether' { break BitLockerCategoryLabel } # Breaks from the BitLocker category and won't process Non-OS Drives 'Exit' { &$CleanUp } @@ -1316,7 +1368,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -TpmAndPinAndStartupKeyProtector -StartupKeyPath (Get-AvailableRemovableDrives) -Pin $Pin -ErrorAction Stop | Out-Null } catch { - Write-Error -Message 'There was a problem adding Startup Key to the removable drive, try ejecting and reinserting the flash drive into your device and run this category again.' + Write-Host 'There was a problem adding Startup Key to the removable drive, try ejecting and reinserting the flash drive into your device and run this category again.' -ForegroundColor Red $_ break BitLockerCategoryLabel } @@ -1359,7 +1411,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod 'XtsAes256' -TpmAndPinAndStartupKeyProtector -StartupKeyPath (Get-AvailableRemovableDrives) -Pin $Pin -SkipHardwareTest -ErrorAction Stop *> $null } catch { - Write-Error -Message 'There was a problem adding Startup Key to the removable drive, try ejecting and reinserting the flash drive into your device and run this category again.' + Write-Host 'There was a problem adding Startup Key to the removable drive, try ejecting and reinserting the flash drive into your device and run this category again.' -ForegroundColor Red $_ break BitLockerCategoryLabel } From 75f4db2118cbdb26b66f3f73b070d063894f766e Mon Sep 17 00:00:00 2001 From: Violet Date: Fri, 17 Nov 2023 22:18:52 +0000 Subject: [PATCH 31/34] Made variable names more unique --- .../Main files/Harden-Windows-Security.ps1 | 6 +++--- Harden-Windows-Security.ps1 | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index 2943ab3a4..063c984d0 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -370,13 +370,13 @@ function Get-AvailableRemovableDrives { ForEach-Object { # Prepare to create an extremely random file name - [System.String]$Path = "$($_.DriveLetter + ':')\$(New-Guid).$(Get-Random -Maximum 400)" + [System.String]$ExtremelyRandomPath = "$($_.DriveLetter + ':')\$(New-Guid).$(Get-Random -Maximum 400)" try { # Create a test file on the drive to make sure it's not write-protected - New-Item -Path $Path -ItemType File -Value 'test' -Force -ErrorAction Stop | Out-Null + New-Item -Path $ExtremelyRandomPath -ItemType File -Value 'test' -Force -ErrorAction Stop | Out-Null # If the drive wasn't write-protected then delete the test file - Remove-Item -Path $Path -Force + Remove-Item -Path $ExtremelyRandomPath -Force # Add the drive to the list only if it's writable $AvailableRemovableDrives += $_ } diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index ed633c423..c874d41d3 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -370,13 +370,13 @@ function Get-AvailableRemovableDrives { ForEach-Object { # Prepare to create an extremely random file name - [System.String]$Path = "$($_.DriveLetter + ':')\$(New-Guid).$(Get-Random -Maximum 400)" + [System.String]$ExtremelyRandomPath = "$($_.DriveLetter + ':')\$(New-Guid).$(Get-Random -Maximum 400)" try { # Create a test file on the drive to make sure it's not write-protected - New-Item -Path $Path -ItemType File -Value 'test' -Force -ErrorAction Stop | Out-Null + New-Item -Path $ExtremelyRandomPath -ItemType File -Value 'test' -Force -ErrorAction Stop | Out-Null # If the drive wasn't write-protected then delete the test file - Remove-Item -Path $Path -Force + Remove-Item -Path $ExtremelyRandomPath -Force # Add the drive to the list only if it's writable $AvailableRemovableDrives += $_ } From b52c59b9e8e338fa120ee908d193e449fb6d91d4 Mon Sep 17 00:00:00 2001 From: Violet Date: Fri, 17 Nov 2023 22:20:31 +0000 Subject: [PATCH 32/34] Adjusted the names of the security levels --- .../Main files/Confirm-SystemCompliance.psm1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 index dd0acfe4d..c7babb48b 100644 --- a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 +++ b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 @@ -565,13 +565,13 @@ function Confirm-SystemCompliance { # Get the key protectors of the OS Drive [System.String[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector.keyprotectortype - # Check if TPM+PIN and recovery password are being used - Normal encryption level + # Check if TPM+PIN and recovery password are being used - Normal Security level if (($KeyProtectors -contains 'Tpmpin') -and ($KeyProtectors -contains 'RecoveryPassword')) { $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Secure OS Drive encryption' Compliant = $True - Value = 'Normal Encryption Level' + Value = 'Normal Security Level' Name = 'Secure OS Drive encryption' Category = $CatName Method = 'Cmdlet' @@ -579,13 +579,13 @@ function Confirm-SystemCompliance { } } - # Check if TPM+PIN+StartupKey and recovery password are being used - Enhanced encryption level + # Check if TPM+PIN+StartupKey and recovery password are being used - Enhanced security level elseif (($KeyProtectors -contains 'TpmPinStartupKey') -and ($KeyProtectors -contains 'RecoveryPassword')) { $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'Secure OS Drive encryption' Compliant = $True - Value = 'Enhanced Encryption Level' + Value = 'Enhanced Security Level' Name = 'Secure OS Drive encryption' Category = $CatName Method = 'Cmdlet' From c527c2162090a1ab975858a21d230bc231a8d335 Mon Sep 17 00:00:00 2001 From: Violet Date: Sat, 18 Nov 2023 13:43:58 +0000 Subject: [PATCH 33/34] Improved Hyper-V groups member detection Using SIDs instead of account names. This makes it more robust and the comparison logic has also been improved. This change makes everything more inclusive by working in situations where the usernames contain non-English alphabets, lots of spaces and such. Or when the username is the same as the computer name --- .../Main files/Confirm-SystemCompliance.psm1 | 26 +++++++++++-------- .../Main files/Harden-Windows-Security.ps1 | 6 ++--- Harden-Windows-Security.ps1 | 6 ++--- Version.txt | 2 +- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 index c7babb48b..056747392 100644 --- a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 +++ b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 @@ -1181,27 +1181,31 @@ function Confirm-SystemCompliance { } # Checking if all user accounts are part of the Hyper-V security Group - # Get all the enabled user accounts - [System.String[]]$enabledUsers = (Get-LocalUser | Where-Object { $_.Enabled -eq 'True' }).Name | Sort-Object + # Get all the enabled user account SIDs + [System.Security.Principal.SecurityIdentifier[]]$EnabledUsers = (Get-LocalUser | Where-Object { $_.Enabled -eq 'True' }).SID # Get the members of the Hyper-V Administrators security group using their SID - [System.String[]]$groupMembers = (Get-LocalGroupMember -SID 'S-1-5-32-578').Name -replace "$($env:COMPUTERNAME)\\" | Sort-Object + [System.Security.Principal.SecurityIdentifier[]]$GroupMembers = (Get-LocalGroupMember -SID 'S-1-5-32-578').SID - # Set the $MatchHyperVUsers variable to $True only if all enabled user accounts are part of the Hyper-V Security group, if one of them isn't part of the group then returns false - [System.Object[]]$MatchHyperVUsers = @() # An array of bool values - for ($i = 0; $i -lt $enabledUsers.Count; $i++) { - $MatchHyperVUsers += ($enabledUsers[$i] -ceq $groupMembers[$i]) ? $True : $false + # Make sure the arrays are not empty + if (($null -ne $EnabledUsers) -and ($null -ne $GroupMembers)) { + # only outputs data if there is a difference, so when it returns $false it means both arrays are equal + $IndividualItemResult = [System.Boolean](-NOT (Compare-Object -ReferenceObject $EnabledUsers -DifferenceObject $GroupMembers) ) } - + else { + # if either of the arrays are null or empty then return false + [System.Boolean]$IndividualItemResult = $false + } + # Saving the results of the Hyper-V administrators members group to the array as an object $NestedObjectArray += [PSCustomObject]@{ FriendlyName = 'All users are part of the Hyper-V Administrators group' - Compliant = [System.Boolean]($MatchHyperVUsers -notcontains $false) - Value = [System.Boolean]($MatchHyperVUsers -notcontains $false) + Compliant = $IndividualItemResult + Value = $IndividualItemResult Name = 'All users are part of the Hyper-V Administrators group' Category = $CatName Method = 'Cmdlet' } - + # Process items in Registry resources.csv file with "Registry Keys" origin and add them to the $NestedObjectArray array as custom objects $NestedObjectArray += [PSCustomObject](Invoke-CategoryProcessing -catname $CatName -Method 'Registry Keys') diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index 063c984d0..b95798570 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -1,6 +1,6 @@ <#PSScriptInfo -.VERSION 2023.11.17 +.VERSION 2023.11.18 .GUID d435a293-c9ee-4217-8dc1-4ad2318a5770 @@ -101,7 +101,7 @@ $Host.UI.RawUI.WindowTitle = '❤️‍🔥Harden Windows Security❤️‍🔥' # Defining global script variables # Current script's version, the same as the version at the top in the script info section -[System.DateTime]$CurrentVersion = '2023.11.17' +[System.DateTime]$CurrentVersion = '2023.11.18' # Minimum OS build number required for the hardening measures used in this script [System.Decimal]$Requiredbuild = '22621.2428' # Fetching Temp Directory @@ -2191,7 +2191,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } # Allow all Windows users to use Hyper-V and Windows Sandbox by adding all Windows users to the "Hyper-V Administrators" security group using its SID - Get-LocalUser | Where-Object { $_.enabled -eq 'True' } | ForEach-Object { Add-LocalGroupMember -SID 'S-1-5-32-578' -Member $_.Name -ErrorAction SilentlyContinue } + Get-LocalUser | Where-Object { $_.enabled -eq 'True' } | ForEach-Object { Add-LocalGroupMember -SID 'S-1-5-32-578' -Member "$($_.SID)" -ErrorAction SilentlyContinue } # Makes sure auditing for the "Other Logon/Logoff Events" subcategory under the Logon/Logoff category is enabled, doesn't touch affect any other sub-category # For tracking Lock screen unlocks and locks diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index c874d41d3..bc104efa4 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -1,6 +1,6 @@ <#PSScriptInfo -.VERSION 2023.11.17 +.VERSION 2023.11.18 .GUID d435a293-c9ee-4217-8dc1-4ad2318a5770 @@ -101,7 +101,7 @@ $Host.UI.RawUI.WindowTitle = '❤️‍🔥Harden Windows Security❤️‍🔥' # Defining global script variables # Current script's version, the same as the version at the top in the script info section -[System.DateTime]$CurrentVersion = '2023.11.17' +[System.DateTime]$CurrentVersion = '2023.11.18' # Minimum OS build number required for the hardening measures used in this script [System.Decimal]$Requiredbuild = '22621.2428' # Fetching Temp Directory @@ -2191,7 +2191,7 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va } # Allow all Windows users to use Hyper-V and Windows Sandbox by adding all Windows users to the "Hyper-V Administrators" security group using its SID - Get-LocalUser | Where-Object { $_.enabled -eq 'True' } | ForEach-Object { Add-LocalGroupMember -SID 'S-1-5-32-578' -Member $_.Name -ErrorAction SilentlyContinue } + Get-LocalUser | Where-Object { $_.enabled -eq 'True' } | ForEach-Object { Add-LocalGroupMember -SID 'S-1-5-32-578' -Member "$($_.SID)" -ErrorAction SilentlyContinue } # Makes sure auditing for the "Other Logon/Logoff Events" subcategory under the Logon/Logoff category is enabled, doesn't touch affect any other sub-category # For tracking Lock screen unlocks and locks diff --git a/Version.txt b/Version.txt index f0821a3cb..f7399b98f 100644 --- a/Version.txt +++ b/Version.txt @@ -1 +1 @@ -2023.11.17 \ No newline at end of file +2023.11.18 \ No newline at end of file From 6fa0809473233d7e9dd6728a61fda5c148af62e1 Mon Sep 17 00:00:00 2001 From: Violet Date: Sat, 18 Nov 2023 14:39:09 +0000 Subject: [PATCH 34/34] Code optimization Optimized various parts of the code to be shorter and better --- .../Main files/Confirm-SystemCompliance.psm1 | 1 + .../Main files/Functions.ps1 | 34 ++++++++---- .../Main files/Harden-Windows-Security.ps1 | 55 ++++++++++--------- Harden-Windows-Security.ps1 | 55 ++++++++++--------- 4 files changed, 83 insertions(+), 62 deletions(-) diff --git a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 index 056747392..e2816439a 100644 --- a/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 +++ b/Harden-Windows-Security Module/Main files/Confirm-SystemCompliance.psm1 @@ -560,6 +560,7 @@ function Confirm-SystemCompliance { # OS Drive encryption verifications # Check if BitLocker is on for the OS Drive + # The ProtectionStatus remains off while the drive is encrypting or decrypting if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { # Get the key protectors of the OS Drive diff --git a/Harden-Windows-Security Module/Main files/Functions.ps1 b/Harden-Windows-Security Module/Main files/Functions.ps1 index b6bbc025b..2944484a3 100644 --- a/Harden-Windows-Security Module/Main files/Functions.ps1 +++ b/Harden-Windows-Security Module/Main files/Functions.ps1 @@ -78,7 +78,7 @@ function Update-self { # Self update the module Update-self -# Requirements Check +#Region Requirements-Check # check if user's OS is Windows Home edition if ((Get-CimInstance -ClassName Win32_OperatingSystem).OperatingSystemSKU -eq '101') { @@ -86,18 +86,32 @@ if ((Get-CimInstance -ClassName Win32_OperatingSystem).OperatingSystemSKU -eq '1 break } -# check if user's OS is latest version -if (-NOT ([System.Environment]::OSVersion.Version -ge [System.Version]'10.0.22621')) { - Write-Error "You're not using the latest version of the Windows OS, exiting..." +# Check if user's OS is the latest build +# Minimum OS build number required for the hardening measures used in this script +[System.Decimal]$Requiredbuild = '22621.2428' + +# Get OS build version +[System.Decimal]$OSBuild = [System.Environment]::OSVersion.Version.Build + +# Get Update Build Revision (UBR) number +[System.Decimal]$UBR = Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'UBR' + +# Create full OS build number as seen in Windows Settings +[System.Decimal]$FullOSBuild = "$OSBuild.$UBR" + +# Make sure the current OS build is equal or greater than the required build +if (-NOT ($FullOSBuild -ge $Requiredbuild)) { + Write-Error -Message "You're not using the latest build of the Windows OS. A minimum build of $Requiredbuild is required but your OS build is $FullOSBuild`nPlease go to Windows Update to install the updates and then try again." break } if (Test-IsAdmin) { # check to make sure TPM is available and enabled - [System.Boolean]$TPMFlag1 = (Get-Tpm).tpmpresent - [System.Boolean]$TPMFlag2 = (Get-Tpm).tpmenabled - if (!$TPMFlag1 -or !$TPMFlag2) { - Write-Error -Message 'TPM is not available or enabled, please go to your UEFI settings to enable it and then try again.' - break + $TPM = Get-Tpm + if (-not ($TPM.tpmpresent -and $TPM.tpmenabled)) { + Write-Error -Message 'TPM is not available or enabled, please enable it in UEFI settings and try again.' + break } -} \ No newline at end of file +} + +#Endregion Requirements-Check diff --git a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 index b95798570..1e2c2ec1f 100644 --- a/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security Module/Main files/Harden-Windows-Security.ps1 @@ -538,6 +538,7 @@ try { break } # Check the current hard-coded version against the latest version online + # the messages can technically only be seen if installing the script in standalone mode using old Windows PowerShell if ($CurrentVersion -lt $LatestVersion) { Write-Host "The currently installed script's version is $CurrentVersion while the latest version is $LatestVersion" -ForegroundColor Cyan Write-Host 'Please update your script using:' -ForegroundColor Yellow @@ -558,58 +559,59 @@ try { #region RequirementsCheck # check if user's OS is Windows Home edition if ((Get-CimInstance -ClassName Win32_OperatingSystem).OperatingSystemSKU -eq '101') { - Write-Error 'Windows Home edition detected, exiting...' + Write-Error -Message 'Windows Home edition detected, exiting...' break } # check if user's OS is the latest build # Get OS build version [System.Decimal]$OSBuild = [System.Environment]::OSVersion.Version.Build + # Get Update Build Revision (UBR) number [System.Decimal]$UBR = Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'UBR' + # Create full OS build number as seen in Windows Settings [System.Decimal]$FullOSBuild = "$OSBuild.$UBR" + # Make sure the current OS build is equal or greater than the required build if (-NOT ($FullOSBuild -ge $Requiredbuild)) { - Write-Error "You're not using the latest build of the Windows OS. A minimum build of $Requiredbuild is required but your OS build is $FullOSBuild`nPlease go to Windows Update to install the updates and then try again." + Write-Error -Message "You're not using the latest build of the Windows OS. A minimum build of $Requiredbuild is required but your OS build is $FullOSBuild`nPlease go to Windows Update to install the updates and then try again." break } if (Test-IsAdmin) { # check to make sure Secure Boot is enabled if (-NOT (Confirm-SecureBootUEFI)) { - Write-Error 'Secure Boot is not enabled, please go to your UEFI settings to enable it and then try again.' + Write-Error -Message 'Secure Boot is not enabled, please go to your UEFI settings to enable it and then try again.' break } # check to make sure TPM is available and enabled - [System.Boolean]$TPMFlag1 = (Get-Tpm).tpmpresent - [System.Boolean]$TPMFlag2 = (Get-Tpm).tpmenabled - if (!$TPMFlag1 -or !$TPMFlag2) { - Write-Error 'TPM is not available or enabled, please go to your UEFI settings to enable it and then try again.' - break + [System.Object]$TPM = Get-Tpm + if (-not ($TPM.tpmpresent -and $TPM.tpmenabled)) { + Write-Error -Message 'TPM is not available or enabled, please enable it in UEFI settings and try again.' + break } if (-NOT ($MDAVConfigCurrent.AMServiceEnabled -eq $true)) { - Write-Error 'Microsoft Defender Anti Malware service is not enabled, please enable it and then try again.' + Write-Error -Message 'Microsoft Defender Anti Malware service is not enabled, please enable it and then try again.' break } if (-NOT ($MDAVConfigCurrent.AntispywareEnabled -eq $true)) { - Write-Error 'Microsoft Defender Anti Spyware is not enabled, please enable it and then try again.' + Write-Error -Message 'Microsoft Defender Anti Spyware is not enabled, please enable it and then try again.' break } if (-NOT ($MDAVConfigCurrent.AntivirusEnabled -eq $true)) { - Write-Error 'Microsoft Defender Anti Virus is not enabled, please enable it and then try again.' + Write-Error -Message 'Microsoft Defender Anti Virus is not enabled, please enable it and then try again.' break } - + if ($MDAVConfigCurrent.AMRunningMode -ne 'Normal') { - Write-Error "Microsoft Defender is running in $($MDAVConfigCurrent.AMRunningMode) state, please remove any 3rd party AV and then try again." + Write-Error -Message "Microsoft Defender is running in $($MDAVConfigCurrent.AMRunningMode) state, please remove any 3rd party AV and then try again." break - } - + } } #endregion RequirementsCheck @@ -1685,18 +1687,19 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va Write-Progress -Id 0 -Activity 'TLS Security' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) # creating these registry keys that have forward slashes in them - @( 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\DES 56/56', # DES 56-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC2 40/128', # RC2 40-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC2 56/128', # RC2 56-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC2 128/128', # RC2 128-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC4 40/128', # RC4 40-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC4 56/128', # RC4 56-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC4 64/128', # RC4 64-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC4 128/128', # RC4 128-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\Triple DES 168' # 3DES 168-bit (Triple DES 168) + @( + 'DES 56/56', # DES 56-bit + 'RC2 40/128', # RC2 40-bit + 'RC2 56/128', # RC2 56-bit + 'RC2 128/128', # RC2 128-bit + 'RC4 40/128', # RC4 40-bit + 'RC4 56/128', # RC4 56-bit + 'RC4 64/128', # RC4 64-bit + 'RC4 128/128', # RC4 128-bit + 'Triple DES 168' # 3DES 168-bit (Triple DES 168) ) | ForEach-Object { -([Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $env:COMPUTERNAME)).CreateSubKey($_) - } | Out-Null + [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $env:COMPUTERNAME).CreateSubKey("SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\$_") | Out-Null + } # TLS Registry section Set-Location $WorkingDir diff --git a/Harden-Windows-Security.ps1 b/Harden-Windows-Security.ps1 index bc104efa4..e387d911c 100644 --- a/Harden-Windows-Security.ps1 +++ b/Harden-Windows-Security.ps1 @@ -538,6 +538,7 @@ try { break } # Check the current hard-coded version against the latest version online + # the messages can technically only be seen if installing the script in standalone mode using old Windows PowerShell if ($CurrentVersion -lt $LatestVersion) { Write-Host "The currently installed script's version is $CurrentVersion while the latest version is $LatestVersion" -ForegroundColor Cyan Write-Host 'Please update your script using:' -ForegroundColor Yellow @@ -558,58 +559,59 @@ try { #region RequirementsCheck # check if user's OS is Windows Home edition if ((Get-CimInstance -ClassName Win32_OperatingSystem).OperatingSystemSKU -eq '101') { - Write-Error 'Windows Home edition detected, exiting...' + Write-Error -Message 'Windows Home edition detected, exiting...' break } # check if user's OS is the latest build # Get OS build version [System.Decimal]$OSBuild = [System.Environment]::OSVersion.Version.Build + # Get Update Build Revision (UBR) number [System.Decimal]$UBR = Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'UBR' + # Create full OS build number as seen in Windows Settings [System.Decimal]$FullOSBuild = "$OSBuild.$UBR" + # Make sure the current OS build is equal or greater than the required build if (-NOT ($FullOSBuild -ge $Requiredbuild)) { - Write-Error "You're not using the latest build of the Windows OS. A minimum build of $Requiredbuild is required but your OS build is $FullOSBuild`nPlease go to Windows Update to install the updates and then try again." + Write-Error -Message "You're not using the latest build of the Windows OS. A minimum build of $Requiredbuild is required but your OS build is $FullOSBuild`nPlease go to Windows Update to install the updates and then try again." break } if (Test-IsAdmin) { # check to make sure Secure Boot is enabled if (-NOT (Confirm-SecureBootUEFI)) { - Write-Error 'Secure Boot is not enabled, please go to your UEFI settings to enable it and then try again.' + Write-Error -Message 'Secure Boot is not enabled, please go to your UEFI settings to enable it and then try again.' break } # check to make sure TPM is available and enabled - [System.Boolean]$TPMFlag1 = (Get-Tpm).tpmpresent - [System.Boolean]$TPMFlag2 = (Get-Tpm).tpmenabled - if (!$TPMFlag1 -or !$TPMFlag2) { - Write-Error 'TPM is not available or enabled, please go to your UEFI settings to enable it and then try again.' - break + [System.Object]$TPM = Get-Tpm + if (-not ($TPM.tpmpresent -and $TPM.tpmenabled)) { + Write-Error -Message 'TPM is not available or enabled, please enable it in UEFI settings and try again.' + break } if (-NOT ($MDAVConfigCurrent.AMServiceEnabled -eq $true)) { - Write-Error 'Microsoft Defender Anti Malware service is not enabled, please enable it and then try again.' + Write-Error -Message 'Microsoft Defender Anti Malware service is not enabled, please enable it and then try again.' break } if (-NOT ($MDAVConfigCurrent.AntispywareEnabled -eq $true)) { - Write-Error 'Microsoft Defender Anti Spyware is not enabled, please enable it and then try again.' + Write-Error -Message 'Microsoft Defender Anti Spyware is not enabled, please enable it and then try again.' break } if (-NOT ($MDAVConfigCurrent.AntivirusEnabled -eq $true)) { - Write-Error 'Microsoft Defender Anti Virus is not enabled, please enable it and then try again.' + Write-Error -Message 'Microsoft Defender Anti Virus is not enabled, please enable it and then try again.' break } - + if ($MDAVConfigCurrent.AMRunningMode -ne 'Normal') { - Write-Error "Microsoft Defender is running in $($MDAVConfigCurrent.AMRunningMode) state, please remove any 3rd party AV and then try again." + Write-Error -Message "Microsoft Defender is running in $($MDAVConfigCurrent.AMRunningMode) state, please remove any 3rd party AV and then try again." break - } - + } } #endregion RequirementsCheck @@ -1685,18 +1687,19 @@ IMPORTANT: Make sure to keep it in a safe place, e.g., in OneDrive's Personal Va Write-Progress -Id 0 -Activity 'TLS Security' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) # creating these registry keys that have forward slashes in them - @( 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\DES 56/56', # DES 56-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC2 40/128', # RC2 40-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC2 56/128', # RC2 56-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC2 128/128', # RC2 128-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC4 40/128', # RC4 40-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC4 56/128', # RC4 56-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC4 64/128', # RC4 64-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC4 128/128', # RC4 128-bit - 'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\Triple DES 168' # 3DES 168-bit (Triple DES 168) + @( + 'DES 56/56', # DES 56-bit + 'RC2 40/128', # RC2 40-bit + 'RC2 56/128', # RC2 56-bit + 'RC2 128/128', # RC2 128-bit + 'RC4 40/128', # RC4 40-bit + 'RC4 56/128', # RC4 56-bit + 'RC4 64/128', # RC4 64-bit + 'RC4 128/128', # RC4 128-bit + 'Triple DES 168' # 3DES 168-bit (Triple DES 168) ) | ForEach-Object { -([Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $env:COMPUTERNAME)).CreateSubKey($_) - } | Out-Null + [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $env:COMPUTERNAME).CreateSubKey("SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\$_") | Out-Null + } # TLS Registry section Set-Location $WorkingDir