├── .gitignore ├── LICENSE ├── README.md ├── Request-Certificate.ps1 └── StorageReplica └── New-StretchedFileCluster.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 J0F3 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerShell 2 | Personal PowerShell Script collection 3 | 4 | ## Content: 5 | 6 | * **Request-Certificate.ps1** 7 | Request certificates from a Enterprise CA and export it optionally directly to a .pfx file. 8 | The Script was initally publisehd on the TechNet Script Center until its shutdown. 9 | Now the Script can be found on the [PowerShell Gallery](https://www.powershellgallery.com/packages/Request-Certificate/1.5.0) and here on GitHub. 10 | 11 | * **StorageReplica/New-StretchedFileCluster.ps1:** 12 | Simple Script to build a virtual two node, stretched, general purpose file server cluster with storage replica 13 | -------------------------------------------------------------------------------- /Request-Certificate.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | 3 | .VERSION 1.6.0 4 | 5 | .GUID eb791b3e-fbbe-4685-8c92-5eb0f05688b6 6 | 7 | .AUTHOR Jonas Feller c/o J0F3 8 | 9 | .COMPANYNAME jfe.cloud 10 | 11 | .COPYRIGHT (c) 2020 Jonas Feller. All rights reserved. 12 | 13 | .TAGS PSEdition_Desktop Certificate ActiveDirectory ActiveDirectoryCertificateServices Security 14 | 15 | .LICENSEURI 16 | 17 | .PROJECTURI https://github.com/J0F3/PowerShell 18 | 19 | .ICONURI 20 | 21 | .EXTERNALMODULEDEPENDENCIES 22 | 23 | .REQUIREDSCRIPTS 24 | 25 | .EXTERNALSCRIPTDEPENDENCIES 26 | 27 | .RELEASENOTES 28 | * Fix SAN request by @bruckect in https://github.com/J0F3/PowerShell/pull/23 29 | * Add option for friendly name by @jmcook1 in https://github.com/J0F3/PowerShell/pull/26 30 | #> 31 | 32 | <# 33 | .SYNOPSIS 34 | Requests a certificate from a Windows CA 35 | 36 | .DESCRIPTION 37 | Requests a certificates with the specified subject name from am Windows CA and saves the resulting certificate with the private key in the local computer store. 38 | 39 | You must specify at least the CN for the subject name. 40 | 41 | With the SAN parameter you can also specify values for subject alternative name to request a SAN certificate. 42 | The CA must support this type of certificate otherwise the request will fail. 43 | 44 | With the Export parameter it's also possible to export the requested certificate (with private key) directly to a .pfx file instead of storing it in the local computer store. 45 | 46 | You can also use the Import-CSV cmdlet with Request-Certificate.ps1 to request multiple certificates. 47 | To do this, use the Import-CSV cmdlet to create custom objects from a comma-separated value (CSV) file that contains a list of object properties (such as CN, SAN etc. ). Then pass these objects through the pipeline to Request-Certificate.ps1 to request the certificates. 48 | 49 | .PARAMETER CN 50 | Specifies the common name for the subject of the certificate(s). 51 | Mostly its the FQDN of a website or service. 52 | e.g. test.jofe.ch 53 | 54 | .PARAMETER SAN 55 | Specifies a comma separated list of subject alternate names (FQDNs) for the certificate 56 | The syntax is {tag}={value}. 57 | Valid tags are: email, upn, dns, guid, url, ipaddress, oid 58 | e.g. dns=test.jofe.ch,email=jfeller@jofe.ch 59 | 60 | .PARAMETER TemplateName 61 | Specifies the name for the temple of the CA to issue the certificate(s). 62 | The default value is "WebServer". 63 | 64 | .PARAMETER KeyLength 65 | Specifies the key length in Bit for the certificate. 66 | Possible Values: 1024,2048,3072,4096,15360 67 | Default Value: 2048 68 | 69 | .PARAMETER CAName 70 | Specifies the name of the CA to send the request to in the format FQDN\CAName 71 | If the CAName is not specified, then the directory is queried for a list of enterprise CAs. 72 | If more than one is returned the user is prompted to choose an enterprise CA from the local Active Directory. 73 | 74 | .PARAMETER Country 75 | Specifies two letter for the optional country value in the subject of the certificate(s). 76 | e.g. CH 77 | 78 | .PARAMETER State 79 | Specifies the optional state value in the subject of the certificate(s). 80 | e.g. Berne 81 | 82 | .PARAMETER City 83 | Specifies the optional city value in the subject of the certificate(s). 84 | e.g. Berne 85 | 86 | .PARAMETER Organisation 87 | Specifies the optional organisation value in the subject of the certificate(s). 88 | e.g. jofe.ch 89 | 90 | .PARAMETER Department 91 | Specifies the optional department value in the subject of the certificate(s). 92 | e.g. IT 93 | 94 | .PARAMETER FriendlyName 95 | Specifies the optional friendly name value of the certificate(s). 96 | e.g. "[CA Issued by] My Certificate" 97 | 98 | .PARAMETER AddCNinSAN 99 | Specifies the CN will be added to the SAN list if not already provided. This ensures compatibility with 100 | modern browsers. 101 | 102 | .PARAMETER Export 103 | Exports the certificate and private key to a pfx file instead of installing it in the local computer store. 104 | By default the certificate will be installed in the local computer store. 105 | 106 | .PARAMETER ExportPath 107 | Path to which the pfx file should be saved when -Export is specified. 108 | 109 | .PARAMETER Password 110 | Specify the Password (as plain String or SecureString) used on the export. 111 | 112 | .INPUTS 113 | System.String 114 | Common name for the subject, SAN , Country, State etc. of the certificate(s) as a string 115 | 116 | .OUTPUTS 117 | None. Request-Certificate.ps1 does not generate any output. 118 | 119 | .EXAMPLE 120 | C:\PS> .\Request-Certificate.ps1 121 | 122 | Description 123 | ----------- 124 | This command requests a certificate form the enterprise CA in the local Active Directory. 125 | The user will be asked for the value for the CN of the certificate. 126 | 127 | .EXAMPLE 128 | C:\PS> .\Request-Certificate.ps1 -CAName "testsrv.test.ch\Test CA" 129 | 130 | Description 131 | ----------- 132 | This command requests a certificate form the CA testsrv.test.ch\Test CA. 133 | The user will be asked for the value for the CN of the certificate. 134 | 135 | .EXAMPLE 136 | C:\PS> .\Request-Certificate.ps1 -CN "webserver.test.ch" -CAName "testsrv.test.ch\Test CA" -TemplateName "Webservercert" 137 | 138 | Description 139 | ----------- 140 | This command requests a certificate form the CA testsrv.test.ch\Test CA with the certificate template "Webservercert" 141 | and a CN of webserver.test.ch 142 | The user will be asked for the value for the SAN of the certificate. 143 | 144 | 145 | .EXAMPLE 146 | Get-Content .\certs.txt | .\Request-Certificate.ps1 -Export 147 | 148 | Description 149 | ----------- 150 | Gets common names from the file certs.txt and request for each a certificate. 151 | Each certificate will then be saved withe the private key in a .pfx file. 152 | 153 | .EXAMPLE 154 | C:\PS> .\Request-Certificate.ps1 -CN "webserver.test.ch" -SAN "DNS=webserver.test.ch,DNS=srvweb.test.local" 155 | 156 | Description 157 | ----------- 158 | This command requests a certificate with a CN of webserver.test.ch and subject alternative names (SANs) 159 | The SANs of the certificate are the DNS names webserver.test.ch and srvweb.test.local. 160 | 161 | .EXAMPLE 162 | C:\PS> Import-Csv .\sancertificates.csv -UseCulture | .\Request-Certificate.ps1 -verbose -Export -CAName "testsrv.test.ch\Test CA" 163 | 164 | Description 165 | ----------- 166 | This example requests multiple SAN certificates from the "Test CA" CA running on the server "testsrv.test.ch". 167 | The first command creates custom objects from a comma-separated value (CSV) file thats contains a list of object properties. The objects are then passed through the pipeline to Request-Certificate.ps1 to request the certificates form the "J0F3's Issuing CA" CA. 168 | Each certificate will then be saved with the private key in a .pfx file. 169 | 170 | The CSV file look something like this: 171 | CN;SAN 172 | test1.test.ch;DNS=test1san1.test.ch,DNS=test1san2.test.ch 173 | test2.test.ch;DNS=test2san1.test.ch,DNS=test2san2.test.ch 174 | test3.test.ch;DNS=test3san1.test.ch,DNS=test3san2.test.ch 175 | 176 | .NOTES 177 | 178 | Version : 1.5.0 179 | Changes : 180 | Kudos to jbpaux for contributing improvements and fixes on GitHub! 181 | - New switch parameter "AddCNinSAN" to automatically populate SAN with the CN. (PR #15) 182 | - Fixes an issue with naming ot the file when a wildcard (*) certificate is requested. (PR #14) 183 | - Improved outputs when requesting SAN certificate. (PR #13) 184 | - Fixes an issue where the request .inf file was not correctly formated when requesting a SAN certificate. (Kudos to smanross, PR #5) 185 | 186 | Version : 1.4, 01/31/2019 187 | Changes : 188 | Thanks to David Allsopp c/o dra27 on GitHub 189 | - Better default for CAName so it is not needed when only one CA is available. 190 | - ProviderName specified in the request inf file 191 | 192 | Version : 1.3, 10/20/2018 193 | Changes : 194 | - Improvements in temp file handling 195 | - Additional parameter to specify the export path for pfx file 196 | - Requesting SAN certs with Extensions instead of Attributes 197 | 198 | File Name : Request-Certificate.ps1 199 | Requires : PowerShell V2 or higher 200 | 201 | .LINK 202 | © Jonas Feller c/o J0F3, 2019 203 | www.jfe.cloud 204 | 205 | #> 206 | 207 | [CmdletBinding(DefaultParametersetname="NoExport")] 208 | Param( 209 | [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 210 | [string]$CN, 211 | [Parameter(Mandatory = $False, ValueFromPipelineByPropertyName = $True)] 212 | [string[]]$SAN, 213 | [Parameter(Mandatory = $False, ValueFromPipelineByPropertyName = $True)] 214 | [String]$TemplateName = "WebServer", 215 | [Parameter(Mandatory = $False, ValueFromPipelineByPropertyName = $True)] 216 | [ValidateSet(1024,2048,3072,4096,15360)] 217 | [int]$keyLength = 2048, 218 | [Parameter(Mandatory = $False, ValueFromPipelineByPropertyName = $True)] 219 | [string]$CAName, 220 | [Parameter(Mandatory = $False, ValueFromPipelineByPropertyName = $True)] 221 | [string]$Country, 222 | [Parameter(Mandatory = $False, ValueFromPipelineByPropertyName = $True)] 223 | [string]$State, 224 | [Parameter(Mandatory = $False, ValueFromPipelineByPropertyName = $True)] 225 | [string]$City, 226 | [Parameter(Mandatory = $False, ValueFromPipelineByPropertyName = $True)] 227 | [string]$Organisation, 228 | [Parameter(Mandatory = $False, ValueFromPipelineByPropertyName = $True)] 229 | [string]$Department, 230 | [Parameter(Mandatory = $False, ValueFromPipelineByPropertyName = $True)] 231 | [string]$FriendlyName = "" , 232 | [Parameter(Mandatory = $False, ValueFromPipelineByPropertyName = $True)] 233 | [switch]$AddCNinSAN, 234 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $True, ParameterSetName='Export')] 235 | [switch]$Export, 236 | [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $True, ParameterSetName='Export')] 237 | [ValidateScript( {Resolve-Path -Path $_})] 238 | [string]$ExportPath, 239 | [Parameter(Mandatory = $False, ValueFromPipelineByPropertyName = $True, ParameterSetName='Export')] 240 | [ValidateScript( {$_.getType().name -eq "SecureString" -or $_.getType().name -eq "String"})] 241 | $Password 242 | 243 | ) 244 | BEGIN { 245 | #internal function to do some cleanup 246 | function Remove-ReqTempfiles() { 247 | param( 248 | [String[]]$tempfiles 249 | ) 250 | Write-Verbose "Cleanup temp files..." 251 | Remove-Item -Path $tempfiles -Force -ErrorAction SilentlyContinue 252 | } 253 | 254 | function Remove-ReqFromStore { 255 | param( 256 | [String]$CN 257 | ) 258 | Write-Verbose "Remove pending certificate request form cert store..." 259 | 260 | #delete pending request (if a request exists for the CN) 261 | $certstore = new-object system.security.cryptography.x509certificates.x509Store('REQUEST', 'LocalMachine') 262 | $certstore.Open('ReadWrite') 263 | foreach ($certreq in $($certstore.Certificates)) { 264 | if ($certreq.Subject -eq "CN=$CN") { 265 | $certstore.Remove($certreq) 266 | } 267 | } 268 | $certstore.close() 269 | } 270 | } 271 | 272 | PROCESS { 273 | #disable debug confirmation messages 274 | if ($PSBoundParameters['Debug']) {$DebugPreference = "Continue"} 275 | 276 | Write-Verbose "Generating request inf file" 277 | $file = @" 278 | [NewRequest] 279 | Subject = "CN=$CN,c=$Country, s=$State, l=$City, o=$Organisation, ou=$Department" 280 | MachineKeySet = TRUE 281 | KeyLength = $KeyLength 282 | KeySpec=1 283 | Exportable = TRUE 284 | RequestType = PKCS10 285 | ProviderName = "Microsoft Enhanced Cryptographic Provider v1.0" 286 | FriendlyName = "$FriendlyName" 287 | [RequestAttributes] 288 | CertificateTemplate = "$TemplateName" 289 | "@ 290 | 291 | 292 | #check if SAN certificate is requested 293 | if ($PSBoundParameters.ContainsKey('SAN')) { 294 | #each SAN must be a array element 295 | #if the array has ony one element then split it on the commas. 296 | if (($SAN).count -eq 1) { 297 | $SAN = @($SAN -split ',') 298 | } 299 | } 300 | 301 | if ($AddCNinSAN) { 302 | $SAN = @("DNS=$CN") + $SAN #Add CN as first SAN entry 303 | } 304 | 305 | # Remove Potential duplicates (if CN was already provided in SAN list) 306 | $SAN = $SAN | Select-Object -Unique 307 | 308 | 309 | if ($SAN.Count -gt 0) { 310 | 311 | Write-Host "Requesting SAN certificate with subject $CN and SAN: $($SAN -join ',')" -ForegroundColor Green 312 | Write-Debug "Parameter values: CN = $CN, TemplateName = $TemplateName, CAName = $CAName, SAN = $($SAN -join ' ')" 313 | 314 | Write-Verbose "A value for the SAN is specified. Requesting a SAN certificate." 315 | Write-Debug "Add Extension for SAN to the inf file..." 316 | $file += 317 | @' 318 | 319 | [Extensions] 320 | ; If your client operating system is Windows Server 2008, Windows Server 2008 R2, Windows Vista, or Windows 7 321 | ; SANs can be included in the Extensions section by using the following text format. Note 2.5.29.17 is the OID for a SAN extension. 322 | 323 | 2.5.29.17 = "{text}" 324 | 325 | '@ 326 | 327 | foreach ($an in $SAN) { 328 | $file += "_continue_ = `"$($an)&`"`n" 329 | } 330 | } 331 | else { 332 | Write-Host "Requesting certificate with subject $CN" -ForegroundColor Green 333 | Write-Debug "Parameter values: CN = $CN, TemplateName = $TemplateName, CAName = $CAName" 334 | } 335 | 336 | Write-Debug "Inf-File: $file" 337 | 338 | try { 339 | #create temp files 340 | $inf = [System.IO.Path]::GetTempFileName() 341 | $req = [System.IO.Path]::GetTempFileName() 342 | $filename = $CN -replace "^\*","wildcard" 343 | $cer = Join-Path -Path $env:TEMP -ChildPath "$filename.cer" 344 | $rsp = Join-Path -Path $env:TEMP -ChildPath "$filename.rsp" 345 | 346 | Remove-ReqTempfiles -tempfiles $inf, $req, $cer, $rsp 347 | 348 | #write the file to debug logs (if debug enabled) 349 | Write-Debug $file 350 | #create new request inf file 351 | Set-Content -Path $inf -Value $file 352 | 353 | #show inf file if -verbose is used 354 | Get-Content -Path $inf | Write-Verbose 355 | 356 | Write-Verbose "generate .req file with certreq.exe" 357 | Invoke-Expression -Command "certreq -new `"$inf`" `"$req`"" 358 | if (!($LastExitCode -eq 0)) { 359 | throw "certreq -new command failed" 360 | } 361 | 362 | write-verbose "Sending certificate request to CA" 363 | Write-Debug "CAName = $CAName" 364 | 365 | if (!$PSBoundParameters.ContainsKey('CAName')) { 366 | $rootDSE = [System.DirectoryServices.DirectoryEntry]'LDAP://RootDSE' 367 | $searchBase = [System.DirectoryServices.DirectoryEntry]"LDAP://$($rootDSE.configurationNamingContext)" 368 | $CAs = [System.DirectoryServices.DirectorySearcher]::new($searchBase,'objectClass=pKIEnrollmentService').FindAll() 369 | 370 | if($CAs.Count -eq 1){ 371 | $CAName = "$($CAs[0].Properties.dnshostname)\$($CAs[0].Properties.cn)" 372 | } 373 | else { 374 | $CAName = "" 375 | } 376 | } 377 | 378 | if (!$CAName -eq "") { 379 | $CAName = " -config `"$CAName`"" 380 | } 381 | 382 | Write-Debug "certreq -submit$CAName `"$req`" `"$cer`"" 383 | Invoke-Expression -Command "certreq -submit$CAName `"$req`" `"$cer`"" 384 | 385 | if (!($LastExitCode -eq 0)) { 386 | throw "certreq -submit command failed" 387 | } 388 | Write-Debug "request was successful. Result was saved to `"$cer`"" 389 | 390 | write-verbose "retrieve and install the certificate" 391 | Invoke-Expression -Command "certreq -accept `"$cer`"" 392 | 393 | if (!($LastExitCode -eq 0)) { 394 | throw "certreq -accept command failed" 395 | } 396 | 397 | if (($LastExitCode -eq 0) -and ($? -eq $true)) { 398 | Write-Host "Certificate request successfully finished!" -ForegroundColor Green 399 | 400 | } 401 | else { 402 | throw "Request failed with unknown error. Try with -verbose -debug parameter" 403 | } 404 | 405 | 406 | if ($export) { 407 | Write-Debug "export parameter is set. => export certificate" 408 | Write-Verbose "exporting certificate and private key" 409 | $cert = Get-Childitem "cert:\LocalMachine\My" | where-object {$_.Thumbprint -eq (New-Object System.Security.Cryptography.X509Certificates.X509Certificate2((Get-Item $cer).FullName, "")).Thumbprint} 410 | Write-Debug "Certificate found in computer store: $cert" 411 | 412 | #create a pfx export as a byte array 413 | if($Password) { 414 | Write-Debug "Exporting with password" 415 | $certbytes = $cert.export([System.Security.Cryptography.X509Certificates.X509ContentType]::pfx, $Password) 416 | } else { 417 | Write-Debug "Exporting without password" 418 | $certbytes = $cert.export([System.Security.Cryptography.X509Certificates.X509ContentType]::pfx) 419 | } 420 | 421 | 422 | #write pfx file 423 | if ($PSBoundParameters.ContainsKey('ExportPath')) { 424 | $pfxPath = Join-Path -Path (Resolve-Path -Path $ExportPath) -ChildPath "$filename.pfx" 425 | } 426 | else { 427 | $pfxPath = ".\$filename.pfx" 428 | } 429 | $certbytes | Set-Content -Encoding Byte -Path $pfxPath -ea Stop 430 | Write-Host "Certificate successfully exported to `"$pfxPath`"!" -ForegroundColor Green 431 | 432 | Write-Verbose "deleting exported certificate from computer store" 433 | # delete certificate from computer store 434 | $certstore = new-object system.security.cryptography.x509certificates.x509Store('My', 'LocalMachine') 435 | $certstore.Open('ReadWrite') 436 | $certstore.Remove($cert) 437 | $certstore.close() 438 | 439 | } 440 | else { 441 | Write-Debug "export parameter is not set. => script finished" 442 | Write-Host "The certificate with the subject $CN is now installed in the computer store !" -ForegroundColor Green 443 | } 444 | } 445 | catch { 446 | #show error message (non terminating error so that the rest of the pipeline input get processed) 447 | Write-Error $_ 448 | } 449 | finally { 450 | #tempfiles and request cleanup 451 | Remove-ReqTempfiles -tempfiles $inf, $req, $cer, $rsp 452 | Remove-ReqFromStore -CN $CN 453 | } 454 | } 455 | 456 | END { 457 | Remove-ReqTempfiles -tempfiles $inf, $req, $cer, $rsp 458 | } 459 | -------------------------------------------------------------------------------- /StorageReplica/New-StretchedFileCluster.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Builds a virtual two node, stretched, general purpose file server cluster with storage replica 4 | .DESCRIPTION 5 | Script with all steps to build a stretched cluster, based on two VMs, with a general purpose file server cluster role. 6 | The underneath storage is not shared storage but local storage. The volume inside the VMs will then replicated with Storage Replica 7 | 8 | The script is intended to run on a third computer like a management server where the Failover Cluster, Hyper-V and Storage Replica RSAT tools are installed. 9 | 10 | .PARAMETER Server 11 | Computer names of the two cluster nodes 12 | 13 | .PARAMETER Locations 14 | Name of the two sites. Will be used to name the sites in cluster fault domains configuration 15 | 16 | .PARAMETER ClusterName 17 | Name of the failover cluster 18 | 19 | .PARAMETER ClusterIP 20 | IP Address of the failover cluster 21 | 22 | .PARAMETER CloudWitnessAccount 23 | Name of the Azure storage account which should be used as cloud witness (empty if cloud witness should not be configure) 24 | 25 | .PARAMETER CloudWitnessAccessKey 26 | Primary access Key of the Azure storage account which should be used as cloud witness (empty if cloud witness should not be configure) 27 | 28 | .PARAMETER WitnessShare 29 | Full UNC path to the share which should be used as witness file share (only if cloud witness can/should not be used) 30 | 31 | .PARAMETER HyperVHosts 32 | Name of the Hyper-V hosts on which the two VMs are currently running (to attach the need VHDX to the VMs) 33 | 34 | .PARAMETER DataVHDXBasePaths 35 | Paths to the location where the VHDX files for the Data disks should be created (SOFS Share,CSV Volume, or local volume of the Hyper-V host) 36 | 37 | .PARAMETER LogVHDXBasePaths 38 | Paths to the location where the VHDX files for the Log disks should be created (SOFS Share,CSV Volume, or local volume of the Hyper-V host) 39 | 40 | .PARAMETER LogDiskSize 41 | Size fo the Log disk VHDX files in bytes (PowerShell will convert GB automatically in the corresponding bytes value) 42 | 43 | .PARAMETER DataDiskSize 44 | Size fo the Data disk VHDX files in bytes (PowerShell will convert GB automatically in the corresponding bytes value) 45 | 46 | .PARAMETER LogDiskLetter 47 | Drive Letter of the Log disk volume 48 | 49 | .PARAMETER DataDiskLetter 50 | Drive Letter of the Data disk volume 51 | 52 | .PARAMETER FSClusterName 53 | Name of the File Server Cluster Role 54 | 55 | .PARAMETER FSClusterIP 56 | IP Address of the File Server Cluster Role 57 | 58 | .PARAMETER ShareNames 59 | An Array with Hashtable(s) with file share which should be created on the Data volume. (One Hashtable per File Share) 60 | The Hashtable must contains to keys "Sharename" and "ContinuouslyAvailable" 61 | Example: @{ShareName = "TestShare";ContinuouslyAvailable=$true} 62 | 63 | .PARAMETER ReplicationMode 64 | The replication mode of Storage Replica. Must be "Synchronous" or "Asynchronous" 65 | 66 | .NOTES 67 | Version: 1.0.0.0, 03/07/2017 (stable) 68 | 69 | Requires: 70 | PowerShell 5.0 71 | Hyper-V Cmdlets 72 | Failover Cluster Cmdlets 73 | Storage Cluster Cmdlets 74 | 75 | .LINK 76 | @ Jonas Feller c/o J0F3, March 2017, www.jofe.ch 77 | 78 | Get latest version at: https://github.com/J0F3/PowerShell/StorageReplica 79 | #> 80 | 81 | Param 82 | ( 83 | [String[]] 84 | $Servers = @('SR-SRV01','SR-SRV02'), 85 | 86 | [String[]] 87 | $Locations = @('Bern', 'Zurich'), 88 | 89 | [String] 90 | $ClusterName = 'SR-CLU01', 91 | 92 | [String] 93 | $ClusterIP = '192.168.1.10', 94 | 95 | [String] 96 | $CloudWitnessAccount = 'cloudwitness', 97 | 98 | [String] 99 | $CloudWitnessAccessKey = 'fcNDPKdzxdTrbg3638ZvUDtrSfKTkAPLItQfsZ2suh10zLr8quWwDUXesIH8N6Wzyw==', 100 | 101 | [String] 102 | $WitnessShare = '', 103 | 104 | [String[]] 105 | $HyperVHosts = @('HV-SRV01','HV-SRV02'), 106 | 107 | [String[]] 108 | $DataVHDXBasePaths = @('\\SOFS-Bern\csv01\SR-SRV01\Virtual Hard Disks\','\\SOFS-Zurich\csv01\SR-SRV02\Virtual Hard Disks\'), 109 | 110 | [String[]] 111 | $LogVHDXBasePaths = @('\\SOFS-Bern\csv01\SR-SRV01\Virtual Hard Disks\','\\SOFS-Zurich\csv01\SR-SRV02\Virtual Hard Disks\'), 112 | 113 | [long] 114 | $LogDiskSize = 10GB, 115 | 116 | [long] 117 | $DataDiskSize = 127GB, 118 | 119 | [string] 120 | $LogDiskLetter = 'L', 121 | 122 | [string] 123 | $DataDiskLetter = 'D', 124 | 125 | [string] 126 | $FSClusterName = 'SR-FS01', 127 | 128 | [string] 129 | $FSClusterIP = '192.168.1.11', 130 | 131 | [hashtable[]] 132 | $ShareNames = @(@{ShareName = "TestShare";ContinuouslyAvailable=$true}), 133 | 134 | [string] 135 | [ValidateSet("Synchronous","Asynchronous")] 136 | $ReplicationMode = "Synchronous" 137 | ) 138 | 139 | # install features 140 | $Servers | ForEach-Object { Install-WindowsFeature -ComputerName $_ -Name Storage-Replica,Failover-Clustering,FS-FileServer -IncludeManagementTools -restart } 141 | 142 | # build cluster 143 | New-Cluster -Name $ClusterName -Server$Servers[0] $Servers -StaticAddress $ClusterIP 144 | 145 | # configure cluster quorum 146 | if($CloudWitnessAccount) 147 | { 148 | Set-ClusterQuorum -Cluster $ClusterName -CloudWitness -AccountName $CloudWitnessAccount -AccessKey $CloudWitnessAccessKey 149 | } 150 | elseif ($WitnessShare) 151 | { 152 | Set-ClusterQuorum -Cluster $ClusterName -FileShareWitness $WitnessShare 153 | } 154 | 155 | # configure fault domains (sites) in cluster 156 | New-ClusterFaultDomain -CimSession $ClusterName -Name $Locations[0] -Type Site -Description "Primary" -Location $Locations[0] 157 | New-ClusterFaultDomain -CimSession $ClusterName -Name $Locations[1] -Type Site -Description "Secondary" -Location $Locations[1] 158 | Set-ClusterFaultDomain -CimSession $ClusterName -Name $Servers[0] -Parent $Locations[0] 159 | Set-ClusterFaultDomain -CimSession $ClusterName -Name $Servers[1] -Parent $Locations[1] 160 | (Get-Cluster -Name $ClusterName).PreferredSite=$Locations[0] 161 | 162 | # Create new VHDX files 163 | # Log disks 164 | New-VHD -CimSession $HyperVHosts[0] -Path $($VHDXBasePaths[0] + 'Log.vhdx') -SizeBytes $LogDiskSize -Fixed 165 | New-VHD -CimSession $HyperVHosts[1] -Path $($VHDXBasePaths[1] + 'Log.vhdx') -SizeBytes $LogDiskSize -Fixed 166 | 167 | # Data diks 168 | New-VHD -CimSession $HyperVHosts[0] -Path $($VHDXBasePaths[0] + 'Data.vhdx') -SizeBytes $DataDiskSize -Dynamic 169 | New-VHD -CimSession $HyperVHosts[1] -Path $($VHDXBasePaths[1] + 'Data.vhdx') -SizeBytes $DataDiskSize -Dynamic 170 | 171 | 172 | # Add new disks to cluster Server$Servers[0s 173 | Add-VMHardDiskDrive -CimSession $HyperVHosts[0] -VMName $Servers[0] -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 1 -Path $($LogVHDXBasePaths[0] + 'Log.vhdx') -SupportPersistentReservationss 174 | 175 | Add-VMHardDiskDrive -CimSession $HyperVHosts[0] -VMName $Servers[0] -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 2 -Path $($DataVHDXBasePaths[0] + 'Data.vhdx') -SupportPersistentReservations 176 | 177 | Add-VMHardDiskDrive -CimSession $HyperVHosts[1] -VMName $Servers[1] -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 1 -Path $($LogVHDXBasePaths[1] + 'Log.vhdx') -SupportPersistentReservations 178 | 179 | Add-VMHardDiskDrive -CimSession $HyperVHosts[1] -VMName $Servers[1] -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 2 -Path $($DataVHDXBasePaths[1] + 'Data.vhdx') -SupportPersistentReservations 180 | 181 | # Format disks on first node 182 | Invoke-Command -ComputerName $Servers[0] -ScriptBlock { 183 | Set-Disk -Number 1 -IsOffline $false 184 | set-disk -Number 2 -IsOffline $false 185 | Set-Disk -Number 1 -IsReadOnly $false 186 | set-disk -Number 2 -IsReadOnly $false 187 | 188 | Initialize-Disk -Number 1 -PartitionStyle GPT 189 | Initialize-Disk -Number 2 -PartitionStyle GPT 190 | 191 | Get-disk | Where-Object {$_.Size -eq 10gb -and $_.DiskNumber -ne $null} | New-Partition -UseMaximumSize -DriveLetter $USING:LogDiskLetter | Format-Volume -FileSystem NTFS -NewFileSystemLabel "Log" 192 | Get-disk | Where-Object {$_.Size -gt 10gb -and $_.DiskNumber -ne $null} | New-Partition -UseMaximumSize -DriveLetter $USING:DataDiskLetter | Format-Volume -FileSystem NTFS -NewFileSystemLabel "Data" 193 | } 194 | 195 | # Add disks of first node to the cluster 196 | Get-ClusterAvailableDisk -All -Cluster $ClusterName | Add-ClusterDisk 197 | 198 | # Rename cluster resources of source node 199 | Invoke-Command -ComputerName $Servers[0] -ScriptBlock { 200 | $ClusterDisks = Get-ClusterResource | Where-Object ResourceType -EQ 'Physical Disk' 201 | 202 | foreach ($ClusterDisk in $ClusterDisks) 203 | { 204 | $DiskResource = Get-WmiObject MSCluster_Resource -Namespace root/mscluster | Where-Object{ $_.Name -eq $ClusterDisk.Name } 205 | $Disk = Get-WmiObject -Namespace root/mscluster -Query "Associators of {$DiskResource} Where ResultClass=MSCluster_Disk" 206 | $Partition = Get-WmiObject -Namespace root/mscluster -Query "Associators of {$Disk} Where ResultClass=MSCluster_DiskPartition" 207 | 208 | $ClusterDisk.Name = "$($Partition.VolumeLabel)-Source" 209 | } 210 | } 211 | 212 | # Create File Server Cluster Role 213 | Add-ClusterFileServerRole -Cluster $ClusterName -Name $FSClusterName -StaticAddress $FSClusterIP -Storage "Data-Source" 214 | 215 | # Create File Shares 216 | foreach($Share in $ShareNames) { 217 | $SharePath = Join-Path -path "${DataDiskLetter}:" -ChildPath $($Share.ShareName) 218 | Invoke-Command -ComputerName $Servers[0] -ScriptBlock {mkdir $USING:SharePath} 219 | New-SmbShare -CimSession $Servers[0] -Name $Share.ShareName -Path $SharePath -ContinuouslyAvailable $Share.ContinuouslyAvailable 220 | } 221 | 222 | # Configure Storage Replica to destination node/site 223 | 224 | # Format disks on second node 225 | Invoke-Command -ComputerName $Servers[1] -ScriptBlock { 226 | Set-Disk -Number 1 -IsOffline $false 227 | set-disk -Number 2 -IsOffline $false 228 | Set-Disk -Number 1 -IsReadOnly $false 229 | set-disk -Number 2 -IsReadOnly $false 230 | 231 | Initialize-Disk -Number 1 -PartitionStyle GPT 232 | Initialize-Disk -Number 2 -PartitionStyle GPT 233 | 234 | Get-disk | Where-Object {$_.Size -eq 10gb -and $_.DiskNumber -ne $null} | New-Partition -UseMaximumSize -DriveLetter $USING:LogDiskLetter | Format-Volume -FileSystem NTFS -NewFileSystemLabel "Log" 235 | Get-disk | Where-Object {$_.Size -gt 10gb -and $_.DiskNumber -ne $null} | New-Partition -UseMaximumSize -DriveLetter $USING:DataDiskLetter | Format-Volume -FileSystem NTFS -NewFileSystemLabel "Data" 236 | } 237 | 238 | # Add disks of second node to the cluster 239 | Get-ClusterAvailableDisk -All -Cluster $ClusterName | Add-ClusterDisk 240 | 241 | # Move available storage to second node 242 | Move-ClusterGroup -Name "Available Storage" -Node $Servers[1] 243 | 244 | # Rename cluster resources of destination node 245 | Invoke-Command -ComputerName $Servers[1] -ScriptBlock { 246 | $ClusterDisks = Get-ClusterResource | Where-Object {$_.ResourceType -EQ 'Physical Disk' -and $_.Name -notlike "*-Source"} 247 | 248 | foreach ($ClusterDisk in $ClusterDisks) 249 | { 250 | $DiskResource = Get-WmiObject MSCluster_Resource -Namespace root/mscluster | Where-Object{ $_.Name -eq $ClusterDisk.Name } 251 | $Disk = Get-WmiObject -Namespace root/mscluster -Query "Associators of {$DiskResource} Where ResultClass=MSCluster_Disk" 252 | $Partition = Get-WmiObject -Namespace root/mscluster -Query "Associators of {$Disk} Where ResultClass=MSCluster_DiskPartition" 253 | 254 | $ClusterDisk.Name = "$($Partition.VolumeLabel)-Destination" 255 | } 256 | } 257 | 258 | # Configure Storage Replica 259 | New-SRPartnership -SourceComputerName $Servers[0] -SourceRGName "RG-Data-$($Servers[0])" -SourceRGDescription "Replication Group for D: from $($Servers[0]) to $($Servers[1])" -SourceVolumeName $DataDiskLetter -SourceLogVolumeName $LogDiskLetter -DestinationComputerName $Servers[1] -DestinationRGName "RG-Data-$($Servers[1])" -DestinationRGDescription "Replication Group for D: from $($Servers[0]) to $($Servers[1])" -DestinationVolumeName $DataDiskLetter -DestinationLogVolumeName $LogDiskLetter -ReplicationMode $ReplicationMode 260 | 261 | #Check status of inital sync 262 | do{ 263 | $r=(Get-SRGroup -ComputerName $Servers[1] -Name "RG-Data-$($Servers[1])").replicas 264 | [System.Console]::Write("Number of remaining bytes {0}`n", $r.NumOfBytesRemaining) 265 | Start-Sleep 10 266 | }until($r.ReplicationStatus -eq 'ContinuouslyReplicating') 267 | Write-Output "Replica Status: "$r.replicationstatus --------------------------------------------------------------------------------