├── tools └── SECDRV │ └── SECDRV.sys ├── src ├── Scripts │ ├── Create-SecDrvModuleManifest.ps1 │ └── QuickFix.ps1 └── SECDRV │ ├── SECDRV.psd1 │ ├── SECDRV.ps1 │ └── SECDRV.psm1 ├── docs ├── SECDRV.md ├── BOOTSIGNING.md └── DEV.md └── README.md /tools/SECDRV/SECDRV.sys: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericwj/PsSecDrv/HEAD/tools/SECDRV/SECDRV.sys -------------------------------------------------------------------------------- /src/Scripts/Create-SecDrvModuleManifest.ps1: -------------------------------------------------------------------------------- 1 | class SecDrvModule { 2 | static [guid]$Guid = [guid]::new("{c8da5d77-b7cf-40a4-9cc8-240f6013a1fd}") 3 | static [string]$Description = "Setup and removal of SECDRV.sys on Windows 10." 4 | static [string]$Author = "Eric Jonker" 5 | static [Version]$Version = [version]::Parse("1.0.0.1") 6 | static [string[]]$Scripts = @("SECDRV.ps1", "SECDRV.psm1") 7 | } 8 | if (!(Test-Path SECDRV.sys)) { 9 | throw [System.IO.FileNotFoundException]::new("SECDRV.sys not found. Change the current directory.") 10 | } 11 | 12 | New-ModuleManifest -Path .\SECDRV.psd1 ` 13 | -Author ([SecDrvModule]::Author) -CompanyName None ` 14 | -ModuleVersion ([SecDrvModule]::Version) ` 15 | -Description ([SecDrvModule]::Description) ` 16 | -Guid ([SecDrvModule]::Guid) ` 17 | -PowerShellVersion 5.0 -ClrVersion 4.0 -DotNetFrameworkVersion 4.0 ` 18 | -ProcessorArchitecture None ` 19 | -RootModule SECDRV ` 20 | -FunctionsToExport Install-SecDrv -------------------------------------------------------------------------------- /docs/SECDRV.md: -------------------------------------------------------------------------------- 1 | # What is SECDRV? 2 | SECDRV is a kernel driver in Windows Operating systems. It implements a copy protection technology 3 | commonly referred to as Safe Disc that is used to protect older games from being copied unbridled 4 | and distributed on illegally burnt media or through the Internet, Torrent sites, etc 5 | 6 | However, as a kernel driver, it has complete and unrestricted access to the system on which it is running 7 | once it is installed and instructed to start. This is fine as long as both the way this software works 8 | and the actual implementation is secure and without (known) flaws. However, this is not the case. 9 | 10 | Windows 10 eliminates this driver from the operating system, because it has known security issues and is 11 | attack surface for malicious people to enter your computer without your knowledge and do practically anything 12 | they can think of, like seeing you type all your passwords and credit card numbers. 13 | 14 | Apart from this, SECDRV is also a relic from a gone era where games were distributed on 15 | CD's and DVD's. However, the removal of this driver also prevents users who still own legitimate copies of 16 | older games that use this copy protection mechanism from running these games on their Windows 10 computers. 17 | -------------------------------------------------------------------------------- /docs/BOOTSIGNING.md: -------------------------------------------------------------------------------- 1 | # What is BOOTSIGNING TEST MODE? 2 | All drivers that Windows can load by default must be submitted to Microsoft for Windows Hardware Quality Labs (WHQL) 3 | verification, after which Microsoft signs them (or hands out a means for the creator of the driver to sign the driver) 4 | with a certificate that chains back to a Microsoft Hardware Publisher certificate. 5 | 6 | BOOTSIGNING TEST MODE is a way in which Windows computers can boot that turns of the WHQL verification selectively. 7 | It is a means for driver developers (let's say NVIDIA) to let Windows load drivers that are not (yet) 8 | signed by a certificate that chains back to a Microsoft Hardware Publisher certificate. It's meant for testing purposes 9 | and it's enabled or disabled at boot time - when the computer starts, so it's called BOOTSIGNING TEST MODE. 10 | However, drivers must still be signed and the computer must still be told to trust the certificate with which 11 | drivers are signed, before a driver will actually load. Hence, WHQL verification is disabled 'selectively'. 12 | 13 | Certificate chaining is the way in which computers can be made to 'trust'. Each chained certificate is checked 14 | by humans - in this case the Microsoft WHQL people - to make sure that the trust that is implied by a certificate 15 | and acted upon by computers all over the world is actually real. Computers also have a way to 'untrust' lets say 16 | a driver publisher that screws up and whose certificate is compromised. That's called a revocation list. 17 | 18 | Each Windows computer has a list of 19 | * Trusted Publishers that can publish drivers (let's say NVIDIA), 20 | * Trusted Root Certification Authorities (Microsoft in this case) 21 | * and a revocation list (including e.g. compromised certificates from DigiCert, to mention an example) 22 | 23 | These lists are updated by humans (at Microsoft) and then pushed to millions of computers using Windows Update. 24 | 25 | SECDRV.sys can be loaded and started without a WHQL certificate by enabling BOOTSIGNING TEST MODE, 26 | by signing the driver with a certificate and by making Windows trust that certificate both as a software 27 | publisher certificate and as a root certificate (the top of the certificate chain). 28 | 29 | This is a bit of a simplification, but it should give a quick overview of how this security feature works. 30 | -------------------------------------------------------------------------------- /src/Scripts/QuickFix.ps1: -------------------------------------------------------------------------------- 1 | # Enable test signing 2 | bcdedit /set "{current}" testsigning on 3 | 4 | # Set path to Windows 10 SDK 5 | $arch = "x86" 6 | if ($env:PROCESSOR_ARCHITECTURE -eq "amd64") { $arch = "x64" } 7 | $env:Path = "$env:Path;${env:ProgramFiles(x86)}\Windows Kits\10\bin\$arch" 8 | cd $env:USERPROFILE 9 | 10 | # Create a private root certificate to sign driver with 11 | $DateTime = "{0:dd MMM yyyy HH:mm}" -f [System.DateTimeOffset]::Now.ToLocalTime() 12 | makecert -r -sr LocalMachine -ss Root -pe -n "CN=Private Signing Certificate for SECDRV.sys on \\$env:COMPUTERNAME created by \\$env:USERDOMAIN\$env:USERNAME on $DateTime" 13 | # Find it in the certificate store 14 | $Certificate = dir Cert:\LocalMachine\Root | where Subject -Match SECDRV | sort NotBefore | select -Last 1 15 | Write-Host "Using certificate $($Certificate.Subject)" 16 | 17 | # Export to file then import to Cert:\LocalMachine\TrustedPublisher 18 | $Rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() 19 | $RandomBytes = [byte[]]::new(16) 20 | $Rng.GetBytes($RandomBytes) 21 | $PasswordPlainText = [System.Convert]::ToBase64String($RandomBytes) 22 | $PasswordSecString = ConvertTo-SecureString -String $PasswordPlainText -AsPlainText -Force 23 | $SuppressOutput = Export-PfxCertificate -Cert $Certificate -FilePath "SECDRV.pfx" -Password $PasswordSecString -Confirm:$false 24 | $SuppressOutput = Import-PfxCertificate -FilePath "SECDRV.pfx" -Password $PasswordSecString -CertStoreLocation Cert:\LocalMachine\TrustedPublisher 25 | 26 | # Delete the certificate with its private key exportable and import the pfx with non-exportable private key 27 | del "Cert:\LocalMachine\Root\$($Certificate.Thumbprint)" 28 | $SuppressOutput = Import-PfxCertificate -FilePath "SECDRV.pfx" -Password $PasswordSecString -CertStoreLocation Cert:\LocalMachine\Root 29 | 30 | # Zero and delete the pfx file and the password 31 | $PfxPath = "$((Get-Location).Path)\SECDRV.pfx" 32 | $PasswordPlainText = "" 33 | $PasswordSecString.Clear() 34 | $FileSize = [System.IO.File]::ReadAllBytes($PfxPath).Length 35 | $RandomBytes = [byte[]]::new($FileSize) # All zeroes 36 | [System.IO.File]::WriteAllBytes($PfxPath, $RandomBytes) 37 | del SECDRV.pfx 38 | 39 | $CDF = @( 40 | "[CatalogHeader]", 41 | "Name=SECDRV.cat", 42 | "PublicVersion=0x1", 43 | "EncodingType=0x00010001", 44 | "CATATTR1=0x10010001:OSAttr:2:6.0", 45 | "[CatalogFiles]", 46 | "SECDRV=SECDRV.sys" 47 | ) -join "`r`n" 48 | # Write catalog definition file (.cdf) 49 | sc SECDRV.cdf -Value $CDF 50 | # Find SECDRV.sys in C:\ and copy the first match to the current directory 51 | $SecdrvSys = dir -ErrorAction SilentlyContinue -Path "$env:SystemDrive\" -Filter SECDRV.sys -Recurse | select -First 1 52 | copy $secdrvsys.FullName . 53 | # Create driver signing catalog file (.cat) and copy readable hashes to text file (.txt) 54 | makecat -o SECDRV.txt -r SECDRV.CDF 55 | # Sign the driver 56 | signtool sign /sm /s Root /sha1 "$($Certificate.Thumbprint)" /t "http://timestamp.verisign.com/scripts/timstamp.dll" secdrv.cat 57 | 58 | # Install driver 59 | copy secdrv.sys "$env:windir\System32\drivers" -Force 60 | signtool catdb /u secdrv.cat 61 | 62 | # Set to automatic start 63 | function Enable-SecDrv { 64 | [CmdLetBinding()] 65 | param( 66 | [Switch]$AutoStart 67 | ) 68 | Process { 69 | if ($AutoStart.IsPresent) { 70 | & cmd /c sc config secdrv start= auto 71 | } else { 72 | & cmd /c sc config secdrv start= demand 73 | } 74 | } 75 | } 76 | 77 | function Disable-SecDrv { 78 | [CmdLetBinding()] 79 | param() 80 | Process { 81 | & cmd /c sc stop secdrv 82 | & cmd /c sc config secdrv start= disabled 83 | } 84 | } 85 | function Start-SecDrv { 86 | [CmdLetBinding()] 87 | param() 88 | Process { & cmd /c sc start secdrv } 89 | } 90 | function Stop-SecDrv { 91 | [CmdLetBinding()] 92 | param() 93 | Process { & cmd /c sc stop secdrv } 94 | } 95 | Enable-SecDrv -AutoStart 96 | # Trying to start before reboot will fail 97 | # Start-SecDrv 98 | -------------------------------------------------------------------------------- /src/SECDRV/SECDRV.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'SECDRV' 3 | # 4 | # Generated by: Eric Jonker 5 | # 6 | # Generated on: 10-11-2015 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'SECDRV' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '1.0.0.1' 16 | 17 | # ID used to uniquely identify this module 18 | GUID = 'c8da5d77-b7cf-40a4-9cc8-240f6013a1fd' 19 | 20 | # Author of this module 21 | Author = 'Eric Jonker' 22 | 23 | # Company or vendor of this module 24 | CompanyName = 'None' 25 | 26 | # Copyright statement for this module 27 | Copyright = '(c) 2015 Eric Jonker. All rights reserved.' 28 | 29 | # Description of the functionality provided by this module 30 | Description = 'Setup and removal of SECDRV.sys on Windows 10.' 31 | 32 | # Minimum version of the Windows PowerShell engine required by this module 33 | PowerShellVersion = '5.0' # Windows 10 34 | 35 | # Name of the Windows PowerShell host required by this module 36 | # PowerShellHostName = '' 37 | 38 | # Minimum version of the Windows PowerShell host required by this module 39 | # PowerShellHostVersion = '' 40 | 41 | # Minimum version of Microsoft .NET Framework required by this module 42 | DotNetFrameworkVersion = '4.5' 43 | 44 | # Minimum version of the common language runtime (CLR) required by this module 45 | CLRVersion = '4.0' 46 | 47 | # Processor architecture (None, X86, Amd64) required by this module 48 | ProcessorArchitecture = 'None' 49 | 50 | # Modules that must be imported into the global environment prior to importing this module 51 | # RequiredModules = @() 52 | 53 | # Assemblies that must be loaded prior to importing this module 54 | # RequiredAssemblies = @() 55 | 56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 57 | # ScriptsToProcess = @() 58 | 59 | # Type files (.ps1xml) to be loaded when importing this module 60 | # TypesToProcess = @() 61 | 62 | # Format files (.ps1xml) to be loaded when importing this module 63 | # FormatsToProcess = @() 64 | 65 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 66 | # NestedModules = @() 67 | 68 | # Functions to export from this module 69 | FunctionsToExport = @( 70 | 'Install-SecDrv' 71 | ,'Enable-SecDrv' 72 | ,'Disable-SecDrv' 73 | ,'Start-SecDrv' 74 | ,'Stop-SecDrv' 75 | ,'Find-SecDrvToolsPath' 76 | ,'Approve-SecDrvToolsPath' 77 | ,'Test-SecDrvWindowsSdk' 78 | ,'Install-SecDrvWindowsSdk' 79 | ,'Repair-SecDrvWindowsSdk' 80 | ,'Uninstall-SecDrvWindowsSdk' 81 | ,'Install-SecDrvWindowsUpdates' 82 | ) 83 | 84 | # Cmdlets to export from this module 85 | CmdletsToExport = '*' 86 | 87 | # Variables to export from this module 88 | VariablesToExport = '*' 89 | 90 | # Aliases to export from this module 91 | AliasesToExport = '*' 92 | 93 | # DSC resources to export from this module 94 | # DscResourcesToExport = @() 95 | 96 | # List of all modules packaged with this module 97 | # ModuleList = @() 98 | 99 | # List of all files packaged with this module 100 | # FileList = @() 101 | 102 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 103 | PrivateData = @{ 104 | 105 | PSData = @{ 106 | 107 | # Tags applied to this module. These help with module discovery in online galleries. 108 | # Tags = @() 109 | 110 | # A URL to the license for this module. 111 | # LicenseUri = '' 112 | 113 | # A URL to the main website for this project. 114 | # ProjectUri = '' 115 | 116 | # A URL to an icon representing this module. 117 | # IconUri = '' 118 | 119 | # ReleaseNotes of this module 120 | # ReleaseNotes = '' 121 | 122 | } # End of PSData hashtable 123 | 124 | } # End of PrivateData hashtable 125 | 126 | # HelpInfo URI of this module 127 | # HelpInfoURI = '' 128 | 129 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 130 | # DefaultCommandPrefix = '' 131 | 132 | } 133 | 134 | -------------------------------------------------------------------------------- /docs/DEV.md: -------------------------------------------------------------------------------- 1 | # How do I test this without ruining my computer? 2 | By making a new computer! 3 | 4 | Seriously, really. And it's not hard if you have Windows Pro. 5 | 6 | ## Get Setup 7 | * Go to http://insider.windows.com and become a Windows Insider. 8 | * Download a Windows Insider Preview image file (.iso) from http://go.microsoft.com/fwlink/?LinkId=691048 9 | Perhaps this requires using the Media Creation Tool option on that page. 10 | * Enable Hyper-V (only available on Windows 8/8.1/10 Pro) 11 | 12 | $result = Enable-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V-All -Online 13 | if ($result.RestartNeeded) { Restart-Computer -Confirm } 14 | 15 | ## Create a Virtual Machine and Install Windows on it 16 | * Then start `Hyper-V Management`, create a Virtual Machine and select the Windows Insider Preview iso file as the CD to boot from. 17 | * Start the VM and follow the prompts to install Windows in the Virtual Machine 18 | * Once you've figured out you have five seconds to press a key and Windows is installed, go straight to Github 19 | and start copying the snippets above. 20 | 21 | Note that most games won't run very well in a VM and even if they do, you'll need a Generation 1 VM to be able 22 | to use your physical CD's and DVD's inside a VM. Whether your VM is a Generation 1 or Generation 2 VM is 23 | just an option to pick while making a new VM. However you can test the script without actually using SECDRV. 24 | 25 | ## Install a game that uses SECDRV 26 | Note however that the script doesn't install the driver service completely. 27 | You need to run the setup program of a game which uses the driver to do that. 28 | 29 | * I made a copy of a game using [Folder2Iso](http://www.trustfm.net/software/utilities/Folder2Iso.php) 30 | * Make sure to use copy/paste the CD labels so they match the original CD's 31 | * Those can be mounted in a virtual CD-ROM drive and used to install the game (but a VM might not play it) 32 | * After that you can test whether SECDRV works by setting the driver to auto start ```Enable-SecDrv -AutoStart``` and rebooting 33 | * You can also run ```Start-SecDrv``` and ```cmd /c sc query secdrv``` to see if SECDRV works 34 | 35 | I used `Command and Conquer: Generals` (both CD's) with labels `GENERALS1` and `GENERALS2` respectively. 36 | 37 | ## VHD Boot for actual gaming 38 | To really use SECDRV, I recommend using Hyper-V Management to create a VHDX file and setup your computer to boot 39 | from it (only from a virtual Hard Disk, but on your real computer). There's help for that in your favorite search engine. 40 | Search for VHD boot. Make sure you have a partition that is large enough and not encrypted to put the virtual hard disk on. 41 | Even if the disk is a dynamically expanding disk, booting from it requires that it is inflated to the maximum size you specified. 42 | A minimum of 20GB is recommended to install Windows 10 Pro. 43 | 44 | ## Options for testing the script and making changes without committing on GitHub 45 | The scripts use a convention introduced in [ASP.NET 5](https://www.github.com/aspnet/) which is useful for storing 46 | machine specific configuration and secretive stuff (like certificate files). Okay well, maybe I misuse the feature a little, 47 | but the idea is just to keep the scripts and its configuration separate and the certificate it creates secret for as far as 48 | necessary. 49 | 50 | The script can be configured by creating the directory `%APPDATA%\Microsoft\UserSecrets\SECDRV` and creating an empty 51 | file called `secrets.json` in it. You can then configure a few options like so: 52 | 53 | ```JSON 54 | { 55 | "OriginSite": "file:////COMPUTERNAME/C$/PathToRepoDirectory", 56 | "DevPath": "\\\\COMPUTERNAME\\C$\\PathToRepoDirectory", 57 | "Verbose": true, 58 | "Confirm": true 59 | } 60 | ``` 61 | 62 | Where 63 | * `OriginSite` is an URL (hence `file:`) where SECDRV.ps1 will download the module files and SECDRV.sys from. 64 | * `DevPath` is a path (hence in UNC notation \\COMPUTER\Share\Path) that contains a copy of the files installed 65 | with the Windows 10 SDK necessary to install SECDRV (so with some tweaking you don't have to install the SDK on a test machine). 66 | The list of files is the `$ToolsManifest` in `SECDRV.psm1`. 67 | * `Verbose` will set PowerShell `$VerbosePreference` (only in the bootstrap script SECDRV.ps1) used to generate more output in PowerShell to see what the script does. 68 | * `Confirm` will set PowerShell `$ConfirmPreference` (only in the bootstrap script SECDRV.ps1) used to do prompting before doing potentially irreversible stuff. 69 | 70 | Both `OriginSite` and `DevPath` should point to the folder containing the `src` and `Tools` subfolders in this repository and ultimately `SECDRV.psm1`, `SECDRV.psd1` and `SECDRV.sys`. 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How to install SECDRV.sys to play games 2 | Microsoft does provide a way to enable `SECDRV` on 64-bit Windows. 3 | 4 | * Install a game that brings (a recent version of) `SECDRV.sys`. 5 | * Start the game. It should prompt for elevation. Elevate. It won't work, but you have to do it once. 6 | If there is an error about insufficient permissions, launch the game explicitly as Administrator, either by right-clicking the shortcut and selecting *Run as Administrator* or by launching the game by typing the executables path and name from an elevated command prompt. 7 | Dismiss all errors about things not working and quit the game. Also ignore and do not accept any changes suggested by Windows to run the game in compatibility mode or as Administrator or anything else. That might still be required for the game itself, it is not required for SECDRV to work besides running it elevated just once. 8 | * Install the Windows 10 SDK from [Get the standalone Windows 10 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk). 9 | Just install all components. 10 | * Start *PowerShell* as an administrator. 11 | * Make sure you are on 64-bit Windows. Going through this guide on 32-bit Windows is pretty pointless - games should work without. `SECDRV` just works on 32-bit Windows. 12 | ``` 13 | [System.Environment]::Is64BitOperatingSystem 14 | ``` 15 | * Find `makecat.exe`, `makecert.exe` and `signtool.exe` and add the path to your PATH in System Properties, Environment Variables. 16 | The ones in a x86 subfolder are always OK on all Intel architecture chips. No need to match the hardware or the OS bitness. 17 | ``` 18 | $SdkToolsPath = dir -Path "${env:ProgramFiles(x86)}\Windows Kits\10" -Recurse -Directory | where { $n = $_.FullName; $_.BaseName -eq "x86" -and [System.IO.File]::Exists("$n\makecert.exe") -and [System.IO.File]::Exists("$n\makecat.exe") -and [System.IO.File]::Exists("$n\signtool.exe") } | sort CreationTime | select -Last 1 19 | $env:Path = "$env:Path;$($SdkToolsPath.FullName)" 20 | ``` 21 | * Create a new folder in your Downloads folder 22 | ``` 23 | $WorkingDirectory = "$env:UserProfile\Downloads\SECDRV" 24 | if (-not (Test-Path $WorkingDirectory)) { mkdir $WorkingDirectory | Out-Null } 25 | ``` 26 | * Run all further commands in a PowerShell prompt as Administrator in the folder you created. 27 | ``` 28 | cd $WorkingDirectory 29 | ``` 30 | * Copy `SECDRV.sys` in it. Match your operating system bitness. 31 | If it's an old version, replace it with this one downloadable [here](https://github.com/ericwj/PsSecDrv/raw/master/tools/SECDRV/SECDRV.sys). Its from September 2006. 32 | ``` 33 | # Using curl (Windows 10 has it inbox) 34 | curl.exe -OL https://github.com/ericwj/PsSecDrv/raw/master/tools/SECDRV/SECDRV.sys 35 | # Using PowerShell or PowerShell Core 36 | iwr -Uri https://github.com/ericwj/PsSecDrv/raw/master/tools/SECDRV/SECDRV.sys -OutFile SECDRV.sys 37 | ``` 38 | That one is 64-bit. 39 | * Check that you have the correct bitness: 40 | ``` 41 | $bytes = [System.IO.File]::ReadAllBytes("$PWD\SECDRV.sys") 42 | [int]$pe = [System.Text.Encoding]::ASCII.GetString($bytes, 0, 1KB).IndexOf("PE`0`0") 43 | $mc = [System.BitConverter]::ToUInt16($bytes, $pe + 4) 44 | switch ($mc) { 0x8664 { "64-bit" } 0x014c { "32-bit" } default { "Unknown" } } 45 | ``` 46 | > This is a very opportunistic way of reading the machine type in about as few lines as possible by simply finding the first occurrence of `PE\0\0` in the file. So use with caution. 47 | 48 | The even more opportunistic way is to simply do `type SECDRV.sys | more`, make sure the first two letters are `MZ` and look for `PE` usually all by itself on a line about a screen down of `This program cannot be run in DOS mode.` and see if you can find `L` or `d` two lines down from it. 49 | 1. If the letter is `L` then the PE file is probably 32-bit (`L` is `0x4c` in ASCII). 50 | 2. If the letter is `d` then the PE file is probably 64-bit (`d` is `0x64` in ASCII). 51 | image 52 | 53 | * Enable test signing boot mode. 54 | ``` 55 | bcdedit /set "{current}" testsigning on 56 | ``` 57 | * Pick a subject - any subject, but include the text "SECDRV" in it 58 | ``` 59 | $Subject = "SECDRV.sys Published by \\$env:ComputerName\$env:UserName on $("{0:yyyy-MM-dd HH:mm}" -f [datetimeoffset]::Now)" 60 | ``` 61 | * Create a root certificate.  62 | ``` 63 | # try this 64 | makecert -r -sr LocalMachine -ss My -n $Subject 65 | # if it doesn't work, use this 66 | makecert -r -sr LocalMachine -ss My -n "CN=$Subject" 67 | ``` 68 | * Open Local Machine Certificates. 69 | ``` 70 | certlm.msc 71 | ``` 72 | * Go to Personal, Certificates and select the certificate created, there usually is only one, or match the subject, right click Copy. 73 | * Go to Trusted Root Certification Authorities, Certificates. Paste. 74 | * Go to Trusted Publishers, Certificates. Paste. 75 | * Make a text file called `SECDRV.cdf` in the folder and put the text between @" and "@ in it. 76 | ``` 77 | Set-Content -Path SECDRV.cdf -Value @" 78 | [CatalogHeader] 79 | Name=SECDRV.cat 80 | PublicVersion=0x1 81 | EncodingType=0x00010001 82 | CATATTR1=0x10010001:OSAttr:2:6.0 83 | [CatalogFiles] 84 | SECDRV=SECDRV.sys 85 | "@ 86 | ``` 87 | * Make a driver catalog file in the folder. 88 | ``` 89 | makecat -o SECDRV.txt -r SECDRV.cdf 90 | ``` 91 | * Get the thumbprint of the certificate you created. The thumbprint is shown in `certlm` for the certificate created, just double click it and look around, without spaces. Or get it in PowerShell with dir: 92 | ```PS 93 | $Publishers = dir Cert:\LocalMachine\TrustedPublisher | where HasPrivateKey | sort NotAfter 94 | $Publishers | select Thumbprint, NotBefore, NotAfter, Subject 95 | $Publisher = $Publishers | select -Last 1 96 | ``` 97 | * Sign the driver. 98 | ``` 99 | signtool sign /sm /s TrustedPublisher /sha1 "$($Publisher.Thumbprint)" /t http://timestamp.digicert.com secdrv.cat 100 | ``` 101 | If you get `SignTool Error: No file digest algorithm specified. (...) use the /fd certHash option.`, run this instead 102 | ``` 103 | signtool sign /sm /s TrustedPublisher /sha1 "$($Publisher.Thumbprint)" /fd SHA256 /t http://timestamp.digicert.com secdrv.cat 104 | ``` 105 | * Install the driver. This adds it to the driver catalog on your system, but does not copy files or create driver services. 106 | ``` 107 | signtool catdb /u secdrv.cat 108 | ``` 109 | * Just to be sure, overwrite the `SECDRV.sys` referred to by the kernel driver service with the exact version that you signed and installed. 110 | ``` 111 | sc.exe qc secdrv 112 | ``` 113 | If the output has something like `\??\C:\Windows\system32\drivers\SECDRV.sys`, copy that path excluding `\??\` and use it in the next command: 114 | ``` 115 | copy .\SECDRV.sys "C:\Windows\system32\drivers\SECDRV.sys" 116 | ``` 117 | * Reboot. 118 | * Test if it works. 119 | ``` 120 | sc.exe start secdrv 121 | ``` 122 | 123 | If it doesn't work, check these reasons. 124 | * You are not an Administrator or you opened the PowerShell prompt without elevation. Right click the button in the Task Bar and hit *Run as Administrator* and start over. 125 | * `SECDRV.sys` is too old. Then the driver doesn't start. Right click it, hit *Properties*, go to *Details* and check *Product version*. It contains a date as a string. If you downloaded it from the link above, the version is "SECURITY Driver 4.03.086 2006/09/13". 126 | * `SECDRV.sys` is 32-bit and your Windows is 64-bit. Download `SECDRV.sys` from the link given. 127 | * `SECDRV.sys` is 64-bit and your Windows is 32-bit. Then don't download the driver from the link given, but use whichever version came with the game you installed. Also you shouldn't have started following this guide in the first place. 128 | * You might have to run games that need `SECDRV` as Administrator. The driver might not be installed and the driver services might not be present until you have tried this. 129 | * Secure Boot is enabled. Run `bcdedit` again after disabling it. 130 | * You didn't reboot. You will have to reboot. 131 | 132 | Now play games. 133 | -------------------------------------------------------------------------------- /src/SECDRV/SECDRV.ps1: -------------------------------------------------------------------------------- 1 | class SecDrvBootstrap { 2 | # Load UserSecrets - System-specific configuration 3 | static [object]GetUserSecrets() { 4 | $Path = [SecDrvBootstrap]::UserSecretsPath 5 | $File = Join-Path $Path "secrets.json" 6 | if (-not (Test-Path $File)) { return $null } 7 | $FileInfo = dir $File 8 | if ($FileInfo -eq $null) { return $null } 9 | if ($FileInfo.Attributes -band ([System.IO.FileAttributes]::Directory)) { return $null } 10 | $Text = Get-Content $File -ErrorAction SilentlyContinue -Encoding UTF8 11 | if ([string]::IsNullOrEmpty($Text)) { return $null } 12 | $Text = $Text -join "`n" 13 | $Json = $null 14 | try { 15 | $Json = ConvertFrom-Json $Text -ErrorAction SilentlyContinue 16 | } catch { 17 | Write-Warning "Could not load '$File'." 18 | } 19 | return $Json 20 | } 21 | 22 | # During dev/test, download from the URL "OriginSite" in UserSecrets (possibly file:////) 23 | static [string]GetOriginSite() { 24 | $Json = [SecDrvBootstrap]::GetUserSecrets() 25 | if ($Json -ne $null -and ![String]::IsNullOrWhiteSpace($Json.OriginSite)) { 26 | try { 27 | [string]$Url = $Json.OriginSite 28 | [uri]$Uri = [uri]::new($Url) # might throw for invalid user data 29 | return $Url 30 | } catch { 31 | Write-Warning "Could not parse 'OriginSite'." 32 | } 33 | } 34 | if (![String]::IsNullOrWhitespace($env:SECDRV_ORIGINSITE)) { 35 | return $env:SECDRV_ORIGINSITE 36 | } 37 | return "https://raw.githubusercontent.com/ericwj/PsSecDrv/master/" 38 | } 39 | # During dev/test, download from the URL "OriginSite" in UserSecrets (possibly file:////) 40 | static [bool]GetVerbosePreference() { 41 | $Json = [SecDrvBootstrap]::GetUserSecrets() 42 | if ($Json -ne $null -and ![String]::IsNullOrWhiteSpace($Json.Verbose)) { 43 | return $Json.Verbose 44 | } 45 | return $false 46 | } 47 | # During dev/test, download from the URL "OriginSite" in UserSecrets (possibly file:////) 48 | static [bool]GetConfirmPreference() { 49 | $Json = [SecDrvBootstrap]::GetUserSecrets() 50 | if ($Json -ne $null -and ![String]::IsNullOrWhiteSpace($Json.Confirm)) { 51 | return $Json.Confirm 52 | } 53 | return $false 54 | } 55 | static [void]Download([string]$Url, [string]$TargetPath) { 56 | $uri = [Uri]::new($Url) # will throw if invalid 57 | if ($uri.IsFile -or $uri.IsUnc) { 58 | # used during test by setting secrets.json#OriginSite=file://C:/Users/pathToGitRepo 59 | copy -Path $uri.LocalPath -Destination $TargetPath 60 | } else { 61 | # Supposed to be some raw.githubusercontent.com URI (tbd) 62 | try { 63 | curl -Uri $Url -OutFile $TargetPath -ErrorAction SilentlyContinue 64 | } catch { 65 | throw [System.Net.WebException]::new("Could not download '$TargetPath' from '$Url'.") 66 | } 67 | } 68 | } 69 | static [string]$ModuleName = "SECDRV" 70 | static [object]$FQN = @{ModuleName=([SecDrvBootstrap]::ModuleName);Guid=[guid]::new("{c8da5d77-b7cf-40a4-9cc8-240f6013a1fd}");ModuleVersion="0.0.0.0"} 71 | static [string]$UserSecretsPath = "$env:USERPROFILE\AppData\Roaming\Microsoft\UserSecrets\SECDRV" 72 | # PowerShell Module path in UserProfile 73 | static [string]$TargetPathPsm = "$env:USERPROFILE\Documents\WindowsPowerShell\Modules\$([SecDrvBootstrap]::ModuleName)" 74 | # Default path to store the driver and the publisher certificate etc 75 | static [string]$TargetPathDefault = [SecDrvBootstrap]::UserSecretsPath 76 | static [string[]]$Manifest = @( 77 | "src/SECDRV/SECDRV.psd1=[TargetPathPsm]\SECDRV.psd1", 78 | "src/SECDRV/SECDRV.psm1=[TargetPathPsm]\SECDRV.psm1", 79 | # "$([SecDrvBootstrap]::ModuleName).ChoiceDialog.psm1=TargetPathPsm", 80 | "tools/SECDRV/SECDRV.sys=[TargetPathDefault]\SECDRV.sys" 81 | ) 82 | static [System.Collections.Generic.Dictionary[string,string]]GetManifestUrls() { 83 | $Result = [System.Collections.Generic.Dictionary[string,string]]::new() 84 | 85 | $OriginSite = [SecDrvBootstrap]::GetOriginSite() 86 | $OriginSiteUri = [Uri]::new($OriginSite) 87 | $OriginSiteIsFile = $OriginSiteUri.IsFile -or $OriginSiteUri.IsUnc 88 | 89 | $Psm = [SecDrvBootstrap]::TargetPathPsm 90 | $Default = [SecDrvBootstrap]::TargetPathDefault 91 | foreach ($file in [SecDrvBootstrap]::Manifest) { 92 | $split = $file -split "=" 93 | $name = $split[0] # tools/SECDRV/SECDRV.sys 94 | $spec = $split[1] # [TargetPathDefault]\SECDRV.sys 95 | $TargetPath = $spec ` 96 | -replace "\[TargetPathPsm\]", ([SecDrvBootstrap]::TargetPathPsm) ` 97 | -replace "\[TargetPathDefault\]", ([SecDrvBootstrap]::TargetPathDefault) 98 | if ($TargetPath -eq $spec) { 99 | $message = "The target path specification '$spec' for file '$file' in the manifest is unknown." 100 | throw [System.ArgumentException]::new($message) 101 | } 102 | $FileOrigin = $OriginSite 103 | if ($OriginSiteIsFile) { 104 | $FileOrigin = Join-Path $OriginSiteUri.LocalPath $name 105 | } else { 106 | $FileOrigin = $OriginSite + $name # simply concatenate 107 | } 108 | $Result.Add($FileOrigin, $TargetPath) 109 | } 110 | return $Result 111 | } 112 | } 113 | # Test code 114 | if ($false) { 115 | # See what's here 116 | dir $env:USERPROFILE\Documents\WindowsPowerShell\Modules -Recurse -ErrorAction SilentlyContinue 117 | dir ([SecDrvBootstrap]::UserSecretsPath) -ErrorAction SilentlyContinue 118 | # Module Info 119 | Get-Module -Name SECDRV -ListAvailable | select -First 1 | fl 120 | # Clean 121 | del (Join-Path ([SecDrvBootstrap]::UserSecretsPath) "SECDRV.*") 122 | rmdir ([SecDrvBootstrap]::TargetPathPsm) -Recurse 123 | } 124 | $Verbose = [switch]::new([SecDrvBootstrap]::GetVerbosePreference()) 125 | $Confirm = [switch]::new([SecDrvBootstrap]::GetConfirmPreference()) 126 | if ($Verbose) { $VerbosePreference = [System.Management.Automation.ActionPreference]::Continue } 127 | # Download the neccessary files 128 | $urls = [SecDrvBootstrap]::GetManifestUrls() 129 | Write-Warning "Downloading the SECDRV intaller module from $([SecDrvBootstrap]::GetOriginSite())" 130 | $origin = [uri]::new([SecDrvBootstrap]::GetOriginSite()) 131 | foreach ($ding in $urls.Keys) { 132 | if ($origin.IsFile -or $origin.IsUnc) { 133 | $short = "." + $ding.Substring($origin.LocalPath.Length) 134 | } else { 135 | $short = $origin.MakeRelative([uri]::new($ding)) 136 | } 137 | Write-Verbose "Downloading $short => $($urls[$ding])" 138 | $FileOrigin = $ding 139 | $TargetPath = $urls[$ding] 140 | $FolderPath = [System.IO.Path]::GetDirectoryName($TargetPath) 141 | if (!(Test-Path $FolderPath)) { $unused = mkdir $FolderPath } 142 | [SecDrvBootstrap]::Download($FileOrigin, $TargetPath) 143 | } 144 | # Check PSModulePath and add the $userprofile\WindowsPowerShell\Modules folder (which is included by default) if it isn't there 145 | $ModulePaths = $env:PSModulePath -split ";" 146 | $ModulePath = "$env:USERPROFILE\Documents\WindowsPowerShell\Modules" 147 | # Unconditionally reorder $PSModulePath to prioritize $ModulePath, 148 | # since we want to load from the folder where the script is downloaded to 149 | # so if this script is run (from a trusted source), it'll always download again (from a trusted source) 150 | # and screw up any attempts to replace the module with another version. 151 | #if ($ModulePaths -notcontains $ModulePath) { 152 | $ModulePaths = @($ModulePath) + $ModulePaths | select -Unique # might have the effect of reordering $ModulePath as the first folder 153 | $env:PSModulePath = $ModulePaths -join ";" 154 | #} 155 | 156 | # If the module is loaded, unload it first 157 | $LoadedModule = Get-Module -FullyQualifiedName ([SecDrvBootstrap]::FQN) 158 | if ($LoadedModule -ne $null) { 159 | Remove-Module -Name SECDRV 160 | } 161 | # Now we should be able to upgrade 162 | $ModuleOK = Get-Module -FullyQualifiedName ([SecDrvBootstrap]::FQN) -ListAvailable 163 | if ($ModuleOK -eq $null) { 164 | Write-Error "Something went wrong. The module SECDRV is not installed." 165 | } else { 166 | Import-Module -FullyQualifiedName ([SecDrvBootstrap]::FQN) 167 | } 168 | $CommandOK = Get-Command -Verb Install -Noun SecDrv -FullyQualifiedModule ([SecDrvBootstrap]::FQN) 169 | if ($CommandOK -eq $null) { 170 | Write-Error "Something went wrong. Could not find the Install-SecDrv command." 171 | } 172 | Write-Host "The PowerShell Module is installed." 173 | if ($Verbose) { 174 | Write-Verbose "The following commands are available:" 175 | Get-Command -FullyQualifiedModule ([SecDrvBootstrap]::FQN) | select Noun, Verb, Name, CommandType, Source, Version | sort Noun, Verb | ft 176 | Write-Verbose "To get more information, start PowerShell and try:" 177 | Write-Verbose "`tGet-Help " 178 | Write-Verbose "E.g." 179 | Write-Verbose "`tGet-Help Install-SecDrv" 180 | } 181 | # no checking for tools here and no call to a comand -- that's better left to the copy/paste script 182 | #Install-SecDrv -Verbose:$Verbose -Confirm:$Confirm -------------------------------------------------------------------------------- /src/SECDRV/SECDRV.psm1: -------------------------------------------------------------------------------- 1 | class SecDrvConstants { 2 | static [string]$DriverName = "SECDRV" 3 | static [string]$SdkPortalTitle = "Windows Dev Center" 4 | static [string]$SdkPortalUrl = "https://dev.windows.com/en-us/downloads" 5 | static [string]$SdkTitle = "Windows Standalone SDK for Windows 10" 6 | static [string]$SdkUrl = "https://go.microsoft.com/fwlink/p/?LinkId=619296" 7 | static [string]$VerisignTimestampServerUrl = "http://timestamp.verisign.com/scripts/timstamp.dll" 8 | # Only the last option 'Windows Software Development Kit' is neccessary. 9 | static [string]$SdkSetup = "sdksetup.exe" 10 | 11 | static [string[]]$WindowsUpdateManifest = @( 12 | "Insider=KB3106638" # Update for Windows 10 Insider Preview: October 21, 2015 13 | "RTM=KB3105213" # 10/31/2015 Cumulatieve update Windows 10 voor x64-systemen (KB3105210) 14 | ) 15 | # List the files that a user needs to run all code 16 | # SECDRV.ps1 is already downloaded, but needs to be downloaded again to the proper folder 17 | static [string[]]$DownloadManifest = @( 18 | "SECDRV.ps1", # Bootstrap Script 19 | "SECDRV.psd1", # Module Definition 20 | "SECDRV.psm1", # Module File 21 | "SECDRV.sys", # Presigning - Driver File 22 | "SECDRV.cat", # Presigning - Signed Driver Catalog 23 | "SECDRV.cer" # Presigning - Driver Publisher Certificate 24 | ) 25 | # List of files needed to run the tools (in the x64 or x86 folder in the SDK) 26 | static [string[]]$ToolsManifest = @( 27 | "appxpackaging.dll" 28 | ,"appxsip.dll" 29 | ,"makecat.exe" 30 | ,"makecat.exe.manifest" 31 | ,"makecert.exe" 32 | ,"Microsoft.Windows.Build.Appx.AppxPackaging.dll.manifest" 33 | ,"Microsoft.Windows.Build.Appx.AppxSip.dll.manifest" 34 | ,"Microsoft.Windows.Build.Appx.OpcServices.dll.manifest" 35 | ,"Microsoft.Windows.Build.Signing.mssign32.dll.manifest" 36 | ,"Microsoft.Windows.Build.Signing.wintrust.dll.manifest" 37 | ,"mssign32.dll" 38 | ,"opcservices.dll" 39 | ,"signtool.exe" 40 | ,"signtool.exe.manifest" 41 | ,"wintrust.dll" 42 | ,"wintrust.dll.ini" 43 | ) 44 | static [hashtable]$PSWindowsUpdateManifest = @{ 45 | ModuleName = "PSWindowsUpdate" 46 | RequiredVersion = "1.5.1.11" 47 | #ModuleVersion = "1.5.1.11" 48 | GUID = "{8ed488ad-7c77-4b33-b06e-32214925163b}" 49 | } 50 | 51 | # Lets align with the ASP.NET way of storing settings we don't want to reside in the code repository 52 | # https://github.com/aspnet/UserSecrets/blob/dev/src/Microsoft.Extensions.Configuration.UserSecrets/PathHelper.cs 53 | static [string]GetUserSecretFolder() { 54 | $Result = $env:APPDATA # Windows only, no conditional stuff 55 | $Result = Join-Path $Result "Microsoft\UserSecrets\SECDRV" 56 | return $Result 57 | } 58 | # Get settings not checked into the repo - not necessarily secrets so much, but user-system-specific stuff 59 | static [string]GetUserSecrets() { 60 | $Path = Join-Path ([SecDrvConstants]::GetUserSecretFolder()) "secrets.json" 61 | $File = dir $Path -ErrorAction SilentlyContinue 62 | # Does something exist at $Path? 63 | if ($File -eq $null) { return $null; } 64 | # Path exists, but is not a file 65 | if ($File.Attributes -band [System.IO.FileAttributes]::Directory) { return $null } 66 | $Text = Get-Content -Path $Path -Encoding UTF8 67 | # Get-Content -is [Object[]], make it [string] 68 | $Text = $Text -join "`n" 69 | if ([string]::IsNullOrWhiteSpace($Text)) { return $null } 70 | return $Text 71 | } 72 | 73 | # Get the Developer path - the path where we may find tools on a specific system 74 | static [string]GetDevPath() { 75 | $Text = [SecDrvConstants]::GetUserSecrets() 76 | $Json = ConvertFrom-Json -InputObject $Text 77 | if ($Json -eq $null) { return $null } 78 | return $Json.DevPath # May still be null 79 | } 80 | } 81 | set-alias ?: Invoke-Ternary -Option AllScope -Description "PowerShell Conditional Operator filter alias" 82 | filter Invoke-Ternary ([scriptblock]$condition, [scriptblock]$yes, [scriptblock]$no) 83 | { if (&$condition) { &$yes } else { &$no } } 84 | 85 | function Read-String([string]$Prompt, [string]$Default, [scriptblock]$Validator, [string]$RegexPattern, [string]$ErrorMessage) { 86 | while ($true) { 87 | $PromptString = $Prompt 88 | if ($Default -ne $null -and $Default -ne "") { $PromptString = "$Prompt [Default = $Default]" } 89 | $Value = Read-Host -Prompt $PromptString 90 | if ($Value -eq "") { $Value = $Default } 91 | if ($RegexPattern -ne $null) { $Validator = { ([regex]$RegexPattern).IsMatch($args[0]) } } 92 | if ($Validator -eq $null) { break; } 93 | if ([bool]$Validator.Invoke($Value)) { break; } 94 | if ($ErrorMessage -ne $null) { Write-Host $ErrorMessage } 95 | Write-Host "The string entered was not allowed, or the validator threw an exception." 96 | } 97 | return $Value 98 | } 99 | class ChoiceItem { 100 | ChoiceItem([string]$Label, [string]$HelpText, [object]$Value) { 101 | $this.Label = $Label 102 | $this.HelpText = $HelpText 103 | $this.Choice = [ChoiceItem]::Build($Label, $HelpText) 104 | $this.Value = $Value 105 | } 106 | [string]$Label 107 | [string]$HelpText 108 | [object]$Value 109 | [System.Management.Automation.Host.ChoiceDescription]$Choice 110 | static [System.Management.Automation.Host.ChoiceDescription] Build([string]$Label, [string]$HelpText) { 111 | if ($Label -eq $null) { 112 | throw "A label is required." 113 | } 114 | if ($HelpText -eq $null) { 115 | return [System.Management.Automation.Host.ChoiceDescription]::new($Label) 116 | } else { 117 | return [System.Management.Automation.Host.ChoiceDescription]::new($Label, $HelpText) 118 | } 119 | } 120 | } 121 | class ChoiceDialog { 122 | ChoiceDialog([object]$Default, [string]$Caption, [string]$Message, [object[]]$Choices) { 123 | $this.Caption = $Caption 124 | $list = [System.Collections.Generic.LinkedList[Tuple[string,string, object]]]::new() 125 | $Choices | foreach { $list.Add([tuple]::Create([string]$_.Item1, [string]$_.Item2, [object]$_.Item3)) } 126 | $this.Choices = $list | foreach { [ChoiceItem]::new($_.Item1, $_.Item2, $_.Item3) } 127 | $this.Default = $Default 128 | $this.Message = $Message 129 | $this.Choice = $Default 130 | } 131 | [object]$Default 132 | [string]$Caption 133 | [string]$Message 134 | [ChoiceItem[]]$Choices 135 | [object]$Choice 136 | [object] Show() { 137 | trap { Write-Host (Get-Variable -Name Error -ValueOnly -Scope Global) } 138 | $cd = [System.Collections.Generic.List[System.Management.Automation.Host.ChoiceDescription]]::new() 139 | $this.Choices | foreach { $cd.Add($_.Choice) } 140 | $h = Get-Variable -Name Host -ValueOnly -Scope Global 141 | $e = Get-Variable -Name Error -ValueOnly -Scope Global 142 | $e.Clear() 143 | try { 144 | $d = -1 145 | for ($i = 0; $i -lt $this.Choices.Count; $i++) { 146 | if ($this.Default -eq $this.Choices[$i].Value) { 147 | $d = $i 148 | break; 149 | } 150 | } 151 | $n = $h.UI.PromptForChoice($this.Caption, $this.Message, $cd.ToArray(), $d) 152 | $this.Choice = ?: { $n -lt 0 } { $null } { $this.Choices[$n].Value } 153 | } catch { 154 | Write-Host $e -ForegroundColor Red 155 | throw 156 | } 157 | return $this.Choice 158 | } 159 | static [bool] Is([object]$a, [object]$b) { 160 | return [object]::Equals($a, $b) 161 | } 162 | } 163 | function Invoke-CommandLine { 164 | [CmdLetBinding()] 165 | param( 166 | [String]$Command, 167 | [Object[]]$Arguments, 168 | [Switch]$Echo, 169 | [Switch]$WhatIf 170 | ) 171 | Process { 172 | if ($WhatIf.IsPresent) { 173 | Write-Host "What If: Invoke-CommandLine $Command $Arguments" 174 | $Output = "What If: Invoke-CommandLine $Command $Arguments" 175 | $ExitCode = 0 176 | } elseif ($Echo.IsPresent) { 177 | Tee-Object -InputObject (& $Command $Arguments) -Variable Output 178 | $ExitCode = $LASTEXITCODE 179 | } else { 180 | $Output = & $Command $Arguments 181 | if ($Verbose.IsPresent) { 182 | Write-Verbose $Output 183 | } 184 | $ExitCode = $LASTEXITCODE 185 | } 186 | return @{ Output = $Output; ExitCode = $ExitCode } 187 | } 188 | } 189 | # We need to run Windows update, since clean install produces ACCESS_VIOLATION in Export-PfxCertificate 190 | # And we need to 1) Uninstall and 2) Disable KB3086255 (https://support.microsoft.com/en-us/kb/3086255) 191 | function Install-SecDrvWindowsUpdates { 192 | $NuGet = Get-PackageProvider -Name NuGet -ForceBootstrap 193 | 194 | $PswuSpec = [SecDrvConstants]::PSWindowsUpdateManifest 195 | $PswuFqn = [Microsoft.PowerShell.Commands.ModuleSpecification]::new($PswuSpec) 196 | $Pswu = Get-Module -FullyQualifiedName $PswuFqn -ListAvailable 197 | if ($Pswu -eq $null) { 198 | Install-Package -Name ($PswuSpec.ModuleName) -ProviderName PSModule -ForceBootstrap -RequiredVersion ($PswuSpec.RequiredVersion) -Confirm:$false -Force 199 | $Pswu = Get-Module -FullyQualifiedName $PswuFqn -ListAvailable 200 | if ($Pswu -eq $null) { 201 | throw "The required package PSWindowsUpdate could not be obtained. This may be due to connectivity problems, or a version conflict. Try again later." 202 | } 203 | Import-Module -FullyQualifiedName $PswuFqn 204 | } 205 | $ready = ((Get-WUInstallerStatus) -match "ready") # text :( 206 | Write-Warning "Searching Windows Update" 207 | $updates = Get-WUInstall -ListOnly 208 | $dlsize = ($updates | measure MaxDownloadSize).Sum / 1MB 209 | if ($updates.Count -gt 0) { 210 | Write-Warning "Downloading $($updates.Count) updates ($("{0:N1}" -f $dlsize)MB)" 211 | $ignore = Get-WUInstall -DownloadOnly -AcceptAll 212 | Write-Warning "Installing updates..." 213 | Get-WUInstall -IgnoreUserInput -AcceptAll -IgnoreReboot 1>4 3>4 # Success and Warnings as Verbose 214 | } 215 | $ready = ((Get-WUInstallerStatus) -match "ready") 216 | $status = Get-WURebootStatus -Silent 217 | return !$status 218 | } 219 | 220 | function Find-SecDrvToolsPath { 221 | [CmdLetBinding()] 222 | param( 223 | [String]$Hint, 224 | [String]$DevPath, 225 | [String]$Sentinel = "makecert.exe" 226 | ) 227 | Process { 228 | $WindowsSdkPath = "${env:ProgramFiles(x86)}\Windows Kits\10\bin" 229 | $HasToolsPath = ![string]::IsNullOrWhiteSpace($Hint) 230 | $HasDevPath = ![string]::IsNullOrWhiteSpace($DevPath) 231 | 232 | if ($HasToolsPath -and (Test-Path $Hint)) { 233 | $RootFolder = $Hint 234 | } elseif ($HasDevPath -and (Test-Path $DevPath)) { 235 | $RootFolder = $DevPath 236 | } elseif (Test-Path $WindowsSdkPath) { 237 | $RootFolder = $WindowsSdkPath 238 | } else { 239 | $message = 240 | "A directory containing the tools does not exist. " + 241 | "You must specify a path as a hint, or an actual path to the required executables." 242 | throw [System.ArgumentNullException]::new($message) 243 | } 244 | 245 | $Joined = Join-Path $RootFolder $Sentinel 246 | if (Test-Path $Joined) { 247 | return $RootFolder 248 | } 249 | 250 | $ProcessorArchitecture = [Environment]::GetEnvironmentVariable("PROCESSOR_ARCHITECTURE", [System.EnvironmentVariableTarget]::Machine) 251 | if ([System.Environment]::Is64BitOperatingSystem -ne [System.Environment]::Is64BitProcess) { 252 | $Bitness = 32 253 | if ([System.Environment]::Is64BitOperatingSystem) { $Bitness = 64 } 254 | throw [System.NotSupportedException]::new( 255 | "Your operating system and the current PowerShell host process are not the same bitness. " + 256 | "You may be running 'PowerShell ISE (x86)' on a 64-bit operating system - start 'PowerShell ISE' instead. " + 257 | "Restart PowerShell using the $Bitness-bit version.") 258 | } 259 | if ([System.Environment]::Is64BitOperatingSystem) { 260 | if ($ProcessorArchitecture -eq "AMD64") { 261 | $ToolsPath = Join-Path $RootFolder "x64" 262 | } else { 263 | throw [System.NotSupportedException]::new( 264 | "Processor architecture $ProcessorArchitecture is not supported. This script only supports 64-bit Intel architecture (AMD64).") 265 | } 266 | } else { 267 | if ($ProcessorArchitecture -eq "x86") { 268 | $ToolsPath = Join-Path $RootFolder "x86" 269 | } else { 270 | throw [System.NotSupportedException]::new( 271 | "Processor architecture $ProcessorArchitecture is not supported. This script only supports 32-bit Intel architecture (x86).") 272 | } 273 | } 274 | 275 | $Joined = Join-Path $ToolsPath $Sentinel 276 | if (Test-Path $Joined) { 277 | return $ToolsPath 278 | } else { 279 | $message = "Could not find the tools under folder '$RootFolder'. '$Joined' not found." 280 | throw [System.IO.FileNotFoundException]::new($message, $Joined) 281 | } 282 | } 283 | } 284 | function Approve-SecDrvToolsPath { 285 | [CmdLetBinding()] 286 | param( 287 | [Parameter(Mandatory = $true)][String]$Path 288 | ) 289 | Process { 290 | $Manifest = [SecDrvConstants]::ToolsManifest 291 | foreach ($file in $Manifest) { 292 | $FullPath = Join-Path $Path $file 293 | $OK = $false 294 | while ($true) { 295 | if (!(Test-Path $FullPath)) { break; } 296 | $FileInfo = dir $FullPath -ErrorAction SilentlyContinue 297 | if ($FileInfo -eq $null) { break; } 298 | if ($FileInfo.Attributes -band [System.IO.FileAttributes]::Directory) { break; } 299 | $OK = $true 300 | break; 301 | } 302 | if ($OK -eq $false) { 303 | throw [System.IO.FileNotFoundException]::new("A required file is missing: $file", $FullPath) 304 | } 305 | } 306 | return $true 307 | } 308 | } 309 | function Test-SecDrvWindowsSdk { 310 | [CmdLetBinding()] 311 | param( 312 | ) 313 | Process { 314 | $WindowsSdk = Get-Package -ProviderName msi -MinimumVersion 10.0.26624 | where Source -Match 89CA32DC-2323-27B2-A388-54373DA1A492 315 | return $WindowsSdk -ne $null 316 | } 317 | } 318 | enum SecDrvPrompt { 319 | Yes 320 | No 321 | } 322 | <# 323 | sdksetup /? 324 | 325 | sdksetup /list 326 | Windows Software Development Kit - Windows 10.0.26624 327 | Available features: (Features marked with * can be downloaded but cannot be installed on this computer) OptionId.WindowsPerformanceToolkit OptionId.WindowsDesktopDebuggers OptionId.AvrfExternal OptionId.NetFxSoftwareDevelopmentKit OptionId.WindowsSoftwareLogoToolkit OptionId.MSIInstallTools OptionId.WindowsSoftwareDevelopmentKit Use Ctrl+C to copy this information to the Clipboard. 328 | 329 | 330 | #> 331 | enum SecDrvWindowsSdkInstallAction { 332 | Install 333 | Uninstall 334 | Repair 335 | } 336 | function Install-SecDrvWindowsSdk { 337 | [CmdLetBinding()] 338 | param( 339 | [Parameter()][String]$LogPath, 340 | [Parameter()][String]$AllowRestart, # If specified, will not use /norestart 341 | #[Parameter()][Switch]$Verbose, # If specified, will not use /quiet # Install-SecDrvWindowsSdk : A parameter with the name 'Verbose' was defined multiple times for the command. 342 | [Parameter()][Switch]$Ceip, 343 | [Parameter()][Timespan]$Timeout = [TimeSpan]::FromMinutes(20), # How long to wait for setup to complete -- pretty long for slow computers 344 | [Parameter()][Switch]$Confirm 345 | ) 346 | Process { 347 | Run-SecDrvWindowsSdkInstaller -InstallAction:$([SecDrvWindowsSdkInstallAction]::Install) -AllowRestart:$AllowRestart -Ceip:$Ceip -Timeout:$Timeout ` 348 | -LogPath:$LogPath -Verbose:($Verbose.IsPresent) -Confirm:($Confirm.IsPresent) # -WhatIf:$($WhatIf.IsPresent) 349 | } 350 | } 351 | function Uninstall-SecDrvWindowsSdk { 352 | [CmdLetBinding()] 353 | param( 354 | [Parameter()][String]$LogPath, 355 | [Parameter()][String]$AllowRestart, # If specified, will not use /norestart 356 | #[Parameter()][Switch]$Verbose, # If specified, will not use /quiet # Install-SecDrvWindowsSdk : A parameter with the name 'Verbose' was defined multiple times for the command. 357 | [Parameter()][Switch]$Ceip, 358 | [Parameter()][Timespan]$Timeout = [TimeSpan]::FromMinutes(20), # How long to wait for setup to complete -- pretty long for slow computers 359 | [Parameter()][Switch]$Confirm 360 | ) 361 | Process { 362 | Run-SecDrvWindowsSdkInstaller -InstallAction:$([SecDrvWindowsSdkInstallAction]::Uninstall) -AllowRestart:$AllowRestart -Ceip:$Ceip -Timeout:$Timeout ` 363 | -LogPath:$LogPath -Verbose:($Verbose.IsPresent) -Confirm:($Confirm.IsPresent) # -WhatIf:$($WhatIf.IsPresent) 364 | } 365 | } 366 | function Repair-SecDrvWindowsSdk { 367 | [CmdLetBinding()] 368 | param( 369 | [Parameter()][String]$LogPath, 370 | [Parameter()][String]$AllowRestart, # If specified, will not use /norestart 371 | #[Parameter()][Switch]$Verbose, # If specified, will not use /quiet # Install-SecDrvWindowsSdk : A parameter with the name 'Verbose' was defined multiple times for the command. 372 | [Parameter()][Switch]$Ceip, 373 | [Parameter()][Timespan]$Timeout = [TimeSpan]::FromMinutes(20), # How long to wait for setup to complete -- pretty long for slow computers 374 | [Parameter()][Switch]$Confirm 375 | ) 376 | Process { 377 | Run-SecDrvWindowsSdkInstaller -InstallAction:$([SecDrvWindowsSdkInstallAction]::Repair) -AllowRestart:$AllowRestart -Ceip:$Ceip -Timeout:$Timeout ` 378 | -LogPath:$LogPath -Verbose:($Verbose.IsPresent) -Confirm:($Confirm.IsPresent) # -WhatIf:$($WhatIf.IsPresent) 379 | } 380 | } 381 | function Run-SecDrvWindowsSdkInstaller { 382 | [CmdLetBinding()] 383 | param( 384 | [Parameter(Mandatory = $true)][SecDrvWindowsSdkInstallAction]$InstallAction, 385 | [Parameter()][String]$LogPath, 386 | [Parameter()][String]$AllowRestart, # If specified, will not use /norestart 387 | #[Parameter()][Switch]$Verbose, # If specified, will not use /quiet # Install-SecDrvWindowsSdk : A parameter with the name 'Verbose' was defined multiple times for the command. 388 | [Parameter()][Switch]$Ceip, 389 | [Parameter()][Timespan]$Timeout, # How long to wait for setup to complete -- pretty long for slow computers 390 | [Parameter()][Switch]$Confirm 391 | ) 392 | Process { 393 | $Lowercase = $InstallAction.ToString().ToLower() 394 | if ($Confirm.IsPresent) { 395 | $InstallPrompt = [ChoiceDialog]::new([SecDrvPrompt]::Yes, "$($InstallAction)?", "Do you want to $Lowercase $([SecDrvConstants]::SdkTitle) now?", @( 396 | [tuple]::Create("&Yes", "$InstallAction $([SecDrvConstants]::SdkTitle).", [SecDrvPrompt]::Yes), 397 | [tuple]::Create("&No", "Skip $InstallAction of $([SecDrvConstants]::SdkTitle).", [SecDrvPrompt]::No) 398 | )) 399 | $InstallAnswer = $InstallPrompt.Show() 400 | if ($InstallAnswer -eq [SecDrvPrompt]::No) { return } 401 | } 402 | 403 | Write-Host "The $([SecDrvConstants]::SdkTitle) will be downloaded from $([SecDrvConstants]::SdkPortalTitle) and then launched." 404 | $TempFile = Join-Path $env:TEMP $([SecDrvConstants]::SdkSetup) 405 | $Uri = [SecDrvConstants]::SdkUrl 406 | Write-Verbose "Downloading $Uri => $TempFile" 407 | curl -Uri $Uri -OutFile $TempFile 408 | 409 | Write-Host "$([SecDrvConstants]::SdkTitle) Setup will now start. Please wait..." 410 | $Arguments = @() 411 | try { 412 | switch ($InstallAction) { 413 | ([SecDrvWindowsSdkInstallAction]::Install) { $Arguments += @("/features OptionId.WindowsSoftwareDevelopmentKit") } 414 | ([SecDrvWindowsSdkInstallAction]::Repair) { $Arguments += @("/repair") } 415 | ([SecDrvWindowsSdkInstallAction]::Uninstall) { $Arguments += @("/features OptionId.WindowsSoftwareDevelopmentKit /uninstall") } 416 | default { 417 | throw [System.NotSupportedException]::new("The setup action '$InstallAction' is not supported.") 418 | } 419 | } 420 | if ($AllowRestart) { 421 | $Arguments += @("/forcerestart") # Can't use promptrestart in PowerShell 422 | } else { 423 | $Arguments += @("/norestart") 424 | } 425 | if (-not $Verbose.IsPresent) { 426 | $Arguments += @("/quiet") 427 | } 428 | if ($Ceip.IsPresent) { 429 | $Arguments += @("/ceip on") 430 | } else { 431 | $Arguments += @("/ceip off") 432 | } 433 | if ([string]::IsNullOrWhiteSpace($LogPath) -eq $false) { 434 | $Arguments += @("/log $LogPath") 435 | } 436 | $psi = [System.Diagnostics.ProcessStartInfo]::new() 437 | $psi.FileName = $TempFile 438 | $psi.Arguments = $Arguments -join " " 439 | Write-Verbose "Executing $($psi.FileName) $($psi.Arguments)" 440 | $process = [System.Diagnostics.Process]::Start($psi) 441 | $Start = [System.DateTimeOffset]::Now 442 | $End = $Start + $Timeout 443 | $ProgressID = Get-Random -Minimum 1 -Maximum ([int]::MaxValue) 444 | 445 | $ProcentComplete = [System.Collections.Generic.Dictionary[double,double]]::new() 446 | $max = [int]::MaxValue 447 | $cfg = @( 448 | @{ Seconds = 0; Procent = 0; Step = 0 }, 449 | @{ Seconds = 180; Procent = 75; Step = (180/75) }, 450 | @{ Seconds = 360; Procent = 20; Step = (360/20) }, 451 | @{ Seconds = 999; Procent = 5; Step = (999/ 5) } 452 | ) 453 | $index = 0 454 | $current = $cfg[0]; 455 | $cumulative = @{ Seconds = 0; Procent = 0; Step = 0 } 456 | foreach ($i in [System.Linq.Enumerable]::Range(1, 200)) { # $i = 1 $i = 151 $i++ 457 | $p = $i / 2 # base 100% 458 | if ($p -gt ($cumulative.Procent + $current.Procent)) { # $cumulative; $current 459 | $cumulative.Seconds += $current.Seconds 460 | $cumulative.Procent += $current.Procent 461 | $cumulative.Step = $p 462 | $index++; 463 | $current = $cfg[$index] 464 | } 465 | $Seconds = $cumulative.Seconds + ($p - $cumulative.Step) * $current.Step 466 | try { 467 | $ProcentComplete.Add($Seconds, $p) 468 | } catch { 469 | $i;$p;throw 470 | } 471 | } 472 | 473 | try { 474 | while ([System.DateTimeOffset]::Now -lt $End) { 475 | $Now = [System.DateTimeOffset]::Now 476 | $Elapsed = ($Now - $Start).TotalSeconds 477 | $Remaining = ($End - $Now).TotalSeconds 478 | $Key = $ProcentComplete.Keys | where {$_ -le $Elapsed} | sort | select -Last 1 479 | $Progress = $ProcentComplete[$Key] 480 | Write-Debug "Elapsed $Elapsed ($($Elapsed.GetType().Name)) Key: $Key ($($Key.GetType().Name)) Value: $Progress ($($Progress.GetType().Name)) LINQ: $($ProcentComplete.Keys | where {$_ -le $Elapsed} | sort )" 481 | Write-Progress -PercentComplete $Progress -Activity "$InstallAction $([SecDrvConstants]::SdkTitle)" -Status "Running, $($Now - $Start) elapsed." -Id $ProgressID 482 | Start-Sleep -Seconds 1 483 | if ($process.HasExited) { break; } 484 | } 485 | if ($process.ExitCode -eq 0) { 486 | Write-Host "Success" -ForegroundColor Green 487 | } else { 488 | Write-Error "Failure. $([SecDrvConstants]::SdkSetup) exited with exit code $($process.ExitCode)" 489 | } 490 | } finally { 491 | Write-Progress -Id $ProgressID -Completed -Activity Done 492 | } 493 | if (!$process.HasExited) { 494 | throw [System.TimeoutException]::new("Timed out waiting for $($process.MainWindowTitle).") 495 | } 496 | if ($process.ExitCode -ne 0) { 497 | Write-Error "Something went wrong. $([SecDrvConstants]::SdkSetup) exited with exit code $($process.ExitCode)." 498 | } 499 | if (Test-RebootRequired) { 500 | Write-Warning "A reboot is required to finish $Lowercase $([SecDrvConstants]::SdkTitle)" 501 | } else { 502 | } 503 | } finally { 504 | del $TempFile -Force -ErrorAction SilentlyContinue 505 | } 506 | } 507 | } 508 | # this is silly, there should be a better way - Enable-WindowsOptionalFeature cooks up a value for example 509 | function Test-RebootRequired { 510 | [CmdletBinding()] 511 | param() 512 | Process { 513 | # http://blogs.technet.com/b/heyscriptingguy/archive/2013/06/11/determine-pending-reboot-status-powershell-style-part-2.aspx 514 | $cbs = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing' 515 | $RebootPending = ($cbs | Get-Member | where Name -Match RebootPending).Count -gt 0 516 | 517 | $wau = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update' 518 | if ($wau -eq $null) { $RebootRequired = $false } else { 519 | $RebootRequired = ($wau | Get-Member | where Name -Match RebootRequired).Count -gt 0 520 | } 521 | 522 | $csm = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' 523 | # is also not null on my system but only files in $env:LocalAppData need renames 524 | $PendingFileRename = $csm -ne $null -and $csm.PendingFileRenameOperations -ne $null 525 | 526 | $SplattedWmiInvoke = @{ 527 | NameSpace='ROOT\ccm\ClientSDK' 528 | Class='CCM_ClientUtilities' 529 | Name='DetermineIfRebootPending' 530 | ComputerName="." 531 | ErrorAction='SilentlyContinue' 532 | } 533 | $WmiResult = Invoke-WmiMethod @SplattedWmiInvoke 534 | if ($WmiResult -eq $null) { 535 | $WmiSaisRebootPending = $false 536 | } elseif ($WmiResult.ReturnValue -ne 0) { 537 | throw "DetermineIfRebootPending failed with return value $($WmiResult.ReturnValue)" 538 | } else { 539 | $WmiSaisRebootPending = $WmiResult.IsHardRebootPending -or $WmiResult.IsRebootPending 540 | } 541 | 542 | return $RebootPending -or $RebootRequired -or $WmiSaisRebootPending #-or $PendingFileRename 543 | } 544 | } 545 | function Write-Conditional { 546 | [CmdLetBinding()] 547 | param( 548 | [object[]]$Text 549 | ) 550 | Process { 551 | if (!$Verbose.IsPresent) { 552 | Write-Verbose ($Text -join "`n") 553 | } else { 554 | Write-Debug ($Text -join "`n") 555 | } 556 | } 557 | } 558 | # Install-SecDrv : Unable to find type [System.ServiceProcess.ServiceStartMode] 559 | if ($false) { 560 | Write-Host "enum SecDrvServiceStartMode {" 561 | Write-Host "`tAutomatic = $([int][System.ServiceProcess.ServiceStartMode]::Automatic)" 562 | Write-Host "`tDisabled = $([int][System.ServiceProcess.ServiceStartMode]::Disabled)" 563 | Write-Host "`tManual = $([int][System.ServiceProcess.ServiceStartMode]::Manual)" 564 | Write-Host "}" 565 | } 566 | enum SecDrvServiceStartMode { 567 | Automatic = 2 568 | Disabled = 4 569 | Manual = 3 570 | } 571 | <# 572 | .NOTES 573 | Copyright (C) Eric Jonker. All rights reserved. 574 | 575 | .SYNOPSIS 576 | Selectively performs actions necessary to install SECDRV.sys, 577 | depending on the switches specified and on the current state 578 | of the system. 579 | 580 | .DESCRIPTION 581 | This command is able to perform the actions necessary to install 582 | SECDRV.sys on a Windows 10 System. This involves the following steps 583 | - Install the Standalone SDK for Windows 10 (if neccessary) 584 | - Create a Catalog Definition File (.cdf) - a small text file 585 | - Generate a Publisher Certificate to digitally sign the driver (.cer) 586 | - Generate a Driver Catalog (.cat) which contains proof of the use 587 | of the certificate previously created to sign the exact driver file present. 588 | - Install the driver file (.sys) and the catalog (.cat) in Windows 589 | - Configure Windows to trust the certificate used to sign the driver 590 | - Turn on BOOTSIGNING test mode so the driver can be loaded 591 | No steps have been taken yet to supress the Windows Update which disables SECDRV. 592 | 593 | The publisher certificate (.cer) that this command creates, the 594 | catalog definition file (.cdf) and driver catalog file (.cat) 595 | will be created or generated in a folder on your machine and kept private. 596 | 597 | If you do not specify any switches, this will effectively mean 598 | 599 | Install-SecDrv -MakeCertificate -SignDriver -InstallDriver -SetBootSigningOn 600 | 601 | If you do not specify -ToolsPath and the Standalone SDK for Windows 10 602 | is not isntalled, the script will silently install the 603 | Windows Standalone SDK for Windows 10 from Windows Dev Center: 604 | https://dev.windows.com/en-us/downloads 605 | Only the single feature containing SIGNTOOL, MAKECERT and MAKECAT 606 | will be installed. No other features are required by this script. 607 | The SECDRV PowerShell module cannot cope with installations of the SDK 608 | in a non-default path. If the SDK is installed in a non-default path, 609 | use the -ToolsPath parameter to specify where the mentioned tools are. 610 | 611 | If you wish to add or remove features from the SDK, or download 612 | it to an offline location, please download the SDK manually. 613 | The applicable file is called sdksetup.exe and is located here 614 | https://go.microsoft.com/fwlink/p/?LinkId=619296 615 | 616 | .PARAMETER MakeCertificate 617 | A new certificate will be created, even if one already exists. 618 | A new certificate is created without specifying this switch, if none exists. 619 | Implies SignDriver. 620 | Exports the certificate and the private key to a folder on your machine. 621 | This will cause a prompt for a password to protect the private key while it is stored on disk. 622 | 623 | .PARAMETER SignDriver 624 | Writes a new Catalog Definition File (.cdf) 625 | Uses MAKECAT to create the Driver Catalog File (.cat), creating the digital signature for the driver in the process. 626 | Uses SIGNTOOL to sign the Driver Catalog File (.cat) using the most recently created certificate. 627 | 628 | .PARAMETER InstallDriver 629 | Imports the most recently created certificate into 630 | 1) Trusted Root Certification Authorities (Cert:\LocalMachine\Root) and 631 | 2) Trusted Publishers (Cert:\LocalMachine\TrustedPublishers) 632 | Copies the driver file to $env:WINDIR\system32\drivers. 633 | Also runs SIGNTOOL to install cq. register the Signed Driver Catalog File (.cat) with Windows. 634 | 635 | .PARAMETER SetBootSigningOn 636 | Runs BCDEDIT to set BOOTSIGNING ON for the current OS boot entry. 637 | BCDEDIT /set {current} BOOTSIGNING ON 638 | 639 | .PARAMETER VerifyDriver 640 | Runs SIGNTOOL to verify the driver signature in the local Driver Catalog File (.cat) 641 | against the certificates present in Trusted Root Certification Authorities 642 | (Cert:\LocalMachine\Root) and Trusted Publishers (Cert:\LocalMachine\TrustedPublishers) 643 | for the Driver File (.sys) present in the folder where this PowerShell Module is installed. 644 | 645 | .PARAMETER ToolsPath 646 | This script requires the MAKECAT, MAKECERT and SIGNTOOL executables in an 647 | architecture-specific subfolder (i.e. x64 or x86) of the default Standalone SDK for 648 | Windows 10 installation location (${env:ProgramFiles(x86)}\Windows Kits\10\bin). 649 | If for any reason you want to customize this behavior, you can specify 650 | a full path to the folder containing MAKECAT, MAKECERT and SIGNTOOL in this parameter. 651 | #> 652 | function Install-SecDrv { 653 | [CmdletBinding()] 654 | param( 655 | [Switch]$MakeCertificate, 656 | [Switch]$SignDriver, 657 | [Switch]$InstallDriver, 658 | [Switch]$SetBootSigningOn, 659 | [Switch]$VerifyDriver, 660 | [String]$ToolsPath, 661 | [SecDrvServiceStartMode]$ServiceStartMode = [SecDrvServiceStartMode]::Manual, 662 | [Switch]$Confirm 663 | ) 664 | DynamicParam {} 665 | Begin {} 666 | Process { 667 | $WhatIf = [Switch]::new($false) # not supported 668 | $CommonSplat = @{ 669 | WhatIf = $WhatIf.IsPresent 670 | Verbose = $Verbose.IsPresent 671 | Confirm = $Confirm.IsPresent 672 | } 673 | $DriverName = [SecDrvConstants]::DriverName 674 | $CurrentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent()) 675 | if(!$CurrentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) 676 | { 677 | throw "This command must be run as Administrator." 678 | } 679 | 680 | # If no switches are present, we'll be smart about what to do by figuring out what hasn't been done yet 681 | $All = !$MakeCertificate.IsPresent ` 682 | -and !$SignDriver.IsPresent ` 683 | -and !$InstallDriver.IsPresent ` 684 | -and !$SetBootSigningOn.IsPresent ` 685 | -and !$VerifyDriver.IsPresent 686 | 687 | $ActualToolsPath = $null 688 | try { 689 | $ActualToolsPath = Find-SecDrvToolsPath -Hint $ToolsPath # OK if $null 690 | } catch { 691 | if (!(Test-SecDrvWindowsSdk)) { 692 | Write-Warning "The $([SecDrvConstants]::SdkTitle) is not installed, or not up to date." 693 | Install-SecDrvWindowsSdk -Confirm:$($Confirm.IsPresent) 694 | if (!(Test-SecDrvWindowsSdk)) { 695 | throw [System.OperationCanceledException]::new("A previous action failed.") 696 | } 697 | $ActualToolsPath = Find-SecDrvToolsPath -Hint $ToolsPath # OK if $null 698 | } 699 | throw; 700 | } 701 | if ([string]::IsNullOrEmpty($ActualToolsPath)) { 702 | throw [System.IO.DirectoryNotFoundException]::new("No path could be found that contains the tools required by this module.") 703 | } 704 | $Suppress = Install-SecDrvWindowsUpdates 705 | 706 | if ((Test-RebootRequired) -or (Get-WURebootStatus -Silent)) { 707 | Write-Warning "A reboot is required and you may experience problems running this script if you do not reboot first." 708 | Write-Host "It is recommended that you reboot now, to make sure that all features used by this script are up-to-date." 709 | Write-Host "To continue installing $DriverName after you reboot, run Install-SecDrv again, in one of the following ways:" 710 | Write-Host "" 711 | Write-Host "1) Start a Command Prompt as Administrator" 712 | Write-Host "2) Go back to github and copy the command you ran to get here" 713 | Write-Host "3) Paste it in the Command Prompt and press Enter, to download the module again and run Install-SecDrv at the same time" 714 | Write-Host " --or--" 715 | Write-Host "1) Start a Command Prompt as Administrator" 716 | Write-Host "2) Starting PowerShell by typing ""PowerShell -ExecutionPolicy RemoteSigned"" and pressing Enter." 717 | Write-Host "3) Running Install-SecDrv by typing ""Install-SecDrv"" and pressing Enter." 718 | $RebootPrompt = [ChoiceDialog]::new( 719 | [SecDrvPrompt]::Yes, 720 | "Reboot?", 721 | "A reboot is required to be able to sign and install $DriverName. Do you want to reboot now?", @( 722 | [tuple]::Create("&Yes", "Reboot immediately. Recommended. Features used by this script may need them.", [SecDrvPrompt]::Yes), 723 | [tuple]::Create("&No", "Reboot later. Not recommended. You may be experiencing lots of red text.", [SecDrvPrompt]::No) 724 | )) 725 | $RebootAnswer = $RebootPrompt.Show() 726 | if ($RebootAnswer -eq [SecDrvPrompt]::Yes) { 727 | Write-Host "Initiating reboot..." 728 | Restart-Computer 729 | } else { 730 | Write-Host "The export of certificates may fail. If this happens, reboot." 731 | } 732 | } 733 | 734 | $Approved = Approve-SecDrvToolsPath -Path $ActualToolsPath # throws if not approved 735 | Write-Verbose "Tools will be used from '$ActualToolsPath'." 736 | $MakeCert = Join-Path -Path $ActualToolsPath -ChildPath "MakeCert.exe" 737 | $MakeCat = Join-Path -Path $ActualToolsPath -ChildPath "MakeCat.exe" 738 | $SignTool = Join-Path -Path $ActualToolsPath -ChildPath "SignTool.exe" 739 | 740 | $HostName = (Get-CimInstance -ClassName Win32_ComputerSystem).Name 741 | $DateTime = "{0:dd MMM yyyy HH:mm}" -f [System.DateTimeOffset]::Now.ToLocalTime() 742 | $CertificateBasicName = "Private Signing Certificate for $DriverName.sys on \\$HostName" 743 | $CertificateCommonName = "$CertificateBasicName (Created by \\$env:USERDOMAIN\$env:USERNAME on $DateTime)" 744 | $CertificateNameRegex = "CN=$CertificateBasicName" -replace "\\", "\\" -replace "\.", "\." # escape the regex with a regex replace 745 | $CertificateNameRegex = "^$CertificateNameRegex \(.*\)$" 746 | $PrivateFolder = [SecDrvConstants]::GetUserSecretFolder() 747 | Write-Verbose "Private certificate and related files will be stored in '$PrivateFolder'." 748 | 749 | if (!(Test-Path $PrivateFolder)) { mkdir $PrivateFolder } 750 | $DriverSys = Join-Path $PrivateFolder "$DriverName.sys" 751 | $DriverCer = Join-Path $PrivateFolder "$DriverName.cer" 752 | $DriverCat = Join-Path $PrivateFolder "$DriverName.cat" 753 | $DriverDef = Join-Path $PrivateFolder "$DriverName.cdf" 754 | $DriverOut = Join-Path $PrivateFolder "$Drivername.txt" 755 | 756 | $Certificate = dir Cert: -Recurse | where Subject -Match $CertificateNameRegex | sort NotBefore -Descending | select -First 1 757 | $CertificateFileExists = Test-Path -Path $DriverCer -PathType Leaf 758 | $CertificateDingExists = $Certificate -ne $null 759 | $CertificateExists = $CertificateFileExists -or $CertificateDingExists 760 | 761 | $CertificateIsNew = $MakeCertificate.IsPresent -or ((-not $CertificateExists) -and $All) 762 | if (!$CertificateIsNew) { 763 | Write-Verbose "A certificate exists: $($Certificate.Subject)" 764 | } else { 765 | Write-Host "Creating Publisher Certificate with subject ""$CertificateCommonName""." 766 | $MakeCertArgs = 767 | "-r", # Root Certificate / Self Signed Certificate 768 | #"-pe", # Private Key is Exportable - not needed 769 | "-sr", "LocalMachine", # certificate store location . Default to 'CurrentUser' 770 | "-ss", "Root", # Subject's certificate store name that stores the output certificate (dir cert:\LocalMachine) 771 | "-n", "CN=$CertificateCommonName", # Certificate subject X500 name (eg: CN=Fred Dews) 772 | $DriverCer # outputCertificateFile 773 | Invoke-CommandLine -Command $MakeCert -Arguments $MakeCertArgs @CommonSplat 774 | $Certificate = dir Cert: -Recurse | where Subject -Match $CertificateNameRegex | sort NotBefore -Descending | select -First 1 775 | } 776 | # Deploy 777 | <# 778 | To test-sign a catalog file or embed a signature in a driver file, 779 | the MakeCert test certificate can be in the Personal certificate store ("my" store), 780 | or some other custom certificate store, of the local computer that signs the software. 781 | 782 | But it must be in Cert:\LocalMachine\Root *and* in Cert:\LocalMachine\TrustedPublisher to use it. 783 | #> 784 | $DriverSignatureOK = $CertificateIsNew 785 | if (-not $DriverSignatureOK) { 786 | Write-Verbose "Verifying the digital certificate using catalog '$DriverCat' for driver file '$DriverSys'." 787 | $SignToolVerifyOutput = & $SignTool verify /v /pa /c $DriverCat $DriverSys 788 | $SignToolStatus = $LASTEXITCODE 789 | Write-Conditional $SignToolVerifyOutput -Verbose:$($Verbose.IsPresent) 790 | $DriverSignatureOK = $SignToolStatus -eq 0 791 | if ($DriverSignatureOK) { 792 | Write-Verbose "The driver signature is OK." 793 | } else { 794 | Write-Verbose "The driver signature is not OK." 795 | } 796 | } 797 | 798 | $DriverCerExists = Test-Path $DriverCer 799 | $DriverDefExists = Test-Path $DriverDef 800 | $DriverCatExists = Test-Path $DriverCat 801 | $DriverIsSigned = $DriverCerExists -and $DriverDefExists -and $DriverCatExists -and $DriverSignatureOK 802 | if ($CertificateIsNew -or $SignDriver.IsPresent -or ($All -and (!$DriverIsSigned))) { 803 | Push-Location -Path $PrivateFolder 804 | try { 805 | # Using MakeCat to Create a Catalog File 806 | # https://msdn.microsoft.com/en-us/library/windows/hardware/ff553620%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 807 | $DriverCatalogDefinition = @( 808 | "[CatalogHeader]", 809 | "Name=$DriverName.cat", 810 | "PublicVersion=0x1", 811 | "EncodingType=0x00010001", 812 | "CATATTR1=0x10010001:OSAttr:2:6.0", 813 | "[CatalogFiles]", 814 | "$DriverName=$DriverName.sys" 815 | ) -join "`r`n" 816 | # SECDRV.cdf 817 | Write-Verbose "Creating a Driver Catalog Definition File (.cdf)" 818 | Set-Content -Value $DriverCatalogDefinition -Path $DriverDef -Encoding Ascii -Force 819 | # SECDRV.sys 820 | # SECDRV.sys is deployed there already # copy "$RootFolder\$DriverName.sys" $PrivateFolder 821 | # SECDRV.cat 822 | Write-Verbose "Creating the Driver Catalog File (.cat)" 823 | $MakeCatArgs = 824 | "-v", # Verbose for Write-Verbose 825 | "-o", $DriverOut, # output filename/hash pairs to specified file 826 | "-r", # return fail even on recoverable errors 827 | $DriverDef # CDF_filename 828 | Invoke-CommandLine -Command $MakeCat -Arguments 829 | #$MakeCatOutput = & $MakeCat -v -o $DriverOut $DriverDef # MakeCat must be run in the folder where the driver file is, hence Push-Location 830 | #Write-Conditional $MakeCatOutput -Verbose:$($Verbose.IsPresent) 831 | 832 | try { 833 | # Sign the driver - doesn't work with [SecureString] obviously 834 | $TimeStampUrl = [SecDrvConstants]::VerisignTimestampServerUrl 835 | Write-Verbose "Using timestamp server $TimestampUrl" 836 | Write-Verbose "Signing the Driver Catalog File (.cat)" 837 | $SignToolArgs = 838 | "sign", # sign a file 839 | "/v", "/debug", # Verbose, Debug => Write-Verbose 840 | #"/f", $DriverCer, # not using the .cer - it doesn't have a private key 841 | "/sm", "/s", "Root", # Store Machine, Root=Trusted Root CA's: Cert:\LocalMachine\Root 842 | "/sha1", $($Certificate.Thumbprint), # Cert:\LocalMachine\Root\53FA801... i.e. the certificate previously generated 843 | "/t", $TimeStampUrl, # Get a timestamp using the Verisign URL 844 | $DriverCat # .\SECDRV.cat stores the result of the signing operation 845 | Invoke-CommandLine -Command $SignTool -Arguments $SignToolArgs @CommonSplat 846 | #$SignToolOutput = & $SignTool sign /v /debug /f $DriverCer /sm /s Root /sha1 $($Certificate.Thumbprint) /t $TimeStampUrl $DriverCat 847 | #Write-Debug ($SignToolOutput -join "`n") 848 | } finally { 849 | $PasswordClearText = $null 850 | } 851 | } finally { 852 | Pop-Location 853 | } 854 | } 855 | <# 856 | To verify a test signature, the corresponding test certificate must be installed in the 857 | Trusted Root Certification Authorities certificate store of the local computer that you use 858 | to verify the signature. 859 | #> 860 | $SecDrvInstalledPath = "$env:windir\system32\drivers\$DriverName.sys" 861 | $DriverSysIsInstalled = Test-Path $SecDrvInstalledPath 862 | if (!$InstallDriver.IsPresent -or $CertificateIsNew -or $VerifyDriver.IsPresent) { 863 | Write-Verbose "Verifying the driver signature to determine whether reinstallation is required." 864 | $SignToolVerifyOutput = & $SignTool verify /v /pa /c $DriverCat $DriverSys 865 | $SignToolStatus = $LASTEXITCODE 866 | Write-Conditional $SignToolVerifyOutput -Verbose:$($Verbose.IsPresent -or $VerifyDriver.IsPresent) 867 | $DriverSignatureOK = $SignToolStatus -eq 0 868 | if ($DriverSignatureOK) { 869 | Write-Verbose "The driver signature is OK." 870 | } else { 871 | Write-Verbose "The driver signature is not OK." 872 | } 873 | } 874 | 875 | $DriverReinstalled = $false 876 | if ($InstallDriver.IsPresent -or ($All -and !$DriverInstalled)) { 877 | $CertificateLocations = @("Cert:\LocalMachine\Root", "Cert:\LocalMachine\TrustedPublisher") 878 | foreach ($CertificateLocation in $CertificateLocations) { # $CertificateLocation = $CertificateLocations[1] 879 | if ((dir "$CertificateLocation\$($Certificate.Thumbprint)" -ErrorAction SilentlyContinue).Count -eq 0) { 880 | Write-Verbose "Importing certificate to $CertificateLocation" 881 | Import-PfxCertificate -Exportable -Password $Password -CertStoreLocation $CertificateLocation -FilePath $DriverCer -Verbose:$($Verbose.IsPresent) 882 | } 883 | } 884 | Write-Verbose "Copying '$DriverSys' to '$SecDrvInstalledPath'." 885 | copy $DriverSys $SecDrvInstalledPath -Force 886 | Write-Verbose "Installing Driver Catalog '$DriverCat'." 887 | # Installing a Catalog File by using SignTool 888 | # https://msdn.microsoft.com/en-us/library/windows/hardware/ff547579(v=vs.85).aspx 889 | $SuppressOutput = & $SignTool catdb /v /u $DriverCat 890 | Write-Conditional $SuppressOutput -Verbose:$($Verbose.IsPresent) 891 | $DriverReinstalled = $true 892 | 893 | # Set the service start mode 894 | $DriverRegistryPath = "HKLM:\SYSTEM\CurrentControlSet\Services\secdrv" 895 | if (!(Test-Path $DriverRegistryPath -ErrorAction SilentlyContinue)) { 896 | $Suppress = New-Item -Path $DriverRegistryPath -ItemType Directory 897 | } 898 | Set-ItemProperty -Path $DriverRegistryPath -Name Start -Value ([int]$ServiceStartMode) 899 | if ($false) { # Test Code 900 | Remove-ItemProperty -Path $DriverRegistryPath -Name Start 901 | rmdir -Path $DriverRegistryPath -Recurse -Force # Don't do this at home 902 | Get-ItemProperty -Path $DriverRegistryPath -ErrorAction SilentlyContinue 903 | } 904 | } 905 | if ($SetBootSigningOn.IsPresent -or $All) { 906 | Write-Verbose "Running BCDEDIT /set {current} TESTSIGNING ON" 907 | $SuppressOutput = & bcdedit /set "{current}" testsigning on 908 | Write-Conditional $SuppressOutput -Verbose:$($Verbose.IsPresent) 909 | if ($LASTEXITCODE -ne 0) { 910 | throw $SuppressOutput -join "`n" 911 | } 912 | } 913 | if ($Confirm.IsPresent -and ($DriverReinstalled -or $SetBootSigningOn.IsPresent)) { 914 | Write-Warning "A reboot is required, to complete installation of the driver. This is required before SECDRV can be enabled and started." 915 | Write-Host "Once the system has rebooted, SECDRV will be installed, but disabled." 916 | Write-Host "Run " 917 | Write-Host "`tPowerShell -ExecutionPolicy RemoteSigned -Command { Enable-SecDrv; Start-SecDrv; }" 918 | Write-Host "to enable and start it." 919 | Write-Host "Run " 920 | Write-Host "`tPowerShell -ExecutionPolicy RemoteSigned -Command { Disable-SecDrv }" 921 | Write-Host "to stop and disable it." 922 | Write-Host "You can add these snippets to a script you can run from a shortcut or the StartMenu." 923 | $RebootPrompt = [ChoiceDialog]::new([SecDrvPrompt]::Yes, "Reboot?", "A reboot is required to enable $DriverName. Do you want to reboot now?", @( 924 | [tuple]::Create("&Yes", "Reboot immediately. You must do this immediately to enable $DriverName ASAP.", [SecDrvPrompt]::Yes), 925 | [tuple]::Create("&No", "Reboot later. There is no pressing need to do this at any particular moment.", [SecDrvPrompt]::No) 926 | )) 927 | $RebootAnswer = $RebootPrompt.Show() 928 | if ($RebootAnswer -eq [SecDrvPrompt]::Yes) { 929 | Write-Host "The system will reboot in 10 seconds." 930 | Start-Sleep -Seconds 10 931 | Restart-Computer 932 | } 933 | } 934 | 935 | } 936 | End { } 937 | } 938 | function Enable-SecDrv { 939 | [CmdLetBinding()] 940 | param( 941 | [Switch]$AutoStart 942 | ) 943 | Process { 944 | if ($AutoStart.IsPresent) { 945 | & cmd /c sc config secdrv start= auto 946 | } else { 947 | & cmd /c sc config secdrv start= demand 948 | } 949 | } 950 | } 951 | function Disable-SecDrv { 952 | [CmdLetBinding()] 953 | param() 954 | Process { 955 | & cmd /c sc stop secdrv 956 | & cmd /c sc config secdrv start= disabled 957 | } 958 | } 959 | function Start-SecDrv { 960 | [CmdLetBinding()] 961 | param() 962 | Process { & cmd /c sc start secdrv } 963 | } 964 | function Stop-SecDrv { 965 | [CmdLetBinding()] 966 | param() 967 | Process { & cmd /c sc stop secdrv } 968 | } 969 | --------------------------------------------------------------------------------