├── .gitattributes ├── .gitignore ├── Build └── Build.ps1 ├── ConEmu.xml ├── Data └── quotes.txt ├── Install.ps1 ├── Microsoft.PowerShell_profile.ps1 ├── Modules ├── Environment │ └── Environment.psm1 └── vagrant-status │ ├── VagrantUtils.ps1 │ ├── install.ps1 │ ├── profile.base.ps1 │ ├── readme.md │ └── vagrant-status.psm1 ├── README.md └── Scripts ├── Connect-ExchangeOnline.ps1 ├── Convert-HashToString.ps1 ├── Create-VSCodeJson.ps1 ├── Disconnect-ExchangeOnline.ps1 ├── Get-GPOPassword.ps1 ├── Get-ObjectType.ps1 ├── Get-ScriptAnalysis.ps1 ├── Get-WUSettings.ps1 ├── Get-vClusterCapacity.ps1 ├── Invoke-Parallel.ps1 ├── Load-PowerCLI.ps1 ├── Load-Vagrant.ps1 ├── New-CodeSigningCertificate.ps1 ├── New-PSGalleryProjectProfile.ps1 ├── Remove-OldModule.ps1 ├── Remove-ScriptSignature.ps1 ├── Set-ProfileScriptSignature.ps1 ├── Set-ScriptSignature.ps1 ├── Update-PSGalleryProjectProfile.ps1 ├── Upgrade-InstalledModules.ps1 ├── Upgrade-System.ps1 └── Upload-ProjectToPSGallery.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore any temp directory (used in build steps) 2 | temp/ 3 | 4 | # PSGallery publishing file can contain personal API key and paths and thus we want to ignore it if it exists 5 | .psgallery 6 | 7 | # Just in case I'm a few screws loose and leave a copy of this in any project directory, ignore it. 8 | psgalleryapi.txt 9 | 10 | # Others you probably don't want or need to be public 11 | ~$* 12 | *~ 13 | *.pfx 14 | 15 | ############# 16 | ## Windows detritus 17 | ############# 18 | 19 | # Windows image file caches 20 | Thumbs.db 21 | ehthumbs.db 22 | 23 | # Folder config file 24 | Desktop.ini 25 | 26 | # Recycle Bin used on file shares 27 | $RECYCLE.BIN/ 28 | 29 | # Mac crap 30 | .DS_Store -------------------------------------------------------------------------------- /Build/Build.ps1: -------------------------------------------------------------------------------- 1 | 2 | # All this script does is copy some stuff from my own profile to this project directory and strip them of any signatures. 3 | Copy-Item -Path $ProfileDir\Scripts\*.ps1 -Destination ..\Scripts -Force 4 | Copy-Item -Path $ProfileDir\Data\quotes.txt -Destination ..\Data\quotes.txt -Force 5 | Copy-Item -Path $ProfileDir\Modules\Environment\Environment.psm1 -Destination ..\Modules\Environment\Environment.psm1 -Force 6 | Copy-Item -Path $ProfileDir\Microsoft.PowerShell_profile.ps1 -Destination ..\Microsoft.PowerShell_profile.ps1 -Force 7 | UnsignAllScripts.ps1 -Path '..\' -------------------------------------------------------------------------------- /ConEmu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | -------------------------------------------------------------------------------- /Data/quotes.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zloeber/PowerShellProfile/5b51df5443b9050de87f1f4d70a17dd49f8118ba/Data/quotes.txt -------------------------------------------------------------------------------- /Install.ps1: -------------------------------------------------------------------------------- 1 | # Run this in an administrative PowerShell prompt to install the PowerShell profile 2 | Write-Host 'Enjoy your new PowerShell profile! (Remember you can hold the shift key while it loads to get more detailed information on what it is doing)' -ForegroundColor Green 3 | Write-Host '' 4 | -------------------------------------------------------------------------------- /Microsoft.PowerShell_profile.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This whole profile is largely Joel Bennett's baby 3 | Original found here: http://poshcode.org/6062 4 | Features 5 | - History persistence between sessions 6 | - Some custom colors 7 | - Random quotes 8 | - Fun session banner 9 | - Several helper functions/scripts for such things as connectin to o365 or PowerCLI 10 | - Press and hold either Shift key while the session starts to display verbose output 11 | If you make changes to this then you probably want to re-sign it as well. The installer script accompanying this profile should have created a self-signed 12 | certificate which can be used with the Scripts\Set-ProfileScriptSignature.ps1 included with this profile as well. This script will re-sign ALL scripts in your 13 | profile (Consider yourself warned!) if run without parameters. 14 | #> 15 | trap { Write-Warning ($_.ScriptStackTrace | Out-String) } 16 | ## Some variables for later (some also get removed from memory at the end of this profile loading) 17 | 18 | $PersistentHistoryCount = 500 19 | $QuoteDir = Join-Path (Split-Path $Profile -parent) "Data" 20 | ## This timer is used by Trace-Message, I want to start it immediately 21 | $Script:TraceVerboseTimer = New-Object System.Diagnostics.Stopwatch 22 | $Script:TraceVerboseTimer.Start() 23 | ## PS5 introduced PSReadLine, which chokes in non-console shells, so I snuff it. 24 | try { 25 | $NOCONSOLE = $FALSE 26 | [System.Console]::Clear() 27 | } 28 | catch { 29 | $NOCONSOLE = $TRUE 30 | } 31 | ## If your PC doesn't have this set already, someone could tamper with this script... 32 | # but at least now, they can't tamper with any of the modules/scripts that I auto-load! 33 | Set-ExecutionPolicy AllSigned Process 34 | if ((Get-ExecutionPolicy -list | Where {$_.Scope -eq 'LocalMachine'}).ExecutionPolicy -ne 'AllSigned') { 35 | Write-Warning 'Execution policy was set to AllSigned for this process but is not set to AllSigned for the LocalMachine. ' 36 | Write-Warning 'What this means is that this profile could be tampered with and you might never know!' 37 | pause 38 | } 39 | 40 | ## Ok, now import environment so we have PSProcessElevated, Trace-Message, and other custom functions we use later 41 | # The others will get loaded automatically, but it's faster to load them explicitly 42 | Import-Module $PSScriptRoot\Modules\Environment, Microsoft.PowerShell.Management, Microsoft.PowerShell.Security, Microsoft.PowerShell.Utility 43 | 44 | ## Check SHIFT state ASAP at startup so I can use that to control verbosity :) 45 | Add-Type -Assembly PresentationCore, WindowsBase 46 | try { 47 | $global:SHIFTED = [System.Windows.Input.Keyboard]::IsKeyDown([System.Windows.Input.Key]::LeftShift) -OR 48 | [System.Windows.Input.Keyboard]::IsKeyDown([System.Windows.Input.Key]::RightShift) 49 | } 50 | catch { 51 | $global:SHIFTED = $false 52 | } 53 | 54 | if($SHIFTED) { 55 | $VerbosePreference = "Continue" 56 | } 57 | 58 | ## Fix colors before anything gets output. 59 | if($Host.Name -eq "ConsoleHost") { 60 | $Host.PrivateData.ErrorForegroundColor = "DarkRed" 61 | $Host.PrivateData.WarningForegroundColor = "DarkYellow" 62 | $Host.PrivateData.DebugForegroundColor = "Green" 63 | $Host.PrivateData.VerboseForegroundColor = "Cyan" 64 | $Host.PrivateData.ProgressForegroundColor = "Yellow" 65 | $Host.PrivateData.ProgressBackgroundColor = "DarkMagenta" 66 | } 67 | elseif(($Host.Name -eq 'Windows PowerShell ISE Host') -or ($Host.Name -eq 'PowerGUIScriptEditorHost')) { 68 | $Host.PrivateData.ErrorForegroundColor = "DarkRed" 69 | $Host.PrivateData.WarningForegroundColor = "Gold" 70 | $Host.PrivateData.DebugForegroundColor = "Green" 71 | $Host.PrivateData.VerboseForegroundColor = "Cyan" 72 | } 73 | # First call to Trace-Message, pass in our TraceTimer that I created at the top to make sure we time EVERYTHING. 74 | Trace-Message "Microsoft.PowerShell.* Modules Imported" -Stopwatch $TraceVerboseTimer 75 | 76 | ## Set the profile directory first, so we can refer to it from now on. 77 | Set-Variable ProfileDir (Split-Path $MyInvocation.MyCommand.Path -Parent) -Scope Global -Option AllScope, Constant -ErrorAction SilentlyContinue 78 | 79 | ## Add additional items to your path. Modify this to suit your needs. 80 | # We do need the Scripts directory for the rest of this profile script to run though so this first one is essential to add. 81 | [string[]]$folders = Get-ChildItem $ProfileDir\Script[s] -Directory | % FullName 82 | 83 | if ($SHIFTED) { 84 | Trace-Message "Path before updates: " 85 | $($ENV:Path -split ';') | Foreach { 86 | Trace-Message " -- $($_)" 87 | } 88 | } 89 | $ENV:PATH = Select-UniquePath $folders ${Env:Path} 90 | if ($SHIFTED) { 91 | Trace-Message "Path AFTER updates: " 92 | $($ENV:Path -split ';') | Foreach { 93 | Trace-Message " -- $($_)" 94 | } 95 | } 96 | ## Additional module directories to search for loading modules with Import-Module 97 | $Env:PSModulePath = Select-UniquePath "$ProfileDir\Modules",(Get-SpecialFolder *Modules -Value),${Env:PSModulePath} 98 | Trace-Message "PSModulePath Updated " 99 | ## Custom aliases if you want them (some examples commented out) 100 | #Set-Alias say Speech\Out-Speech -Option Constant, ReadOnly, AllScope -Description "Personal Profile Alias" 101 | #Set-Alias gph Get-PerformanceHistory -Option Constant, ReadOnly, AllScope -Description "Personal Profile Alias" 102 | ## Start sessions in the profile directory. 103 | # If you need to go to the prior directory just run pop-location right after starting powershell 104 | if($ProfileDir -ne (Get-Location)) { 105 | Push-Location $ProfileDir 106 | } 107 | ## Add some psdrives if you want them 108 | New-PSDrive Documents FileSystem (Get-SpecialFolder MyDocuments -Value) 109 | 110 | ## The prompt function is in it's own script, and executing it imports previous history 111 | if($Host.Name -ne "Package Manager Host") { 112 | . Set-Prompt -Clean -PersistentHistoryCount $PersistentHistoryCount 113 | Trace-Message "Prompt updated" 114 | } 115 | if (($Host.Name -eq 'PowerGUIScriptEditorHost') -or (($Host.Name -eq 'ConsoleHost') -and (-not $NOCONSOLE))) { 116 | if((-not (Get-Module PSReadLine)) -and (Get-Module -ListAvailable PSReadLine)) { 117 | Import-Module PSReadLine 118 | } 119 | ## If you have history to reload, you must do that BEFORE you import PSReadLine 120 | ## That way, the "up arrow" navigation works on the previous session's commands 121 | function Set-PSReadLineMyWay { 122 | param( 123 | #$BackgroundColor = $(if($PSProcessElevated) { "DarkGray" } else { "Black" } ) 124 | $BackgroundColor = "Black" 125 | ) 126 | $Host.UI.RawUI.BackgroundColor = $BackgroundColor 127 | $Host.UI.RawUI.ForegroundColor = "Gray" 128 | Set-PSReadlineOption -TokenKind Keyword -ForegroundColor Yellow -BackgroundColor $BackgroundColor 129 | Set-PSReadlineOption -TokenKind String -ForegroundColor Green -BackgroundColor $BackgroundColor 130 | Set-PSReadlineOption -TokenKind Operator -ForegroundColor DarkGreen -BackgroundColor $BackgroundColor 131 | Set-PSReadlineOption -TokenKind Variable -ForegroundColor DarkMagenta -BackgroundColor $BackgroundColor 132 | Set-PSReadlineOption -TokenKind Command -ForegroundColor DarkYellow -BackgroundColor $BackgroundColor 133 | Set-PSReadlineOption -TokenKind Parameter -ForegroundColor DarkCyan -BackgroundColor $BackgroundColor 134 | Set-PSReadlineOption -TokenKind Type -ForegroundColor Blue -BackgroundColor $BackgroundColor 135 | Set-PSReadlineOption -TokenKind Number -ForegroundColor Red -BackgroundColor $BackgroundColor 136 | Set-PSReadlineOption -TokenKind Member -ForegroundColor DarkRed -BackgroundColor $BackgroundColor 137 | Set-PSReadlineOption -TokenKind None -ForegroundColor White -BackgroundColor $BackgroundColor 138 | Set-PSReadlineOption -TokenKind Comment -ForegroundColor Black -BackgroundColor DarkGray 139 | Set-PSReadlineOption -EmphasisForegroundColor White -EmphasisBackgroundColor $BackgroundColor ` 140 | -ContinuationPromptForegroundColor DarkBlue -ContinuationPromptBackgroundColor $BackgroundColor -ContinuationPrompt (([char]183) + " ") 141 | 142 | } 143 | if (Get-Module PSReadLine) { 144 | Set-PSReadLineMyWay 145 | Set-PSReadlineKeyHandler -Key "Ctrl+Shift+R" -Function ForwardSearchHistory 146 | Set-PSReadlineKeyHandler -Key "Ctrl+R" -Function ReverseSearchHistory 147 | Set-PSReadlineKeyHandler Ctrl+M SetMark 148 | Set-PSReadlineKeyHandler Ctrl+Shift+M ExchangePointAndMark 149 | Set-PSReadlineKeyHandler Ctrl+K KillLine 150 | Set-PSReadlineKeyHandler Ctrl+I Yank 151 | Set-PSReadlineKeyHandler -Chord 'Ctrl+p' -Function 'PossibleCompletions' 152 | Trace-Message "PSReadLine fixed" 153 | } 154 | } 155 | else { 156 | Remove-Module PSReadLine -ErrorAction SilentlyContinue 157 | Trace-Message "PSReadLine skipped!" 158 | } 159 | ## Superfluous but fun quotes. 160 | # By default we look for these in $ProfileDir\Data\quotes.txt 161 | if(Test-Path $Script:QuoteDir) { 162 | # Only export $QuoteDir if it refers to a folder that actually exists 163 | Set-Variable QuoteDir (Resolve-Path $QuoteDir) -Scope Global -Option AllScope -Description "Personal PATH Variable" 164 | function Get-Quote { 165 | param( 166 | $Path = "${QuoteDir}\quotes.txt", 167 | [int]$Count=1 168 | ) 169 | if(!(Test-Path $Path) ) { 170 | $Path = Join-Path ${QuoteDir} $Path 171 | if(!(Test-Path $Path) ) { 172 | $Path = $Path + ".txt" 173 | } 174 | } 175 | Get-Content $Path | Where-Object { $_ } | Get-Random -Count $Count 176 | } 177 | Trace-Message "Random Quotes Loaded" 178 | } 179 | ## Fix em-dash screwing up our commands... 180 | $ExecutionContext.SessionState.InvokeCommand.CommandNotFoundAction = { 181 | param( $CommandName, $CommandLookupEventArgs ) 182 | if($CommandName.Contains([char]8211)) { 183 | $CommandLookupEventArgs.Command = Get-Command ( $CommandName -replace ([char]8211), ([char]45) ) -ErrorAction Ignore 184 | } 185 | } 186 | 187 | ## Write a quick banner and a random quote for fun 188 | if (-not $SHIFTED) { 189 | Clear-Host 190 | } 191 | 192 | # Show a session banner based on your platform 193 | $IsLinux = if ((Get-OSPlatform) -eq 'Linux') {$true} else {$false} 194 | Write-SessionBannerToHost -Linux $IsLinux 195 | Write-Host '' 196 | 197 | # Put a random quote out there for your brand new session 198 | try { 199 | Get-Quote 200 | } 201 | catch {} 202 | 203 | ## Clean up variables created in this profile that we don't wan't littering a cleanly started profile. 204 | Remove-Variable folders -ErrorAction SilentlyContinue 205 | Remove-Variable SHIFTED -ErrorAction SilentlyContinue 206 | Remove-Variable PersistentHistoryCount -ErrorAction SilentlyContinue 207 | Trace-Message "Profile Finished Loading!" -KillTimer 208 | 209 | ## And finally, relax the code signing restriction so we can actually get work done 210 | Set-ExecutionPolicy RemoteSigned Process 211 | -------------------------------------------------------------------------------- /Modules/Environment/Environment.psm1: -------------------------------------------------------------------------------- 1 | function Get-OSPlatform { 2 | # Parameter help description 3 | param( 4 | [Parameter()] 5 | [Switch]$IncludeLinuxDetails 6 | ) 7 | try { 8 | $Runtime = [System.Runtime.InteropServices.RuntimeInformation] 9 | $OSPlatform = [System.Runtime.InteropServices.OSPlatform] 10 | 11 | $IsCoreCLR = $true 12 | $IsLinux = $Runtime::IsOSPlatform($OSPlatform::Linux) 13 | $IsOSX = $Runtime::IsOSPlatform($OSPlatform::OSX) 14 | $IsWindows = $Runtime::IsOSPlatform($OSPlatform::Windows) 15 | } 16 | catch { 17 | # If these are already set, then they're read-only and we're done 18 | try { 19 | $IsCoreCLR = $false 20 | $IsLinux = $false 21 | $IsOSX = $false 22 | $IsWindows = $true 23 | } 24 | catch { } 25 | } 26 | 27 | if ($IsLinux) { 28 | if ($IncludeLinuxDetails) { 29 | $LinuxInfo = Get-Content /etc/os-release | ConvertFrom-StringData 30 | $IsUbuntu = $LinuxInfo.ID -match 'ubuntu' 31 | if ($IsUbuntu -and $LinuxInfo.VERSION_ID -match '14.04') { 32 | return 'Ubuntu 14.04' 33 | } 34 | if ($IsUbuntu -and $LinuxInfo.VERSION_ID -match '16.04') { 35 | return 'Ubuntu 16.04' 36 | } 37 | if ($LinuxInfo.ID -match 'centos' -and $LinuxInfo.VERSION_ID -match '7') { 38 | return 'CentOS' 39 | } 40 | } 41 | return 'Linux' 42 | } 43 | elseif ($IsOSX) { 44 | return 'OSX' 45 | } 46 | elseif ($IsWindows) { 47 | return 'Windows' 48 | } 49 | else { 50 | return 'Unknown' 51 | } 52 | } 53 | 54 | # Determine current OS platform 55 | $global:OSPlatform = Get-OSPlatform 56 | 57 | # if you're running "elevated" we want to know that: 58 | $global:PSProcessElevated = if ($OSPlatform -eq 'Windows') {([System.Environment]::OSVersion.Version.Major -gt 5) -and (New-object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)} else {$true} 59 | 60 | $OFS = ';' 61 | 62 | function LoadSpecialFolders { 63 | $Script:SpecialFolders = @{} 64 | 65 | foreach($name in [System.Environment+SpecialFolder].GetFields("Public,Static") | Sort-Object Name) { 66 | $Script:SpecialFolders.($name.Name) = [int][System.Environment+SpecialFolder]$name.Name 67 | 68 | if($Name.Name.StartsWith("My")) { 69 | $Script:SpecialFolders.($name.Name.Substring(2)) = [int][System.Environment+SpecialFolder]$name.Name 70 | } 71 | } 72 | $Script:SpecialFolders.CommonModules = Join-Path $Env:ProgramFiles "WindowsPowerShell\Modules" 73 | $Script:SpecialFolders.CommonProfile = (Split-Path $Profile.AllUsersAllHosts) 74 | $Script:SpecialFolders.Modules = Join-Path (Split-Path $Profile.CurrentUserAllHosts) "Modules" 75 | $Script:SpecialFolders.Profile = (Split-Path $Profile.CurrentUserAllHosts) 76 | $Script:SpecialFolders.PSHome = $PSHome 77 | $Script:SpecialFolders.SystemModules = Join-Path (Split-Path $Profile.AllUsersAllHosts) "Modules" 78 | } 79 | 80 | $Script:SpecialFolders = [Ordered]@{} 81 | 82 | function Get-SpecialFolder { 83 | #.Synopsis 84 | # Gets the current value for a well known special folder 85 | [CmdletBinding()] 86 | param( 87 | # The name of the Path you want to fetch (supports wildcards). 88 | # From the list: AdminTools, ApplicationData, CDBurning, CommonAdminTools, CommonApplicationData, CommonDesktopDirectory, CommonDocuments, CommonMusic, CommonOemLinks, CommonPictures, CommonProgramFiles, CommonProgramFilesX86, CommonPrograms, CommonStartMenu, CommonStartup, CommonTemplates, CommonVideos, Cookies, Desktop, DesktopDirectory, Favorites, Fonts, History, InternetCache, LocalApplicationData, LocalizedResources, MyComputer, MyDocuments, MyMusic, MyPictures, MyVideos, NetworkShortcuts, Personal, PrinterShortcuts, ProgramFiles, ProgramFilesX86, Programs, PSHome, Recent, Resources, SendTo, StartMenu, Startup, System, SystemX86, Templates, UserProfile, Windows 89 | [ValidateScript({ 90 | $Name = $_ 91 | if(!$Script:SpecialFolders.Count -gt 0) { LoadSpecialFolders } 92 | if($Script:SpecialFolders.Keys -like $Name){ 93 | return $true 94 | } else { 95 | throw "Cannot convert Path, with value: `"$Name`", to type `"System.Environment+SpecialFolder`": Error: `"The identifier name $Name is not one of $($Script:SpecialFolders.Keys -join ', ')" 96 | } 97 | })] 98 | [String]$Path = "*", 99 | 100 | # If not set, returns a hashtable of folder names to paths 101 | [Switch]$Value 102 | ) 103 | 104 | $Names = $Script:SpecialFolders.Keys -like $Path 105 | if(!$Value) { 106 | $return = @{} 107 | } 108 | 109 | foreach($name in $Names) { 110 | $result = $( 111 | $id = $Script:SpecialFolders.$name 112 | if($Id -is [string]) { 113 | $Id 114 | } else { 115 | ($Script:SpecialFolders.$name = [Environment]::GetFolderPath([int]$Id)) 116 | } 117 | ) 118 | 119 | if($result) { 120 | if($Value) { 121 | Write-Output $result 122 | } else { 123 | $return.$name = $result 124 | } 125 | } 126 | } 127 | if(!$Value) { 128 | Write-Output $return 129 | } 130 | } 131 | 132 | function Set-EnvironmentVariable { 133 | #.Synopsis 134 | # Set an environment variable at the highest scope possible 135 | [CmdletBinding()] 136 | param( 137 | [Parameter(Position=0)] 138 | [String]$Name, 139 | 140 | [Parameter(Position=1)] 141 | [String]$Value, 142 | 143 | [System.EnvironmentVariableTarget] 144 | $Scope="Machine", 145 | 146 | [Switch]$FailFast 147 | ) 148 | 149 | Set-Content "ENV:$Name" $Value 150 | $Success = $False 151 | do { 152 | try { 153 | [System.Environment]::SetEnvironmentVariable($Name, $Value, $Scope) 154 | Write-Verbose "Set $Scope environment variable $Name = $Value" 155 | $Success = $True 156 | } 157 | catch [System.Security.SecurityException] 158 | { 159 | if($FailFast) { 160 | $PSCmdlet.ThrowTerminatingError( (New-Object System.Management.Automation.ErrorRecord ( 161 | New-Object AccessViolationException "Can't set environment variable in $Scope scope" 162 | ), "FailFast:$Scope", "PermissionDenied", $Scope) ) 163 | } else { 164 | Write-Warning "Cannot set environment variables in the $Scope scope" 165 | } 166 | $Scope = [int]$Scope - 1 167 | } 168 | } while(!$Success -and $Scope -gt "Process") 169 | } 170 | 171 | function Add-Path { 172 | #.Synopsis 173 | # Add a folder to a path environment variable 174 | #.Description 175 | # Gets the existing content of the path variable, splits it with the PathSeparator, 176 | # adds the specified paths, and then joins them and re-sets the EnvironmentVariable 177 | [CmdletBinding()] 178 | param( 179 | [Parameter(Position=0, Mandatory=$True)] 180 | [String]$Name, 181 | 182 | [Parameter(Position=1)] 183 | [String[]]$Append = @(), 184 | 185 | [String[]]$Prepend = @(), 186 | 187 | [System.EnvironmentVariableTarget] 188 | $Scope="User", 189 | 190 | [Char] 191 | $Separator = [System.IO.Path]::PathSeparator 192 | ) 193 | 194 | # Make the new thing as an array so we don't get duplicates 195 | $Path = @($Prepend -split "$Separator" | %{ $_.TrimEnd("\/") } | ?{ $_ }) 196 | $Path += $OldPath = @([Environment]::GetEnvironmentVariable($Name, $Scope) -split "$Separator" | %{ $_.TrimEnd("\/") }| ?{ $_ }) 197 | $Path += @($Append -split "$Separator" | %{ $_.TrimEnd("\/") }| ?{ $_ }) 198 | 199 | # Dedup path 200 | # If the path actually exists, use the actual case of the folder 201 | $Path = $(foreach($Folder in $Path) { 202 | if(Test-Path $Folder) { 203 | Get-Item ($Folder -replace '(? 264 | $Output = @() 265 | foreach($folderPath in $Path) { 266 | if ($Delimiter) { 267 | $folderPath = $folderPath -split $Delimiter 268 | } 269 | $folderPath = $folderPath | Foreach {$_.TrimEnd('\/')} | Sort-Object | Select-Object -Unique 270 | $folderPath | Foreach { 271 | if (Test-Path $_) { 272 | $Output += Get-Item $_ 273 | Write-Verbose "Unique path added:: $($_)" 274 | } 275 | else { 276 | Write-Verbose "Path excluded because it doesn't exist: $($_)" 277 | } 278 | } 279 | } 280 | } 281 | end { 282 | if($Delimiter) { 283 | ($Output | Select -Expand FullName -Unique) -join $Delimiter 284 | } else { 285 | $Output | Select -Expand FullName -Unique 286 | } 287 | } 288 | } 289 | 290 | function Trace-Message { 291 | [CmdletBinding()] 292 | param( 293 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 294 | [string]$Message, 295 | 296 | [switch]$AsWarning, 297 | 298 | [switch]$ResetTimer, 299 | 300 | [switch]$KillTimer, 301 | 302 | [Diagnostics.Stopwatch]$Stopwatch 303 | ) 304 | begin { 305 | if($Stopwatch) { 306 | $Script:TraceTimer = $Stopwatch 307 | $Script:TraceTimer.Start() 308 | } 309 | if(-not $Script:TraceTimer) { 310 | $Script:TraceTimer = New-Object System.Diagnostics.Stopwatch 311 | $Script:TraceTimer.Start() 312 | } 313 | 314 | if($ResetTimer) 315 | { 316 | $Script:TraceTimer.Restart() 317 | } 318 | } 319 | 320 | process { 321 | $Message = "$Message - at {0} Line {1} | {2}" -f (Split-Path $MyInvocation.ScriptName -Leaf), $MyInvocation.ScriptLineNumber, $TraceTimer.Elapsed 322 | 323 | if($AsWarning) { 324 | Write-Warning $Message 325 | } else { 326 | Write-Verbose $Message 327 | } 328 | } 329 | 330 | end { 331 | if($KillTimer) { 332 | $Script:TraceTimer.Stop() 333 | $Script:TraceTimer = $null 334 | } 335 | } 336 | } 337 | 338 | function Set-AliasToFirst { 339 | param( 340 | [string[]]$Alias, 341 | [string[]]$Path, 342 | [string]$Description = "the app in $($Path[0])...", 343 | [switch]$Force, 344 | [switch]$Passthru 345 | ) 346 | if($App = Resolve-Path $Path -EA Ignore | Sort LastWriteTime -Desc | Select-Object -First 1 -Expand Path) { 347 | foreach($a in $Alias) { 348 | Set-Alias $a $App -Scope Global -Option Constant, ReadOnly, AllScope -Description $Description -Force:$Force 349 | } 350 | if($Passthru) { 351 | Split-Path $App 352 | } 353 | } else { 354 | Write-Warning "Could not find $Description" 355 | } 356 | } 357 | 358 | function Get-PIIPAddress { 359 | $NetworkInterfaces = @([System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces() | Where {($_.OperationalStatus -eq 'Up')}) 360 | $NetworkInterfaces | Foreach-Object { 361 | $_.GetIPProperties() | Where {$_.GatewayAddresses} | Foreach-Object { 362 | $Gateway = $_.GatewayAddresses.Address.IPAddressToString 363 | $DNSAddresses = @($_.DnsAddresses | Foreach-Object {$_.IPAddressToString}) 364 | $_.UnicastAddresses | Where {$_.Address -notlike '*::*'} | Foreach { 365 | New-Object PSObject -Property @{ 366 | IP = $_.Address 367 | Prefix = $_.PrefixLength 368 | Gateway = $Gateway 369 | DNS = $DNSAddresses 370 | } 371 | } 372 | } 373 | } 374 | } 375 | 376 | function Get-PIUptime { 377 | param( 378 | [switch]$FromSleep 379 | ) 380 | switch ( Get-OSPlatform ) { 381 | 'Linux' {} 382 | 'OSX' {} 383 | Default { 384 | try { 385 | if (-not $FromSleep) { 386 | $os = Get-WmiObject win32_operatingsystem 387 | $uptime = (Get-Date) - ($os.ConvertToDateTime($os.lastbootuptime)) 388 | } 389 | else { 390 | $Uptime = (((Get-Date)- (Get-EventLog -LogName system -Source 'Microsoft-Windows-Power-Troubleshooter' -Newest 1).TimeGenerated)) 391 | } 392 | $Display = "" + $Uptime.Days + " days / " + $Uptime.Hours + " hours / " + $Uptime.Minutes + " minutes" 393 | 394 | Write-Output $Display 395 | } 396 | catch {} 397 | } 398 | } 399 | } 400 | 401 | function Write-SessionBannerToHost { 402 | param( 403 | [int]$Spacer = 1, 404 | [switch]$AttemptAutoFit 405 | ) 406 | Begin { 407 | function Get-OSPlatform { 408 | param( 409 | [Parameter()] 410 | [Switch]$IncludeLinuxDetails 411 | ) 412 | try { 413 | $Runtime = [System.Runtime.InteropServices.RuntimeInformation] 414 | $OSPlatform = [System.Runtime.InteropServices.OSPlatform] 415 | 416 | $IsCoreCLR = $true 417 | $IsLinux = $Runtime::IsOSPlatform($OSPlatform::Linux) 418 | $IsOSX = $Runtime::IsOSPlatform($OSPlatform::OSX) 419 | $IsWindows = $Runtime::IsOSPlatform($OSPlatform::Windows) 420 | } 421 | catch { 422 | # If these are already set, then they're read-only and we're done 423 | try { 424 | $IsCoreCLR = $false 425 | $IsLinux = $false 426 | $IsOSX = $false 427 | $IsWindows = $true 428 | } 429 | catch { } 430 | } 431 | 432 | if ($IsLinux) { 433 | if ($IncludeLinuxDetails) { 434 | $LinuxInfo = Get-Content /etc/os-release | ConvertFrom-StringData 435 | $IsUbuntu = $LinuxInfo.ID -match 'ubuntu' 436 | if ($IsUbuntu -and $LinuxInfo.VERSION_ID -match '14.04') { 437 | return 'Ubuntu 14.04' 438 | } 439 | if ($IsUbuntu -and $LinuxInfo.VERSION_ID -match '16.04') { 440 | return 'Ubuntu 16.04' 441 | } 442 | if ($LinuxInfo.ID -match 'centos' -and $LinuxInfo.VERSION_ID -match '7') { 443 | return 'CentOS' 444 | } 445 | } 446 | return 'Linux' 447 | } 448 | elseif ($IsOSX) { 449 | return 'OSX' 450 | } 451 | elseif ($IsWindows) { 452 | return 'Windows' 453 | } 454 | else { 455 | return 'Unknown' 456 | } 457 | } 458 | 459 | function Get-PIIPAddress { 460 | $NetworkInterfaces = @([System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces() | Where {($_.OperationalStatus -eq 'Up')}) 461 | $NetworkInterfaces | Foreach-Object { 462 | $_.GetIPProperties() | Where {$_.GatewayAddresses} | Foreach-Object { 463 | $Gateway = $_.GatewayAddresses.Address.IPAddressToString 464 | $DNSAddresses = @($_.DnsAddresses | Foreach-Object {$_.IPAddressToString}) 465 | $_.UnicastAddresses | Where {$_.Address -notlike '*::*'} | Foreach { 466 | New-Object PSObject -Property @{ 467 | IP = $_.Address 468 | Prefix = $_.PrefixLength 469 | Gateway = $Gateway 470 | DNS = $DNSAddresses 471 | } 472 | } 473 | } 474 | } 475 | } 476 | 477 | function Get-PIUptime { 478 | param( 479 | [switch]$FromSleep 480 | ) 481 | switch ( Get-OSPlatform ) { 482 | 'Linux' { 483 | # Add me! 484 | } 485 | 'OSX' { 486 | # Add me! 487 | } 488 | Default { 489 | try { 490 | if (-not $FromSleep) { 491 | $os = Get-WmiObject win32_operatingsystem 492 | $uptime = (Get-Date) - ($os.ConvertToDateTime($os.lastbootuptime)) 493 | } 494 | else { 495 | $Uptime = (((Get-Date)- (Get-EventLog -LogName system -Source 'Microsoft-Windows-Power-Troubleshooter' -Newest 1).TimeGenerated)) 496 | } 497 | $Display = "" + $Uptime.Days + " days / " + $Uptime.Hours + " hours / " + $Uptime.Minutes + " minutes" 498 | 499 | Write-Output $Display 500 | } 501 | catch {} 502 | } 503 | } 504 | } 505 | 506 | $Spaces = (' ' * $Spacer) 507 | $OSPlatform = Get-OSPlatform 508 | 509 | if ($AttemptAutoFit) { 510 | try { 511 | $IP = @(Get-PIIPAddress)[0] 512 | if ([string]::isnullorempty($IP)) { 513 | $IPAddress = 'IP: Offline' 514 | $IPGateway = 'GW: Offline' 515 | } 516 | else { 517 | $IPAddress = "IP: $(@($IP.IP)[0])/$($IP.Prefix)" 518 | $IPGateway = "GW: $($IP.Gateway)" 519 | } 520 | } 521 | catch { 522 | $IPAddress = 'IP: NA' 523 | $IPGateway = 'GW: NA' 524 | } 525 | 526 | $PSExecPolicy = "Exec Pol: $(Get-ExecutionPolicy)" 527 | $PSVersion = "PS Ver: $($PSVersionTable.PSVersion.Major)" 528 | $CompName = "Computer: $($env:COMPUTERNAME)" 529 | $UserDomain = "Domain: $($env:UserDomain)" 530 | $LogonServer = "Logon Sever: $($env:LOGONSERVER -replace '\\')" 531 | $UserName = "User: $($env:UserName)" 532 | $UptimeBoot = "Uptime (hardware boot): $(Get-PIUptime)" 533 | $UptimeResume = Get-PIUptime -FromSleep 534 | if ($UptimeResume) { 535 | $UptimeResume = "Uptime (system resume): $($UptimeResume)" 536 | } 537 | } else { 538 | # Collect all the banner data 539 | try { 540 | $IP = @(Get-PIIPAddress)[0] 541 | if ([string]::isnullorempty($IP)) { 542 | $IPAddress = 'Offline' 543 | $IPGateway = 'Offline' 544 | } 545 | else { 546 | $IPAddress = "$(@($IP.IP)[0])/$($IP.Prefix)" 547 | $IPGateway = "$($IP.Gateway)" 548 | } 549 | } 550 | catch { 551 | $IPAddress = 'NA' 552 | $IPGateway = 'NA' 553 | } 554 | 555 | $OSPlatform = Get-OSPlatform 556 | $PSExecPolicy = Get-ExecutionPolicy 557 | $PSVersion = $PSVersionTable.PSVersion.Major 558 | $CompName = $env:COMPUTERNAME 559 | $UserDomain = $env:UserDomain 560 | $LogonServer = $env:LOGONSERVER -replace '\\' 561 | $UserName = $env:UserName 562 | $UptimeBoot = Get-PIUptime 563 | $UptimeResume = Get-PIUptime -FromSleep 564 | } 565 | 566 | $PSProcessElevated = 'TRUE' 567 | if ($OSPlatform -eq 'Windows') { 568 | if (([System.Environment]::OSVersion.Version.Major -gt 5) -and ((New-object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))) { 569 | $PSProcessElevated = 'TRUE' 570 | } else { 571 | $PSProcessElevated = 'FALSE' 572 | } 573 | } 574 | 575 | if ($AttemptAutoFit) { 576 | $PSProcessElevated = "Elevated: $($PSProcessElevated)" 577 | } 578 | } 579 | 580 | Process {} 581 | End { 582 | if ($AttemptAutoFit) { 583 | Write-Host ("{0,-25}$($Spaces)" -f $IPAddress) -noNewline 584 | Write-Host ("{0,-25}$($Spaces)" -f $UserDomain) -noNewline 585 | Write-Host ("{0,-25}$($Spaces)" -f $LogonServer) -noNewline 586 | Write-Host ("{0,-25}$($Spaces)" -f $PSExecPolicy) 587 | 588 | Write-Host ("{0,-25}$($Spaces)" -f $IPGateway) -noNewline 589 | Write-Host ("{0,-25}$($Spaces)" -f $CompName) -noNewline 590 | Write-Host ("{0,-25}$($Spaces)" -f $UserName) -noNewline 591 | Write-Host ("{0,-25}$($Spaces)" -f $PSVersion) 592 | Write-Host 593 | Write-Host $UptimeBoot 594 | if ($UptimeResume) { 595 | Write-Host $UptimeResume 596 | } 597 | } 598 | else { 599 | Write-Host "Dom:" -ForegroundColor Green -nonewline 600 | Write-Host $UserDomain -ForegroundColor Cyan -nonewline 601 | Write-Host "$Spaces|$Spaces" -ForegroundColor White -nonewline 602 | 603 | Write-Host "Host:"-ForegroundColor Green -nonewline 604 | Write-Host $CompName -ForegroundColor Cyan -nonewline 605 | Write-Host "$Spaces|$Spaces" -ForegroundColor White -nonewline 606 | 607 | Write-Host "Logon Svr:" -ForegroundColor Green -nonewline 608 | Write-Host $LogonServer -ForegroundColor Cyan 609 | #Write-Host "$Spaces|$Spaces" -ForegroundColor Yellow 610 | 611 | 612 | Write-Host "PS:" -ForegroundColor Green -nonewline 613 | Write-Host $PSVersion -ForegroundColor Cyan -nonewline 614 | Write-Host "$Spaces|$Spaces" -ForegroundColor White -nonewline 615 | 616 | Write-Host "Elevated:" -ForegroundColor Green -nonewline 617 | if ($PSProcessElevated) { 618 | Write-Host $PSProcessElevated -ForegroundColor Red -nonewline 619 | } 620 | else { 621 | Write-Host $PSProcessElevated -ForegroundColor Cyan -nonewline 622 | } 623 | Write-Host "$Spaces|$Spaces" -ForegroundColor White -nonewline 624 | 625 | Write-Host "Execution Policy:" -ForegroundColor Green -nonewline 626 | Write-Host $PSExecPolicy -ForegroundColor Cyan 627 | 628 | # Line 2 629 | Write-Host "User:" -ForegroundColor Green -nonewline 630 | Write-Host $UserName -ForegroundColor Cyan -nonewline 631 | Write-Host "$Spaces|$Spaces" -ForegroundColor White -nonewline 632 | 633 | Write-Host "IP:" -ForegroundColor Green -nonewline 634 | Write-Host $IPAddress -ForegroundColor Cyan -nonewline 635 | Write-Host "$Spaces|$Spaces" -ForegroundColor White -nonewline 636 | 637 | Write-Host "GW:" -ForegroundColor Green -nonewline 638 | Write-Host $IPGateway -ForegroundColor Cyan 639 | 640 | Write-Host 641 | 642 | # Line 3 643 | Write-Host "Uptime (hardware boot): " -nonewline -ForegroundColor Green 644 | Write-Host $UptimeBoot -ForegroundColor Cyan 645 | 646 | # Line 4 647 | if ($UptimeResume) { 648 | Write-Host "Uptime (system resume): " -nonewline -ForegroundColor Green 649 | Write-Host $UptimeResume -ForegroundColor Cyan 650 | } 651 | } 652 | } 653 | } 654 | 655 | function Reset-Module ($ModuleName) { 656 | rmo $ModuleName; ipmo $ModuleName -force -pass | ft Name, Version, Path -AutoSize 657 | } 658 | 659 | function Set-Prompt { 660 | <# 661 | .Synopsis 662 | Sets my favorite prompt function 663 | 664 | .Notes 665 | I put the id in my prompt because it's very, very useful. 666 | 667 | Invoke-History and my Expand-Alias and Get-PerformanceHistory all take command history IDs 668 | Also, you can tab-complete with "#[Tab]" so . 669 | For example, the following commands: 670 | r 4 671 | ## r is an alias for invoke-history, so this reruns your 4th command 672 | 673 | #6[Tab] 674 | ## will tab-complete whatever you typed in your 6th command (now you can edit it) 675 | 676 | Expand-Alias -History 6,8,10 > MyScript.ps1 677 | ## generates a script from those history items 678 | 679 | GPH -id 6, 8 680 | ## compares the performance of those two commands ... 681 | 682 | Ganked from Joel Bennett at http://poshcode.org/4705 683 | #> 684 | [CmdletBinding(DefaultParameterSetName="Default")] 685 | param( 686 | # Controls how much history we keep in the command log between sessions 687 | [Int]$PersistentHistoryCount = 30, 688 | 689 | # If set, we use a pasteable prompt with <# #> around the prompt info 690 | [Parameter(ParameterSetName="Pasteable")] 691 | [Alias("copy","demo")][Switch]$Pasteable, 692 | 693 | # If set, use a simple, clean prompt (otherwise use a fancy multi-line prompt) 694 | [Parameter(ParameterSetName="Clean")] 695 | [Switch]$Clean, 696 | 697 | # Maximum history count 698 | [Int]$MaximumHistoryCount = 2048, 699 | # The main prompt foreground color 700 | [ConsoleColor]$Foreground = "Yellow", 701 | # The ERROR prompt foreground color 702 | [ConsoleColor]$ErrorForeground = "DarkRed", 703 | # The prompt background (should probably match your console background) 704 | [ConsoleColor]$Background = "Black" 705 | ) 706 | end { 707 | # Regression bug? 708 | [ConsoleColor]$global:PromptForeground = $Foreground 709 | [ConsoleColor]$global:ErrorForeground = $ErrorForeground 710 | [ConsoleColor]$global:PromptBackground = $Background 711 | $global:MaximumHistoryCount = $MaximumHistoryCount 712 | $global:PersistentHistoryCount = $PersistentHistoryCount 713 | 714 | # Some stuff goes OUTSIDE the prompt function because it doesn't need re-evaluation 715 | 716 | # I set the title in my prompt every time, because I want the current PATH location there, 717 | # rather than in my prompt where it takes up too much space. 718 | 719 | # But I want other stuff too. I calculate an initial prefix for the window title 720 | # The title will show the PowerShell version, user, current path, and whether it's elevated or not 721 | # E.g.:"PoSh3 Jaykul@HuddledMasses (ADMIN) - C:\Your\Path\Here (FileSystem)" 722 | if(!$global:WindowTitlePrefix) { 723 | $global:WindowTitlePrefix = "PoSh$($PSVersionTable.PSVersion.Major) ${Env:UserName}@${Env:UserDomain}" 724 | 725 | # if you're running "elevated" we want to show that: 726 | $PSProcessElevated = ([System.Environment]::OSVersion.Version.Major -gt 5) -and ( # Vista and ... 727 | new-object Security.Principal.WindowsPrincipal ( 728 | [Security.Principal.WindowsIdentity]::GetCurrent()) # current user is admin 729 | ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) 730 | 731 | if($PSProcessElevated) { 732 | $global:WindowTitlePrefix += " (ADMIN)" 733 | } 734 | } 735 | 736 | ## Global first-run (profile or first prompt) 737 | if($MyInvocation.HistoryId -eq 1) { 738 | if ($global:profiledir -eq $null) { 739 | $ProfileDir = Split-Path $Profile.CurrentUserAllHosts 740 | } 741 | ## Import my history 742 | if (Test-Path $ProfileDir\.poshhistory) { 743 | Import-CSV $ProfileDir\.poshhistory | Add-History 744 | } 745 | } 746 | 747 | # As this is not digitally signed it will fail if we are in AllSigned mode so I don't load it here 748 | # if(Get-Module -ListAvailable Posh-Git){ 749 | # Import-Module Posh-Git 750 | # } 751 | 752 | if($Pasteable) { 753 | # The pasteable prompt starts with "<#PS " and ends with " #>" 754 | # so that you can copy-paste with the prompt and it will still run 755 | function global:prompt { 756 | # FIRST, make a note if there was an error in the previous command 757 | $err = !$? 758 | Write-host "<#PS " -NoNewLine -fore gray 759 | 760 | # Make sure Windows and .Net know where we are (they can only handle the FileSystem) 761 | [Environment]::CurrentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath 762 | 763 | try { 764 | # Also, put the path in the title ... (don't restrict this to the FileSystem) 765 | $Host.UI.RawUI.WindowTitle = "{0} - {1} ({2})" -f $global:WindowTitlePrefix,$pwd.Path,$pwd.Provider.Name 766 | } catch {} 767 | 768 | # Determine what nesting level we are at (if any) 769 | $Nesting = "$([char]0xB7)" * $NestedPromptLevel 770 | 771 | # Generate PUSHD(push-location) Stack level string 772 | $Stack = "+" * (Get-Location -Stack).count 773 | 774 | # I used to use Export-CliXml, but Export-CSV is a lot faster 775 | $null = Get-History -Count $PersistentHistoryCount | Export-CSV $ProfileDir\.poshhistory 776 | # Output prompt string 777 | # If there's an error, set the prompt foreground to the error color... 778 | if($err) { $fg = $global:ErrorForeground } else { $fg = $global:PromptForeground } 779 | # Notice: no angle brackets, makes it easy to paste my buffer to the web 780 | Write-Host "[${Nesting}$($myinvocation.historyID)${Stack}]" -NoNewLine -Foreground $fg 781 | Write-host " #>" -NoNewLine -fore gray 782 | # Hack PowerShell ISE CTP2 (requires 4 characters of output) 783 | if($Host.Name -match "ISE" -and $PSVersionTable.BuildVersion -eq "6.2.8158.0") { 784 | return "$("$([char]8288)"*3) " 785 | } else { 786 | return " " 787 | } 788 | } 789 | } elseif($Clean) { 790 | function global:prompt { 791 | # FIRST, make a note if there was an error in the previous command 792 | $err = !$? 793 | 794 | # Make sure Windows and .Net know where we are (they can only handle the FileSystem) 795 | [Environment]::CurrentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath 796 | 797 | try { 798 | # Also, put the path in the title ... (don't restrict this to the FileSystem) 799 | $Host.UI.RawUI.WindowTitle = "{0} - {1} ({2})" -f $global:WindowTitlePrefix, $pwd.Path, $pwd.Provider.Name 800 | } catch {} 801 | 802 | # Determine what nesting level we are at (if any) 803 | $Nesting = "$([char]0xB7)" * $NestedPromptLevel 804 | 805 | # Generate PUSHD(push-location) Stack level string 806 | $Stack = "+" * (Get-Location -Stack).count 807 | 808 | # I used to use Export-CliXml, but Export-CSV is a lot faster 809 | $null = Get-History -Count $PersistentHistoryCount | Export-CSV $ProfileDir\.poshhistory 810 | 811 | # Output prompt string 812 | # If there's an error, set the prompt foreground to "Red", otherwise, "Yellow" 813 | if($err) { $fg = $global:ErrorForeground } else { $fg = $global:PromptForeground } 814 | # Notice: no angle brackets, makes it easy to paste my buffer to the web 815 | Write-Host "[${Nesting}$($myinvocation.historyID)${Stack}]:" -NoNewLine -Fore $fg 816 | # Hack PowerShell ISE CTP2 (requires 4 characters of output) 817 | if($Host.Name -match "ISE" -and $PSVersionTable.BuildVersion -eq "6.2.8158.0") { 818 | return "$("$([char]8288)"*3) " 819 | } else { 820 | return " " 821 | } 822 | } 823 | } else { 824 | function global:prompt { 825 | # FIRST, make a note if there was an error in the previous command 826 | $err = !$? 827 | 828 | # Make sure Windows and .Net know where we are (they can only handle the FileSystem) 829 | [Environment]::CurrentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath 830 | 831 | try { 832 | # Also, put the path in the title ... (don't restrict this to the FileSystem) 833 | $Host.UI.RawUI.WindowTitle = "{0} - {1} ({2})" -f $global:WindowTitlePrefix,$pwd.Path,$pwd.Provider.Name 834 | } catch {} 835 | 836 | # Determine what nesting level we are at (if any) 837 | $Nesting = "$([char]0xB7)" * $NestedPromptLevel 838 | 839 | # Generate PUSHD(push-location) Stack level string 840 | $Stack = "+" * (Get-Location -Stack).count 841 | 842 | # I used to use Export-CliXml, but Export-CSV is a lot faster 843 | $null = Get-History -Count $PersistentHistoryCount | Export-CSV $ProfileDir\.poshhistory 844 | 845 | # Output prompt string 846 | # If there's an error, set the prompt foreground to "Red", otherwise, "Yellow" 847 | if($err) { $fg = $global:ErrorForeground } else { $fg = $global:PromptForeground } 848 | # Notice: no angle brackets, makes it easy to paste my buffer to the web 849 | Write-Host '╔' -NoNewLine -Foreground $global:PromptBackground 850 | Write-Host " $(if($Nesting){"$Nesting "})#$($MyInvocation.HistoryID)${Stack} " -Background $global:PromptBackground -Foreground $fg -NoNewLine 851 | if(Get-Module Posh-Git) { 852 | $LEC = $LASTEXITCODE 853 | Set-GitPromptSettings -DefaultForegroundColor $fg -DefaultBackgroundColor $global:PromptBackground -BeforeForegroundColor Black -DelimForegroundColor Black -AfterForegroundColor Black -BranchBehindAndAheadForegroundColor Black 854 | $path = $pwd -replace $([Regex]::Escape((Convert-Path "~"))),"~" 855 | Write-Host $path -Background $global:PromptBackground -Foreground $fg -NoNewLine 856 | Write-VcsStatus 857 | $global:LASTEXITCODE = $LEC 858 | } 859 | Write-Host ' ' 860 | Write-Host '╚═══╕' -Foreground $global:PromptBackground -NoNewLine 861 | # Hack PowerShell ISE CTP2 (requires 4 characters of output) 862 | if($Host.Name -match "ISE" -and $PSVersionTable.BuildVersion -eq "6.2.8158.0") { 863 | return "$("$([char]8288)"*3) " 864 | } else { 865 | return " " 866 | } 867 | } 868 | } 869 | } 870 | } 871 | 872 | 873 | -------------------------------------------------------------------------------- /Modules/vagrant-status/VagrantUtils.ps1: -------------------------------------------------------------------------------- 1 | # 2 | } 3 | } 4 | -------------------------------------------------------------------------------- /Modules/vagrant-status/install.ps1: -------------------------------------------------------------------------------- 1 | if($PSVersionTable.PSVersion.Major -lt 3) { 2 | Write-Host 'vagrant-status installed reload to see changes' 3 | 4 | -------------------------------------------------------------------------------- /Modules/vagrant-status/profile.base.ps1: -------------------------------------------------------------------------------- 1 | Push-Location (Split-Path -Path $MyInvocation.MyCommand.Definition -Parent) 2 | Pop-Location 3 | 4 | -------------------------------------------------------------------------------- /Modules/vagrant-status/readme.md: -------------------------------------------------------------------------------- 1 | ##Vagrant-Status 2 | 3 | A PowerShell prompt modification that shows the basic status of [Vagrant](https://www.vagrantup.com/) machines in the current directory. 4 | 5 | ###Install Guide 6 | 7 | 1. Clone this repo 8 | 2. In PowerShell make sure that your ExecutionPolicy is Unrestricted 9 | * Get-ExecutionPolicy will show you current ExecutionPolicy. 10 | * Set-ExecutionPolicy Unrestricted will set your ExecutionPolicy to Unrestricted. 11 | 3. Run install.ps1 to install to your profile 12 | 13 | ###Prompt Explanation 14 | 15 | The prompt is defined in the profile.base.ps1 which will output a working directory as well as a simple/detailed vagrant status indicator depending on your choice. profile.base.ps1 has two options which can be commented in or out. Don't leave both out or in. 16 | 17 | #### Detailed 18 | 19 | A basic example layout of this status is [D:0 R:1]. 20 | 21 | The D(own) or 'poweroff/aborted in vagrant status' collects the number of machines for the current directory (vagrant environment) that are in that state. This will be colored in gray 22 | 23 | The R(unning) or 'running in vagrant status' collects the number of machines for the current directory (vagrant environment) that are in that state. This will be colored in green 24 | 25 | If there is a vagrantfile but no (D)own or (R)unning aka 'not created in vagrant status' machines you will see [-] in grey. This is to convey that there is a dormant vagrant environment in the current directory. 26 | 27 | #### Simple 28 | 29 | If there is an active Vagrant machine(s) you will see [^] the ^ is colorized in green. If there is a vagrantfile and/or folder but no Vagrant machine(s) active you will see [-]. 30 | 31 | ###Other Info 32 | 33 | vagrant-status can be installed with posh-git from the following repo [posh-git-vagrant-status](https://github.com/n00bworks/posh-git-vagrant-status) 34 | 35 | ###Based On 36 | 37 | This project is based on the great PowerShell prompt plug-in [posh-git](https://github.com/dahlbyk/posh-git) 38 | 39 | ###Contributing 40 | 41 | 1. Fork it 42 | 2. Create your feature branch (git checkout -b my-new-feature) 43 | 3. Commit your changes (git commit -am 'Add some feature') 44 | 4. Push to the branch (git push origin my-new-feature) 45 | 5. Create a new Pull Request 46 | -------------------------------------------------------------------------------- /Modules/vagrant-status/vagrant-status.psm1: -------------------------------------------------------------------------------- 1 | if (Get-Module vagrant-status) 2 | Export-ModuleMember -Function Get-VagrantFile, Get-VagrantDir, Get-VagrantEnvIndex, Write-VagrantStatusSimple, Write-VagrantStatusDetailed 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Powershell Profile and Environment 2 | 3 | This is a collection of scripts, personal profile preferences that I pieced together from people much smarter (or at least more developer minded) than I. I'm also trying to include my PowerShell development environment settings like fonts and apps but this is secondary. This is all for a personal need to be able to setup a standardized environment between my work and home PCs (or rebuild it quickly in either). 4 | 5 | ##Profile Description 6 | I found and repurposed Joel Bennett's work for much of this part. His work is genius and I'm probably doing it an extreme disservice but I made several small changes and simplifications to it where I ran into issues. I repurposed is environment.psm1 module as a general location for profile related functions (and fixed some path mangling issues I ran into for some reason). But truthfully 95% of the best parts of this environment are Joel's scriptcraft. 7 | 8 | ###Profile Configuration 9 | There are 2 major components of the profile. There is an environment.psm1 file that includes the lion's share of important functions for the profile. This includes Joel's 'Set-Prompt' function that is used to retain command history across sessions among other things. 10 | 11 | I've also included a number of one off scripts that are not used in the actual profile but are part of my personal essential scripts or used in the initial configuration of the profile. This includes: 12 | - Connect-ExchangeOnline.ps1 13 | - Disconnect-ExchangeOnline.ps1 14 | - Load-PowerCLI.ps1 15 | - Load-Vagrant.ps1 16 | - Remove-ScriptSignature.ps1 17 | - Set-ProfileScriptSignature.ps1 18 | - New-CodeSigningCertificate.ps1 19 | 20 | The profile directory tree for my configuration looks like this: 21 | 22 | `C:\Users\\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1` 23 | 24 | `C:\Users\\Documents\WindowsPowerShell\Scripts\*.ps1` 25 | 26 | `C:\Users\\Documents\WindowsPowerShell\Modules\Environment\Environment.psm1` 27 | 28 | `C:\Users\\Documents\WindowsPowerShell\Data\quotes.txt` 29 | 30 | ###Installing 31 | All you need to do is copy the contents of this repo into your WindowsPowerShell directory then optionally create a self-signed code signing certificate and run a script signing script I put together. I put together a quick install.ps1 file to do this but if I were you I'd just do this manually so you know nothing is getting accidentially overwritten. 32 | 33 | `iex (New-Object Net.WebClient).DownloadString("https://raw.githubusercontent.com/zloeber/PowerShellProfile/master/Install.ps1")` 34 | 35 | Once you have installed the basic components you can secure your profile a bit with a code signing certificate. An extra script or two I put together will do just that for ya if you like: 36 | 37 | `. (Split-Path $Profile)\Scripts\New-CodeSigningCertificate.ps1` 38 | 39 | `. (Split-Path $Profile)\Scripts\Set-ProfileScriptSignature.ps1` 40 | 41 | `Set-ExecutionPolicy AllSigned` 42 | 43 | The script will look for a code signing certificate and attempt to create one if it doesn't already exist. It is suggested to go ahead and do this if you aren't using one already to sign your scripts. This will give you a few prompts as it tries to move the self-signed certificate to the appropriate store to be trusted. 44 | 45 | Assuming that the self-signed code signing certificate gets created or already exists the next script will try to sign the environment.psm1 and Microsoft.PowerShell_profile.ps1 scripts to help prevent tampering of your profile. This really only becomes effective if you also set your local system to have its execution policy of AllSigned. Check your execution policy with this: 46 | 47 | `Get-ExecutionPolicy -List` 48 | 49 | If you change your profile script or the Environment.psm1 file just run the following to re-sign them again: 50 | `. (Split-Path $Profile)\Scripts\Set-ProfileScriptSignature.ps1` 51 | 52 | Note: After the profile loads, it will set the Process scoped execution policy to RemoteSigned! 53 | 54 | 55 | ##Other Information 56 | **Author:** Zachary Loeber 57 | 58 | **Website:** http://www.the-little-things.net 59 | 60 | **Github:** https://github.com/zloeber/PowerShellProfile 61 | 62 | ##Other credits: 63 | [HarooPad](http://pad.haroopress.com/) 64 | [Joel Bennett](http://http://huddledmasses.org/) -------------------------------------------------------------------------------- /Scripts/Connect-ExchangeOnline.ps1: -------------------------------------------------------------------------------- 1 | $upn = ([ADSISEARCHER]"samaccountname=$($env:USERNAME)").Findone().Properties.userprincipalname 2 | $creds = Get-Credential -UserName $upn -Message "Enter password for $upn" 3 | $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $creds -Authentication Basic -AllowRedirection 4 | 5 | $yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes','' 6 | $no = New-Object System.Management.Automation.Host.ChoiceDescription '&No','' 7 | $choices = [System.Management.Automation.Host.ChoiceDescription[]]($no,$yes) 8 | $result = $Host.UI.PromptForChoice('Prefix Commands','Do you want to prefix all imported commands with o365 (useful if you are accessing both on premise and cloud environments?',$choices,0) 9 | $AddPrefix = ($result -eq $true) 10 | $ImportParam = @{} 11 | If ( $AddPrefix ) { $ImportParam.Prefix = 'o365' } 12 | Import-PSSession $session @ImportParam 13 | Write-Output "`n`n`nDon't forget to 'Remove-PSSession `$session' or 'Disconnect-ExchangeOnline' when you're done" 14 | 15 | # If the msonline module is available then ask if we want to load it as well 16 | if ((get-module msonline -ListAvailable) -ne $null) { 17 | $result = $Host.UI.PromptForChoice('MSOL','Connect to MSOL as well?',$choices,0) 18 | $MSOL = ($result -eq $true) 19 | 20 | if ( $MSOL ) { 21 | import-module msonline -ErrorAction SilentlyContinue 22 | if ((get-module | Where-Object {$_.Name -eq 'msonline'}) -ne $null) { 23 | Connect-MsolService -Credential $creds } 24 | else { 25 | Write-Warning 'Unable to load the MSOnline powershell module!' 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Scripts/Convert-HashToString.ps1: -------------------------------------------------------------------------------- 1 | [cmdletbinding()] 2 | 3 | Param 4 | ( 5 | [Parameter(Mandatory=$true,Position=0)] 6 | [Hashtable]$Hashtable, 7 | 8 | [Parameter(Mandatory=$False)] 9 | [switch]$Flatten 10 | ) 11 | 12 | Begin{ 13 | If($Flatten -or $Hashtable.Keys.Count -eq 0) 14 | { 15 | $Mode = 'Append' 16 | $Indenting = '' 17 | $RecursiveIndenting = '' 18 | } 19 | Else{ 20 | $Mode = 'Appendline' 21 | $Indenting = ' ' 22 | $RecursiveIndenting = ' ' * (Get-PSCallStack).Where({$_.Command -match 'Convert-ArrayToString|Convert-HashToSTring' -and $_.InvocationInfo.CommandOrigin -eq 'Internal' -and $_.InvocationInfo.Line -notmatch '\$This'}).Count 23 | } 24 | 25 | } 26 | 27 | Process{ 28 | $StringBuilder = [System.Text.StringBuilder]::new() 29 | 30 | If($Hashtable.Keys.Count -ge 1) 31 | { 32 | [void]$StringBuilder.$Mode("@{") 33 | } 34 | Else 35 | { 36 | [void]$StringBuilder.Append("@{") 37 | } 38 | 39 | Foreach($Key in $Hashtable.Keys) 40 | { 41 | $Value = $Hashtable[$Key] 42 | 43 | If($Key -match '\s') 44 | { 45 | $Key = "'$Key'" 46 | } 47 | 48 | If($Value -is [String]) 49 | { 50 | [void]$StringBuilder.$Mode($Indenting + $RecursiveIndenting + "$Key = '$Value'") 51 | } 52 | ElseIf($Value -is [int] -or $Value -is [double]) 53 | { 54 | [void]$StringBuilder.$Mode($Indenting + $RecursiveIndenting + "$Key = $($Value.ToString())") 55 | } 56 | ElseIf($Value -is [bool]) 57 | { 58 | [void]$StringBuilder.$Mode($Indenting + $RecursiveIndenting + "$Key = `$$Value") 59 | } 60 | ElseIf($Value -is [array]) 61 | { 62 | $Value = Convert-ArrayToString -Array $Value -Flatten:$Flatten 63 | 64 | [void]$StringBuilder.$Mode($Indenting + $RecursiveIndenting + "$Key = $Value") 65 | } 66 | ElseIf($Value -is [hashtable]) 67 | { 68 | $Value = Convert-HashToSTring -Hashtable $Value -Flatten:$Flatten 69 | [void]$StringBuilder.$Mode($Indenting + $RecursiveIndenting + "$Key = $Value") 70 | } 71 | Else 72 | { 73 | Throw "Key value is not of known type." 74 | } 75 | 76 | If($Flatten){[void]$StringBuilder.Append("; ")} 77 | } 78 | 79 | [void]$StringBuilder.Append($RecursiveIndenting + "}") 80 | 81 | $StringBuilder.ToString().Replace("; }",'}') 82 | } 83 | 84 | End{} 85 | 86 | #Remove-TypeData -TypeName System.Collections.HashTable -ErrorAction SilentlyContinue 87 | #Update-TypeData -TypeName System.Collections.HashTable -MemberType ScriptMethod -MemberName ToString -Value {Convert-HashToString $This} 88 | -------------------------------------------------------------------------------- /Scripts/Create-VSCodeJson.ps1: -------------------------------------------------------------------------------- 1 | # Simple script to create the necessary json file to allow vscode to debug a script file 2 | param ([string]$ScriptName) 3 | 4 | $ScriptName = $ScriptName -replace './','' -replace '.\','' 5 | $Template = @' 6 | { 7 | "version": "0.2.0", 8 | "configurations": [{ 9 | "name": "PowerShell", 10 | "type": "PowerShell", 11 | "request": "launch", 12 | "program": "${workspaceRoot}/{{ScriptName}}" 13 | }] 14 | } 15 | 16 | '@ -replace "{{ScriptName}}", $ScriptName 17 | 18 | if (-not (test-path '.\.vscode')) { 19 | New-Item -Name '.vscode' -ItemType:Directory 20 | } 21 | 22 | $Template | Out-File -FilePath '.\.vscode\launch.json' -Force -Encoding utf8 23 | 24 | Write-Host 'json file created, remember to open the current folder in vscode. Use f5 to start debugging, use ctrl+shift+D to see the debugger panel.' -------------------------------------------------------------------------------- /Scripts/Disconnect-ExchangeOnline.ps1: -------------------------------------------------------------------------------- 1 | if ($session) { 2 | Remove-PSSession $session 3 | } else { 4 | Get-PSSession | Remove-PSSession 5 | } 6 | 7 | "$((Get-PSSession | Measure-Object).Count) PowerShell session(s)" 8 | -------------------------------------------------------------------------------- /Scripts/Get-GPOPassword.ps1: -------------------------------------------------------------------------------- 1 | #function Get-GPPPassword { 2 | 3 | <# 4 | .Synopsis 5 | 6 | Get-GPPPassword retrieves the plaintext password for accounts pushed through Group Policy in groups.xml. 7 | Author: Chris Campbell (@obscuresec) 8 | License: GNU GPL v2 9 | .Description 10 | 11 | Get-GPPPassword imports the encoded and encrypted password string from groups.xml and then decodes and decrypts the plaintext password. 12 | 13 | .Parameter Path 14 | 15 | The path to the targeted groups.xml file. 16 | 17 | .Example 18 | 19 | Get-GPPPassword -path c:\demo\groups.xml 20 | 21 | .Link 22 | 23 | http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences 24 | http://www.obscuresecurity.blogspot.com/2012/05/gpp-password-retrieval-with-powershell.html 25 | #> 26 | 27 | Param ( [Parameter(Position = 0, Mandatory = $True)] [String] $Path = "$PWD\groups.xml" ) 28 | 29 | #Function to pull encrypted password string from groups.xml 30 | function Parse-cPassword { 31 | 32 | try { 33 | [xml] $Xml = Get-Content ($Path) 34 | [String] $Cpassword = $Xml.Groups.User.Properties.cpassword 35 | } catch { Write-Error "No Password Policy Found in File!" } 36 | 37 | return $Cpassword 38 | } 39 | 40 | #Function to look to see if the administrator account is given a newname 41 | function Parse-NewName { 42 | 43 | [xml] $Xml = Get-Content ($Path) 44 | [String] $NewName = $Xml.Groups.User.Properties.newName 45 | 46 | return $NewName 47 | } 48 | 49 | #Function to parse out the Username whose password is being specified 50 | function Parse-UserName { 51 | 52 | try { 53 | [xml] $Xml = Get-Content ($Path) 54 | [string] $UserName = $Xml.Groups.User.Properties.userName 55 | } catch { Write-Error "No Username Specified in File!" } 56 | 57 | return $UserName 58 | } 59 | 60 | #Function that decodes and decrypts password 61 | function Decrypt-Password { 62 | 63 | try { 64 | #Append appropriate padding based on string length 65 | $Pad = "=" * (4 - ($Cpassword.length % 4)) 66 | $Base64Decoded = [Convert]::FromBase64String($Cpassword + $Pad) 67 | #Create a new AES .NET Crypto Object 68 | $AesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider 69 | #Static Key from http://msdn.microsoft.com/en-us/library/2c15cbf0-f086-4c74-8b70-1f2fa45dd4be%28v=PROT.13%29#endNote2 70 | [Byte[]] $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8, 71 | 0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b) 72 | #Set IV to all nulls (thanks Matt) to prevent dynamic generation of IV value 73 | $AesIV = New-Object Byte[]($AesObject.IV.Length) 74 | $AesObject.IV = $AesIV 75 | $AesObject.Key = $AesKey 76 | $DecryptorObject = $AesObject.CreateDecryptor() 77 | [Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length) 78 | 79 | return [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock) 80 | } catch { Write-Error "Decryption Failed!" } 81 | 82 | } 83 | 84 | $Cpassword = Parse-cPassword 85 | $Password = Decrypt-Password 86 | $NewName = Parse-NewName 87 | $UserName = Parse-UserName 88 | 89 | $Results = New-Object System.Object 90 | 91 | Add-Member -InputObject $Results -type NoteProperty -name UserName -value $UserName 92 | Add-Member -InputObject $Results -type NoteProperty -name NewName -value $NewName 93 | Add-Member -InputObject $Results -type NoteProperty -name Password -value $Password 94 | 95 | return $Results -------------------------------------------------------------------------------- /Scripts/Get-ObjectType.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Display the object type name 4 | .DESCRIPTION 5 | Display the object type name 6 | .PARAMETER Obj 7 | Object to get the typename of 8 | .PARAMETER Tree 9 | Show entire typename inheritence tree 10 | .LINK 11 | http://www.the-little-things.net 12 | .NOTES 13 | Version 14 | 1.0.0 06/14/2016 15 | - Initial release 16 | Author : Zachary Loeber 17 | 18 | .EXAMPLE 19 | $a | Get-ObjectType.ps1 20 | 21 | Description 22 | ----------- 23 | Gets the type of object that $a is defined as. 24 | #> 25 | [CmdLetBinding()] 26 | param( 27 | [Parameter(Mandatory = $True, ValueFromPipeline = $True, Position = 0, HelpMessage = 'An object to check.')] 28 | $Obj, 29 | [Parameter(Position = 1, HelpMessage = 'View as an inheritence tree.')] 30 | [switch]$Tree 31 | ) 32 | 33 | Process { 34 | $Obj | Foreach { 35 | if ($Tree) { 36 | $depth = '' 37 | Foreach ($t in ($Obj).pstypenames) { 38 | Write-Output $depth$t 39 | $depth = '--' 40 | } 41 | } 42 | else { 43 | ($Obj).pstypenames[0] 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Scripts/Get-ScriptAnalysis.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Installs PSScriptAnalyzer module and runs it against a supplied path. 4 | .DESCRIPTION 5 | Installs PSScriptAnalyzer module and runs it against a supplied path. 6 | .PARAMETER ProjectPath 7 | Path to scripts to analyze. 8 | .EXAMPLE 9 | .\Get-ScriptAnalysis.ps1 -ProjectPath C:\Project1\ 10 | .NOTES 11 | Author: Zachary Loeber 12 | Site: http://www.the-little-things.net/ 13 | Requires: Powershell 5.0 14 | 15 | Version History 16 | 1.0.0 - Initial release 17 | #> 18 | [CmdletBinding()] 19 | param( 20 | [parameter(Mandatory=$true, ValueFromPipeline=$true, HelpMessage='Path of module to analyze.')] 21 | [string]$ProjectPath = (Read-Host -Prompt 'Please enter the script path to analyze') 22 | ) 23 | 24 | # This assumes you are running PowerShell 5 25 | if ($PSVersionTable.PSVersion.Major -ge 5) { 26 | if (Test-Path $ProjectPath) { 27 | if ( -not (get-module PSScriptAnalyzer)) { 28 | Install-Module -Name PSScriptAnalyzer -Repository PSGallery -force 29 | } 30 | if (get-module PSScriptAnalyzer) { 31 | try { 32 | Invoke-ScriptAnalyzer -Path $ProjectPath 33 | } 34 | catch { 35 | throw 'ScriptAnalyzer failed!' 36 | } 37 | } 38 | else { 39 | Write-Error 'Unable to install PSScriptAnalyzer!' 40 | } 41 | } 42 | else { 43 | Write-Error 'Path to project was not found!' 44 | } 45 | } 46 | else { 47 | Write-Error 'This requires powershell version 5. Please see the requirements for PSScriptAnalyzer here: https://github.com/PowerShell/PSScriptAnalyzer' 48 | } -------------------------------------------------------------------------------- /Scripts/Get-WUSettings.ps1: -------------------------------------------------------------------------------- 1 | # https://p0w3rsh3ll.wordpress.com/2013/01/09/get-windows-update-client-configuration/ 2 | 3 | [cmdletbinding()] 4 | Param( 5 | [switch]$viaRegistry=$false 6 | ) 7 | Begin { 8 | # Get the Operating system 9 | $OSVersion = [environment]::OSVersion.Version 10 | 11 | # Initialize object 12 | $WshShell = New-Object -ComObject Wscript.Shell 13 | 14 | $polkey = 'HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' 15 | $stdkey = 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update' 16 | } 17 | Process { 18 | if ($viaRegistry) { 19 | try { 20 | $AUEnabled = $WshShell.RegRead("$polkey\NoAutoUpdate") 21 | } catch { 22 | # if this value is absent, it means it's turned on 23 | $AUEnabled = 0 24 | } 25 | Switch ($AUEnabled) { 26 | 1 {$AUEnabled = $false} 27 | 0 {$AUEnabled = $true } 28 | } 29 | try { 30 | $AUOptions = $WshShell.RegRead("$polkey\AUOptions") 31 | } catch { 32 | try { 33 | $AUOptions = $WshShell.RegRead("$stdkey\AUOptions") 34 | } catch { 35 | $AUOptions = 0 36 | } 37 | } 38 | Switch ($AUOptions) { 39 | 0 {$AUNotificationLevel = 'Not Configured'} 40 | 1 {$AUNotificationLevel = 'Never check for updates'} 41 | 2 {$AUNotificationLevel = 'Notify Before Download'} 42 | 3 {$AUNotificationLevel = 'Notify Before Installation'} 43 | 4 {$AUNotificationLevel = 'Install updates automatically'} 44 | } 45 | try { 46 | $IncludeRecommendedUpdates = $WshShell.RegRead("$polkey\IncludeRecommendedUpdates") 47 | } catch { 48 | # if the value is absent we get it from 49 | $IncludeRecommendedUpdates = $WshShell.RegRead("$stdkey\IncludeRecommendedUpdates") 50 | } 51 | Switch ($IncludeRecommendedUpdates) { 52 | 0 {$GetRecommendedUpdates = $false} 53 | 1 {$GetRecommendedUpdates = $true} 54 | } 55 | try { 56 | $UseWUServerVal = $WshShell.RegRead("$polkey\UseWUServer") 57 | } catch { 58 | # if the value doesn't exist, it means that we don't use a WSUS server 59 | $UseWUServerVal = 0 60 | } 61 | Switch ($UseWUServerVal) { 62 | 1 {$UseWUServer = $true} 63 | 0 {$UseWUServer = $false } 64 | } 65 | # Create a default object with a subset of properties 66 | $obj = New-Object -TypeName psobject -Property @{ 67 | 'Is Automatic Update Enabled' = $AUEnabled 68 | 'Use a WSUS Server' = $UseWUServer 69 | 'Automatic Updates Notification' = $AUNotificationLevel; 70 | 'Receive recommended udpates' = $GetRecommendedUpdates; 71 | } 72 | if ($OSVersion -lt [version]'6.2') { 73 | try { 74 | $ScheduledInstallDay = $WshShell.RegRead("$polkey\ScheduledInstallDay") 75 | $ScheduledInstallTime = $WshShell.RegRead("$polkey\ScheduledInstallTime") 76 | } catch { 77 | try { 78 | $ScheduledInstallDay = $WshShell.RegRead("$stdkey\ScheduledInstallDay") 79 | $ScheduledInstallTime = $WshShell.RegRead("$stdkey\ScheduledInstallTime") 80 | } catch { 81 | # Absent = Every Day @3 AM but I prefer to leave it blank in the returned object 82 | } 83 | } 84 | Switch ($ScheduledInstallDay) { 85 | 0 {$InstallDay = 'Every Day'} 86 | 1 {$InstallDay = 'Every Sunday'} 87 | 2 {$InstallDay = 'Every Monday'} 88 | 3 {$InstallDay = 'Every Tuesday'} 89 | 4 {$InstallDay = 'Every Wednesday'} 90 | 5 {$InstallDay = 'Every Thursday'} 91 | 6 {$InstallDay = 'Every Friday'} 92 | 7 {$InstallDay = 'Every Saturday'} 93 | } 94 | if ($ScheduledInstallTime) { 95 | $InstallTime = New-TimeSpan -Hours $ScheduledInstallTime 96 | } 97 | $obj | Add-Member -MemberType NoteProperty -Name 'Install Frequency' -Value $InstallDay 98 | $obj | Add-Member -MemberType NoteProperty -Name 'Install Time' -Value $InstallTime 99 | } else { 100 | # These properties don't exist anymore on Windows 8 101 | } 102 | # Add extra properties 103 | if ($UseWUServer) { 104 | try { 105 | $WUServer = $WshShell.RegRead('HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\WUServer') 106 | $WUStatusServer = $WshShell.RegRead('HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\WUStatusServer') 107 | } catch { 108 | # we silently fail 109 | } 110 | $obj | Add-Member -MemberType NoteProperty -Name 'WSUS Server' -Value $WUServer 111 | $obj | Add-Member -MemberType NoteProperty -Name 'WSUS Status URL' -Value $WUStatusServer 112 | } 113 | try { 114 | $OptinGUID = $WshShell.RegRead('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\DefaultService') 115 | } catch { 116 | # Fail silently 117 | } 118 | if ($OptinGUID -eq '7971f918-a847-4430-9279-4a52d1efe18d') { 119 | $obj | Add-Member -MemberType NoteProperty -Name "Opted-in Microsoft Update" -Value $true 120 | } else { 121 | $obj | Add-Member -MemberType NoteProperty -Name "Opted-in Microsoft Update" -Value $false 122 | } 123 | # Return our object 124 | $obj 125 | 126 | } else { 127 | # We use Com Object 128 | $COMWUSettings = (New-Object -ComObject Microsoft.Update.AutoUpdate).Settings 129 | # Settings might be controlled by GPO 130 | if ($COMWUSettings.ReadOnly) { 131 | # Use the registry 132 | Get-WUSettings -viaRegistry:$true 133 | break 134 | } else { 135 | $UseWUServer = $false 136 | } 137 | Switch ($COMWUSettings.NotificationLevel) { 138 | 0 {$AUNotificationLevel = 'Not Configured'} 139 | 1 {$AUNotificationLevel = 'Never check for updates'} 140 | 2 {$AUNotificationLevel = 'Notify Before Download'} 141 | 3 {$AUNotificationLevel = 'Notify Before Installation'} 142 | 4 {$AUNotificationLevel = 'Install updates automatically'} 143 | } 144 | $isAUenabled = (New-Object -ComObject Microsoft.Update.AutoUpdate).serviceEnabled 145 | $obj = New-Object -TypeName psobject -Property @{ 146 | 'Is Automatic Update Enabled' = $isAUenabled 147 | 'Automatic Updates Notification' = $AUNotificationLevel; 148 | 'Use a WSUS Server' = $UseWUServer 149 | 'Receive recommended udpates' = $COMWUSettings.IncludeRecommendedUpdates; 150 | } 151 | if ($OSVersion -lt [version]'6.2') { 152 | Switch ($COMWUSettings.ScheduledInstallationDay) { 153 | 0 {$InstallDay = 'Every Day'} 154 | 1 {$InstallDay = 'Every Sunday'} 155 | 2 {$InstallDay = 'Every Monday'} 156 | 3 {$InstallDay = 'Every Tuesday'} 157 | 4 {$InstallDay = 'Every Wednesday'} 158 | 5 {$InstallDay = 'Every Thursday'} 159 | 6 {$InstallDay = 'Every Friday'} 160 | 7 {$InstallDay = 'Every Saturday'} 161 | } 162 | if ($COMWUSettings.ScheduledInstallationTime) { 163 | $InstallTime = New-TimeSpan -Hours $COMWUSettings.ScheduledInstallationTime 164 | } 165 | $obj | Add-Member -MemberType NoteProperty -Name 'Install Frequency' -Value $InstallDay 166 | $obj | Add-Member -MemberType NoteProperty -Name 'Install Time' -Value $InstallTime 167 | 168 | } else { 169 | # not available on W8 170 | } 171 | (New-Object -ComObject Microsoft.Update.ServiceManager).services | ForEach-Object { 172 | if ($_.IsDefaultAUService) { 173 | $OptinGUID = $_.ServiceID 174 | } 175 | } 176 | if ($OptinGUID -eq '7971f918-a847-4430-9279-4a52d1efe18d') { 177 | $obj | Add-Member -MemberType NoteProperty -Name "Opted-in Microsoft Update" -Value $true 178 | } else { 179 | $obj | Add-Member -MemberType NoteProperty -Name "Opted-in Microsoft Update" -Value $false 180 | } 181 | # return 182 | $obj 183 | } 184 | } 185 | End {} -------------------------------------------------------------------------------- /Scripts/Get-vClusterCapacity.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | Connect to vSphere vCenter Server and return the vSphere Cluster and 5 | vSphere SDRS Cluster resources capacity information. 6 | 7 | .DESCRIPTION 8 | 9 | Allows the administrator to connect to vSphere vCenter Server and 10 | retrieve the cluster resources capacity information based on 11 | calculating the actual resources capacity, usable available resources 12 | capacity, consumed resources capacity, virtual vs physical resource ratio 13 | and overcommit percentage. 14 | 15 | .PARAMETER vCenterName 16 | 17 | Specify VMware vSphere vCenter Hostname 18 | 19 | .PARAMETER Username 20 | 21 | Specify the Username for VMware vSphere vCenter Server 22 | 23 | .PARAMETER Password 24 | 25 | Specify the Password for VMware vSphere vCenter Server 26 | 27 | .PARAMETER Cluster 28 | 29 | Specify the VMware vSphere Cluster Name 30 | 31 | .PARAMETER DatastoreCluster 32 | 33 | Specify the VMware vSphere Datastore Cluster Name 34 | 35 | .EXAMPLE 36 | 37 | C:\> Import-Module ` 38 | -Global D:\Temp\Get-vClusterCapacity.psm1 ; 39 | 40 | C:\> Get-vClusterCapacity ` 41 | -vCenterName vCenter.vmware.local ` 42 | -Cluster vSphereCluster ` 43 | -DatastoreCluster vSphere_Datastore_Cluster ` 44 | -Username vmware.local\username ` 45 | -Password Password123 ; 46 | .INPUTS 47 | 48 | The Get-vClusterCapacity Cmdlet will accept all string inputs from pipeline. 49 | 50 | .OUTPUTS 51 | 52 | The Get-vClusterCapacity Cmdlet will output following details below: 53 | 54 | Cluster Name : vSphereCluster 55 | Cluster ESXi Host Names : {vsphere-esx022.vmware.local, 56 | vsphere-esx057.vmware.local, 57 | vsphere-esx038.vmware.local, 58 | vsphere-esx051.vmware.local...} 59 | Cluster CPU Cores : 96 60 | Cluster Total Allocated vCPUs : 537 61 | Cluster Total PoweredOn vCPUs : 315 62 | Cluster Total PoweredOff vCPUs : 63 | Cluster vCPU/Core Ratio : 3.281 64 | Cluster CPU Overcommit (%) : 228.125 65 | Cluster Physical RAM (GB) : 1535.59 66 | Cluster Total Allocated vRAM (GB) : 1792.98 67 | Cluster Total PoweredOn vRAM (GB) : 1063 68 | Cluster vRAM/Physical RAM Ratio : 0.692 69 | Cluster RAM Overcommit (%) : -30.78 70 | Datastore Cluster Name : vSphere_Datastore_Cluster 71 | Datastore Cluster Datastore Names : {vSphere_Datastore_VMFS0001_00, vSphere_Datastore_VMFS0001_01, 72 | vSphere_Datastore_VMFS0001_02, vSphere_Datastore_VMFS0001_03...} 73 | Datastore Cluster Capacity (GB) : 47098.25 74 | Datastore Cluster Reservation (GB) : 4709.82 75 | Datastore Cluster Usable Capacity (GB) : 42388.42 76 | Datastore Cluster PoweredOn Guest Used Space (GB) : 18885.69 77 | Datastore Cluster PoweredOff Guest Used Space (GB) : 28212.56 78 | Datastore Cluster Used Space (GB) : 31410.96 79 | Datastore Cluster Provisioned Space (GB) : 32197.46 80 | Datastore Cluster Provisioned / Capacity Ratio : 0.684 81 | Datastore Cluster Provisioned / Capacity Ratio - Reservation : 0.76 82 | Datastore Cluster Storage Overcommit (%) : -31.64 83 | 84 | .COMPONENT 85 | 86 | This Get-vClusterCapacity Cmdlet requires VMware PowerCLI to be installed in order to provide the PowerShell capabilities to 87 | connect to vSphere vCenter and interrogate the environment. 88 | 89 | .NOTES 90 | 91 | Title : PowerShell Get VMWare vSphere Cluster and Datastore Cluster Capacity 92 | FileName: Get-vClusterCapacity.psm1 93 | Author : Ryen Kia Zhi Tang 94 | Date : 22/06/2016 95 | Blog : ryentang.wordpress.com 96 | Version : 1.0 97 | 98 | .LINK 99 | 100 | Microsoft TechNet Gallery - Get-vClusterCapacity Cmdlet for VMware vSphere vCenter: https://gallery.technet.microsoft.com/Get-vClusterCapacity-a2ab9755 101 | 102 | #> 103 | Param( 104 | 105 | [Parameter( 106 | Mandatory=$True, 107 | ValueFromPipeline=$True, 108 | ValueFromPipelineByPropertyName=$True)] 109 | [Alias('VIServer')] 110 | [String] $vCenterName = $env:COMPUTERNAME , 111 | 112 | [Parameter( 113 | Mandatory=$True, 114 | ValueFromPipeline=$True, 115 | ValueFromPipelineByPropertyName=$True)] 116 | [Alias('U')] 117 | [String] $Username, 118 | 119 | [Parameter( 120 | Mandatory=$True, 121 | ValueFromPipeline=$True, 122 | ValueFromPipelineByPropertyName=$True)] 123 | [Alias('P')] 124 | [String] $Password, 125 | 126 | [Parameter( 127 | Mandatory=$True, 128 | ValueFromPipeline=$True, 129 | ValueFromPipelineByPropertyName=$True)] 130 | [Alias('HClu')] 131 | [String] $Cluster, 132 | 133 | [Parameter( 134 | Mandatory=$True, 135 | ValueFromPipeline=$True, 136 | ValueFromPipelineByPropertyName=$True)] 137 | [Alias('DataClu')] 138 | [String] $DatastoreCluster 139 | 140 | ) ; 141 | 142 | BEGIN { 143 | 144 | # Display progress 145 | Write-Progress ` 146 | -Id 1 ` 147 | -Activity 'Working on adding VMware PowerCLI PSSnapin...' ` 148 | -Status 'Validating VMware PowerCLI is installed.' ; 149 | 150 | if((Get-PSSnapin -Name VMware.VIMAutomation.Core -Registered) -ne (Out-Null)) { 151 | 152 | # Display progress 153 | Write-Progress ` 154 | -Id 1 ` 155 | -Activity 'Working on adding VMware PowerCLI PSSnapin...' ` 156 | -Status 'Adding VMware PowerCLI PSSnapin. Please wait...' ; 157 | 158 | Try { 159 | 160 | # Add VMware PowerShell CLI Snapin 161 | Add-PSSnapin ` 162 | -Name VMware.VIMAutomation.Core ` 163 | -ErrorAction Stop ; 164 | 165 | }Catch{ 166 | 167 | Write-Host 'Oops. Something went wrong. Please kindly ensure that VMware PowerCLI is installed properly.' ` 168 | -ForegroundColor Red ; 169 | 170 | Return (Out-Null) ; 171 | 172 | } ; 173 | 174 | # Display progress 175 | Write-Progress ` 176 | -Id 1 ` 177 | -Activity 'Working on adding VMware PowerCLI PSSnapin...' ` 178 | -Status 'Added VMware PowerShell CLI Snapin.' ; 179 | 180 | }else{ 181 | 182 | # Display progress 183 | Write-Progress ` 184 | -Id 1 ` 185 | -Activity 'Working on adding VMware PowerCLI PSSnapin...' ` 186 | -Status 'Adding VMware PowerShell CLI PSSnapin failed. VMware.VIMAutomation.Core is not registered.' ` 187 | -Completed ; 188 | 189 | Write-Host 'Please kindly ensure that VMware PowerCLI is installed' ` 190 | -ForegroundColor Red ; 191 | 192 | Exit ; 193 | 194 | } ; 195 | 196 | # Create an array collection to store information for each individual ESXi host 197 | $ClusterPropertiesCollection = @() ; 198 | 199 | 200 | # Display progress 201 | Write-Progress ` 202 | -Id 1 ` 203 | -Activity 'Working on connecting to vCenter Server...' ` 204 | -Status ('Attempting to connect to vCenter Server [' + $vCenterName + ']. Please wait...') ; 205 | 206 | # Connect to vSphere vCenter 207 | $ObjConnection = Connect-VIServer ` 208 | -Server $vCenterName ` 209 | -User $Username ` 210 | -Password $Password ; 211 | 212 | if($ObjConnection.IsConnected -eq "True") { 213 | 214 | # Display progress 215 | Write-Progress ` 216 | -Id 1 ` 217 | -Activity 'Working on connecting to vCenter Server...' ` 218 | -Status ('Connected to vCenter Server [' + $vCenterName + ']') ; 219 | 220 | }else{ 221 | 222 | # Display progress 223 | Write-Progress ` 224 | -Id 1 ` 225 | -Activity 'Working on connecting to vCenter Server...' ` 226 | -Status ('Connection to vCenter Server [' + $vCenterName + '] failed') ` 227 | -Completed ; 228 | 229 | Write-Host 'Please kindly ensure that VMware vCenter Server is available' ` 230 | -ForegroundColor Red ; 231 | 232 | Exit ; 233 | 234 | } ; 235 | 236 | } ; 237 | 238 | PROCESS { 239 | 240 | # Display progress 241 | Write-Progress ` 242 | -Id 1 ` 243 | -Activity 'Working on collecting ESXi Hosts information...' ` 244 | -Status 'Attempting to collect ESXi Hosts properties. Please wait...' ; 245 | 246 | # Get a collection of ESXi hosts properties within the cluster 247 | $VMhosts = Get-Cluster ` 248 | -Name $Cluster | ` 249 | Get-VMHost ; 250 | 251 | if($VMhosts -ne (Out-Null)) { 252 | 253 | # Display progress 254 | Write-Progress ` 255 | -Id 1 ` 256 | -Activity 'Working on collecting ESXi Hosts information...' ` 257 | -Status 'Collected ESXi Hosts properties' ; 258 | 259 | }else{ 260 | 261 | # Display progress 262 | Write-Progress ` 263 | -Id 1 ` 264 | -Activity 'Working on collecting ESXi Hosts information...' ` 265 | -Status 'Collecting ESXi Hosts properties failed. Found no ESXi Host in Cluster.' ` 266 | -Completed ; 267 | 268 | # Exit 269 | Return (Out-Null) ; 270 | 271 | } ; 272 | 273 | # Create an array collection to store information for each individual ESXi host 274 | $VMHostPropertiesCollection = @() ; 275 | 276 | 277 | # Display progress 278 | Write-Progress ` 279 | -Id 1 ` 280 | -Activity 'Working on each ESXi Host information...' ` 281 | -Status 'Calculating every ESXi Host capacity. Please wait...' ; 282 | 283 | ForEach($VMhost in $VMhosts) { 284 | 285 | # Progress Bar 286 | $intProgressCount++ ; 287 | 288 | # Display progress 289 | Write-Progress ` 290 | -Id 100 ` 291 | -ParentId 1 ` 292 | -Activity 'Working on each ESXi Host information...' ` 293 | -Status ('Calculating each ESXi Host [' + $VMhost.Name + '] capacity. Please wait...') ` 294 | -PercentComplete (($intProgressCount/($VMhosts | Measure-Object).Count)*100) ` 295 | -CurrentOperation ([String] ([Math]::Round(($intProgressCount/($VMhosts | Measure-Object).Count)*100, 2)) + '% complete') ; 296 | 297 | # Get total amount of Powered On VM guest vCPUs per host 298 | $VMHostTotalPoweredOnVMGuestvCPUs = (Get-VM ` 299 | -Location $VMhost | ` 300 | Where-Object { $_.PowerState -eq "PoweredOn" } | ` 301 | Measure-Object NumCpu -Sum).Sum ; 302 | 303 | $VMHostPhysicalRAM = [Math]::Round($VMhost.MemoryTotalGB, 2) ; 304 | 305 | $VMHostTotalPoweredOnVMGuestvRAM = [Math]::Round((Get-VM ` 306 | -Location $VMhost | ` 307 | Where-Object { $_.PowerState -eq "PoweredOn" } | ` 308 | Measure-Object MemoryGB -Sum).Sum, 2) ; 309 | 310 | 311 | # Construct the properties for our custom object 312 | $VMHostProperties = ` 313 | @{ 314 | 315 | 'ESXi Hostname' = $VMhost.Name ; 316 | 317 | 'CPU Cores' = $VMhost.NumCpu ; 318 | 319 | 'Total Allocated vCPUs' = (Get-VM ` 320 | -Location $VMhost | ` 321 | Measure-Object NumCpu -Sum).Sum ; 322 | 323 | 'Total PoweredOn vCPUs' = If($VMHostTotalPoweredOnVMGuestvCPUs) { ` 324 | $VMHostTotalPoweredOnVMGuestvCPUs ; ` 325 | } Else { [Int] "0" ; } ; 326 | 327 | 'vCPU/Core Ratio' = If($VMHostTotalPoweredOnVMGuestvCPUs) { ` 328 | [Math]::Round(($VMHostTotalPoweredOnVMGuestvCPUs / $VMhost.NumCpu), 3) ; ` 329 | } Else { Out-Null ; } ; 330 | 331 | 'CPU Overcommit (%)' = If($VMHostTotalPoweredOnVMGuestvCPUs) { ` 332 | [Math]::Round(100*(($VMHostTotalPoweredOnVMGuestvCPUs - $VMhost.NumCpu) / $VMhost.NumCpu), 3) ; ` 333 | } Else { Out-Null ; } ; 334 | 335 | 'Physical RAM (GB)' = $VMHostPhysicalRAM ; 336 | 337 | 'Total Allocated vRAM (GB)' = [Math]::Round((Get-VM ` 338 | -Location $VMhost | ` 339 | Measure-Object MemoryGB -Sum).Sum, 2) ; 340 | 341 | 'Total PoweredOn vRAM (GB)' = If($VMHostTotalPoweredOnVMGuestvRAM) { ` 342 | $VMHostTotalPoweredOnVMGuestvRAM ; ` 343 | } Else { [Int] "0" ; } ; 344 | 345 | 'vRAM/Physical RAM Ratio' = If($VMHostTotalPoweredOnVMGuestvRAM) { ` 346 | [Math]::Round(($VMHostTotalPoweredOnVMGuestvRAM / $VMHostPhysicalRAM), 3) ; ` 347 | } Else { Out-Null ; } ; 348 | 349 | 'RAM Overcommit (%)' = If($VMHostTotalPoweredOnVMGuestvRAM) { ` 350 | [Math]::Round(100*(($VMHostTotalPoweredOnVMGuestvRAM - $VMHostPhysicalRAM) / $VMHostPhysicalRAM), 2) ; ` 351 | } Else { Out-Null ; } ; 352 | 353 | } ; 354 | 355 | # Construct a custom object contain the list of properties above 356 | $ObjVMHostProperties = New-Object ` 357 | -TypeName PSObject ` 358 | -Property $VMHostProperties ; 359 | 360 | # Store each ESXi Host properties to a collection 361 | $VMHostPropertiesCollection += $ObjVMHostProperties ; 362 | 363 | # Display progress 364 | Write-Progress ` 365 | -Id 100 ` 366 | -ParentId 1 ` 367 | -Activity 'Working on each ESXi Host information...' ` 368 | -Status ('Calculated ESXi Host [' + $VMhost.Name + '] capacity.') ` 369 | -Completed ; 370 | 371 | } ; 372 | 373 | # Display progress 374 | Write-Progress ` 375 | -Id 1 ` 376 | -Activity 'Working on vSphere Cluster information...' ` 377 | -Status ('Calculating Cluster [' + $Cluster + '] resource capacity. Please wait...') ; 378 | 379 | $ClusterTotalPoweredOnGuestvCPUs = (Get-VM ` 380 | -Location $Cluster | ` 381 | Where-Object { $_.PowerState -eq "PoweredOn" } | ` 382 | Measure-Object NumCpu -Sum).Sum ; 383 | 384 | $ClusterTotalCPUCores = ($VMhosts | ` 385 | Measure-Object NumCpu -Sum).Sum ; 386 | 387 | $ClusterTotalPoweredOnGuestvRAM = [Math]::Round((Get-VM ` 388 | -Location $Cluster | ` 389 | Where-Object { $_.PowerState -eq "PoweredOn" } | ` 390 | Measure-Object MemoryGB -Sum).Sum, 2) ; 391 | 392 | $ClusterTotalPhysicalRAM = [Math]::Round(($VMhosts | ` 393 | Measure-Object MemoryTotalGB -Sum).Sum, 2) ; 394 | 395 | # Display progress 396 | Write-Progress ` 397 | -Id 1 ` 398 | -Activity 'Working on vSphere Datastore Cluster information...' ` 399 | -Status ('Calculating Datastore Cluster [' + $Cluster + '] resource capacity. Please wait...') ; 400 | 401 | $DatastoreClusterCapacity = [Math]::Round(((Get-DatastoreCluster ` 402 | -Name $DatastoreCluster).CapacityGB | ` 403 | Measure-Object -Sum).Sum, 2) ; 404 | 405 | $VMDatastoreProperties = Get-VM ` 406 | -Datastore $DatastoreCluster ; 407 | 408 | $DatastoresProperties = Get-Datastore ` 409 | -Location $DatastoreCluster ; 410 | 411 | $DatastoreClusterUsedSpace = [Math]::Round((($VMDatastoreProperties | Select -Expand UsedSpaceGB) | ` 412 | Measure-Object -Sum).Sum, 2) ; 413 | 414 | $DatastoreClusterPoweredOnGuestUsedSpace = [Math]::Round((($VMDatastoreProperties | ` 415 | Where-Object { $_.PowerState -eq "PoweredOn" } | Select -Expand UsedSpaceGB) | ` 416 | Measure-Object -Sum).Sum, 2) ; 417 | 418 | $DatastoreClusterProvisionedSpace = [Math]::Round((($VMDatastoreProperties | Select -Expand ProvisionedSpaceGB) | ` 419 | Measure-Object -Sum).Sum, 2) ; 420 | 421 | # Building a custom object specific to the -Cluster parameter 422 | $ClusterProperties = [Ordered] ` 423 | @{ 424 | 425 | 'Cluster Name' = $Cluster ; 426 | 427 | 'Cluster ESXi Host Names' = $VMHostPropertiesCollection.'ESXi Hostname' ; 428 | 429 | 'Cluster CPU Cores' = $ClusterTotalCPUCores ; 430 | 431 | 'Cluster Total Allocated vCPUs' = ($VMHostPropertiesCollection.'Total Allocated vCPUs' | ` 432 | Measure-Object -Sum).Sum ; 433 | 434 | 'Cluster Total PoweredOn vCPUs' = If($ClusterTotalPoweredOnGuestvCPUs) { ` 435 | $ClusterTotalPoweredOnGuestvCPUs ; ` 436 | } Else { [Int] "0" ; } ; 437 | 438 | 'Cluster Total PoweredOff vCPUs' = If($ClusterTotalPoweredOnGuestvCPUs) { ` 439 | (($VMHostPropertiesCollection.'Total Allocated vCPUs' | ` 440 | Measure-Object -Sum).Sum - $ClusterTotalPoweredOnGuestvCPUs) ; ` 441 | } Else { [Int] "0" ; } ; 442 | 443 | 'Cluster vCPU/Core Ratio' = If($ClusterTotalPoweredOnGuestvCPUs) { ` 444 | [Math]::Round(($ClusterTotalPoweredOnGuestvCPUs / $ClusterTotalCPUCores), 3) ; ` 445 | } Else { Out-Null ; } ; 446 | 447 | 'Cluster CPU Overcommit (%)' = If($ClusterTotalPoweredOnGuestvCPUs) { ` 448 | [Math]::Round(100*(( $ClusterTotalPoweredOnGuestvCPUs - $ClusterTotalCPUCores) / $ClusterTotalCPUCores), 3) ; ` 449 | } Else { Out-Null ; } ; 450 | 451 | 'Cluster Physical RAM (GB)' = $ClusterTotalPhysicalRAM ; 452 | 453 | 'Cluster Total Allocated vRAM (GB)' = [Math]::Round(($VMHostPropertiesCollection.'Total Allocated vRAM (GB)' | ` 454 | Measure-Object -Sum).Sum, 2) ; 455 | 456 | 'Cluster Total PoweredOn vRAM (GB)' = If($ClusterTotalPoweredOnGuestvRAM) { ` 457 | $ClusterTotalPoweredOnGuestvRAM ` 458 | } Else { [Int] "0" ; } ; 459 | 460 | 'Cluster vRAM/Physical RAM Ratio' = If($ClusterTotalPoweredOnGuestvRAM) { ` 461 | [Math]::Round(($ClusterTotalPoweredOnGuestvRAM / $ClusterTotalPhysicalRAM), 3) ; ` 462 | } Else { Out-Null ; } ; 463 | 464 | 'Cluster RAM Overcommit (%)' = If($ClusterTotalPoweredOnGuestvRAM) { ` 465 | [Math]::Round(100*(( $ClusterTotalPoweredOnGuestvRAM - $ClusterTotalPhysicalRAM) / $ClusterTotalPhysicalRAM), 2) ; ` 466 | } Else { Out-Null ; } ; 467 | 468 | 'Datastore Cluster Name' = $DatastoreCluster ; 469 | 470 | 'Datastore Cluster Datastore Names' = $DatastoresProperties.Name ; 471 | 472 | 'Datastore Cluster Capacity (GB)' = $DatastoreClusterCapacity ; 473 | 474 | 'Datastore Cluster Reservation (GB)' = [Math]::Round(($DatastoreClusterCapacity * 0.1), 2) ; 475 | 476 | 'Datastore Cluster Usable Capacity (GB)' = [Math]::Round(($DatastoreClusterCapacity - ($DatastoreClusterCapacity * 0.1)), 2) ; 477 | 478 | 'Datastore Cluster PoweredOn Guest Used Space (GB)' = $DatastoreClusterPoweredOnGuestUsedSpace ; 479 | 480 | 'Datastore Cluster PoweredOff Guest Used Space (GB)' = [Math]::Round(($DatastoreClusterCapacity - $DatastoreClusterPoweredOnGuestUsedSpace), 2) ; 481 | 482 | 'Datastore Cluster Used Space (GB)' = If($DatastoreClusterUsedSpace){ ` 483 | $DatastoreClusterUsedSpace ; ` 484 | } Else { [Int] "0" ; } ; 485 | 486 | 'Datastore Cluster Provisioned Space (GB)' = If($DatastoreClusterProvisionedSpace) { ` 487 | $DatastoreClusterProvisionedSpace ; ` 488 | } Else { [Int] "0" ; } ; 489 | 490 | 'Datastore Cluster Provisioned / Capacity Ratio' = If($DatastoreClusterProvisionedSpace) { ` 491 | [Math]::Round(($DatastoreClusterProvisionedSpace / $DatastoreClusterCapacity), 3) ; ` 492 | } Else { Out-Null ; } ; 493 | 494 | 'Datastore Cluster Provisioned / Capacity Ratio - Reservation' = If($DatastoreClusterProvisionedSpace) { ` 495 | [Math]::Round(($DatastoreClusterProvisionedSpace / ($DatastoreClusterCapacity - ($DatastoreClusterCapacity * 0.1 ))), 3) ; ` 496 | } Else { Out-Null ; } ; 497 | 498 | 'Datastore Cluster Storage Overcommit (%)' = If($DatastoreClusterProvisionedSpace) { ` 499 | [Math]::Round(100*(( $DatastoreClusterProvisionedSpace - $DatastoreClusterCapacity) / $DatastoreClusterCapacity), 2) ; ` 500 | } Else { Out-Null ; } ; 501 | 502 | } ; 503 | 504 | # Construct a custom object contain the list of properties above 505 | $ObjClusterProperties = New-Object ` 506 | -TypeName PSObject ` 507 | -Property $ClusterProperties ; 508 | 509 | #$ClusterPropertiesCollection += $ObjClusterProperties ; 510 | Return $ObjClusterProperties ; 511 | 512 | } ; 513 | 514 | END { 515 | 516 | # Disconnect from the vSphere vCenter 517 | Disconnect-VIServer ` 518 | -Server $vCenterName ` 519 | -Confirm:$False ; 520 | 521 | # Remove VMware PowerShell CLI Snapin 522 | Remove-PSSnapin ` 523 | -Name VMware.VimAutomation.Core ` 524 | -Confirm:$False ; 525 | 526 | } ; 527 | 528 | -------------------------------------------------------------------------------- /Scripts/Invoke-Parallel.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-Parallel { 2 | <# 3 | .SYNOPSIS 4 | Function to control parallel processing using runspaces 5 | 6 | .DESCRIPTION 7 | Function to control parallel processing using runspaces 8 | 9 | Note that each runspace will not have access to variables and commands loaded in your session or in other runspaces by default. 10 | This behaviour can be changed with parameters. 11 | 12 | .PARAMETER ScriptFile 13 | File to run against all input objects. Must include parameter to take in the input object, or use $args. Optionally, include parameter to take in parameter. Example: C:\script.ps1 14 | 15 | .PARAMETER ScriptBlock 16 | Scriptblock to run against all computers. 17 | 18 | You may use $Using: language in PowerShell 3 and later. 19 | 20 | The parameter block is added for you, allowing behaviour similar to foreach-object: 21 | Refer to the input object as $_. 22 | Refer to the parameter parameter as $parameter 23 | 24 | .PARAMETER InputObject 25 | Run script against these specified objects. 26 | 27 | .PARAMETER Parameter 28 | This object is passed to every script block. You can use it to pass information to the script block; for example, the path to a logging folder 29 | 30 | Reference this object as $parameter if using the scriptblock parameterset. 31 | 32 | .PARAMETER ImportVariables 33 | If specified, get user session variables and add them to the initial session state 34 | 35 | .PARAMETER ImportModules 36 | If specified, get loaded modules and pssnapins, add them to the initial session state 37 | 38 | .PARAMETER Throttle 39 | Maximum number of threads to run at a single time. 40 | 41 | .PARAMETER SleepTimer 42 | Milliseconds to sleep after checking for completed runspaces and in a few other spots. I would not recommend dropping below 200 or increasing above 500 43 | 44 | .PARAMETER RunspaceTimeout 45 | Maximum time in seconds a single thread can run. If execution of your code takes longer than this, it is disposed. Default: 0 (seconds) 46 | 47 | WARNING: Using this parameter requires that maxQueue be set to throttle (it will be by default) for accurate timing. Details here: 48 | http://gallery.technet.microsoft.com/Run-Parallel-Parallel-377fd430 49 | 50 | .PARAMETER NoCloseOnTimeout 51 | Do not dispose of timed out tasks or attempt to close the runspace if threads have timed out. This will prevent the script from hanging in certain situations where threads become non-responsive, at the expense of leaking memory within the PowerShell host. 52 | 53 | .PARAMETER MaxQueue 54 | Maximum number of powershell instances to add to runspace pool. If this is higher than $throttle, $timeout will be inaccurate 55 | 56 | If this is equal or less than throttle, there will be a performance impact 57 | 58 | The default value is $throttle times 3, if $runspaceTimeout is not specified 59 | The default value is $throttle, if $runspaceTimeout is specified 60 | 61 | .PARAMETER LogFile 62 | Path to a file where we can log results, including run time for each thread, whether it completes, completes with errors, or times out. 63 | 64 | .PARAMETER Quiet 65 | Disable progress bar. 66 | 67 | .EXAMPLE 68 | Each example uses Test-ForPacs.ps1 which includes the following code: 69 | param($computer) 70 | 71 | if(test-connection $computer -count 1 -quiet -BufferSize 16){ 72 | $object = [pscustomobject] @{ 73 | Computer=$computer; 74 | Available=1; 75 | Kodak=$( 76 | if((test-path "\\$computer\c$\users\public\desktop\Kodak Direct View Pacs.url") -or (test-path "\\$computer\c$\documents and settings\all users 77 | 78 | \desktop\Kodak Direct View Pacs.url") ){"1"}else{"0"} 79 | ) 80 | } 81 | } 82 | else{ 83 | $object = [pscustomobject] @{ 84 | Computer=$computer; 85 | Available=0; 86 | Kodak="NA" 87 | } 88 | } 89 | 90 | $object 91 | 92 | .EXAMPLE 93 | Invoke-Parallel -scriptfile C:\public\Test-ForPacs.ps1 -inputobject $(get-content C:\pcs.txt) -runspaceTimeout 10 -throttle 10 94 | 95 | Pulls list of PCs from C:\pcs.txt, 96 | Runs Test-ForPacs against each 97 | If any query takes longer than 10 seconds, it is disposed 98 | Only run 10 threads at a time 99 | 100 | .EXAMPLE 101 | Invoke-Parallel -scriptfile C:\public\Test-ForPacs.ps1 -inputobject c-is-ts-91, c-is-ts-95 102 | 103 | Runs against c-is-ts-91, c-is-ts-95 (-computername) 104 | Runs Test-ForPacs against each 105 | 106 | .EXAMPLE 107 | $stuff = [pscustomobject] @{ 108 | ContentFile = "windows\system32\drivers\etc\hosts" 109 | Logfile = "C:\temp\log.txt" 110 | } 111 | 112 | $computers | Invoke-Parallel -parameter $stuff { 113 | $contentFile = join-path "\\$_\c$" $parameter.contentfile 114 | Get-Content $contentFile | 115 | set-content $parameter.logfile 116 | } 117 | 118 | This example uses the parameter argument. This parameter is a single object. To pass multiple items into the script block, we create a custom object (using a PowerShell v3 language) with properties we want to pass in. 119 | 120 | Inside the script block, $parameter is used to reference this parameter object. This example sets a content file, gets content from that file, and sets it to a predefined log file. 121 | 122 | .EXAMPLE 123 | $test = 5 124 | 1..2 | Invoke-Parallel -ImportVariables {$_ * $test} 125 | 126 | Add variables from the current session to the session state. Without -ImportVariables $Test would not be accessible 127 | 128 | .EXAMPLE 129 | $test = 5 130 | 1..2 | Invoke-Parallel {$_ * $Using:test} 131 | 132 | Reference a variable from the current session with the $Using: syntax. Requires PowerShell 3 or later. Note that -ImportVariables parameter is no longer necessary. 133 | 134 | .FUNCTIONALITY 135 | PowerShell Language 136 | 137 | .NOTES 138 | Credit to Boe Prox for the base runspace code and $Using implementation 139 | http://learn-powershell.net/2012/05/10/speedy-network-information-query-using-powershell/ 140 | http://gallery.technet.microsoft.com/scriptcenter/Speedy-Network-Information-5b1406fb#content 141 | https://github.com/proxb/PoshRSJob/ 142 | 143 | Credit to T Bryce Yehl for the Quiet and NoCloseOnTimeout implementations 144 | 145 | Credit to Sergei Vorobev for the many ideas and contributions that have improved functionality, reliability, and ease of use 146 | 147 | .LINK 148 | https://github.com/RamblingCookieMonster/Invoke-Parallel 149 | #> 150 | [cmdletbinding(DefaultParameterSetName='ScriptBlock')] 151 | Param ( 152 | [Parameter(Mandatory=$false,position=0,ParameterSetName='ScriptBlock')] 153 | [System.Management.Automation.ScriptBlock]$ScriptBlock, 154 | 155 | [Parameter(Mandatory=$false,ParameterSetName='ScriptFile')] 156 | [ValidateScript({test-path $_ -pathtype leaf})] 157 | $ScriptFile, 158 | 159 | [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 160 | [Alias('CN','__Server','IPAddress','Server','ComputerName')] 161 | [PSObject]$InputObject, 162 | 163 | [PSObject]$Parameter, 164 | 165 | [switch]$ImportVariables, 166 | 167 | [switch]$ImportModules, 168 | 169 | [int]$Throttle = 20, 170 | 171 | [int]$SleepTimer = 200, 172 | 173 | [int]$RunspaceTimeout = 0, 174 | 175 | [switch]$NoCloseOnTimeout = $false, 176 | 177 | [int]$MaxQueue, 178 | 179 | [validatescript({Test-Path (Split-Path $_ -parent)})] 180 | [string]$LogFile = "C:\temp\log.log", 181 | 182 | [switch] $Quiet = $false 183 | ) 184 | 185 | Begin { 186 | 187 | #No max queue specified? Estimate one. 188 | #We use the script scope to resolve an odd PowerShell 2 issue where MaxQueue isn't seen later in the function 189 | if( -not $PSBoundParameters.ContainsKey('MaxQueue') ) 190 | { 191 | if($RunspaceTimeout -ne 0){ $script:MaxQueue = $Throttle } 192 | else{ $script:MaxQueue = $Throttle * 3 } 193 | } 194 | else 195 | { 196 | $script:MaxQueue = $MaxQueue 197 | } 198 | 199 | Write-Verbose "Throttle: '$throttle' SleepTimer '$sleepTimer' runSpaceTimeout '$runspaceTimeout' maxQueue '$maxQueue' logFile '$logFile'" 200 | 201 | #If they want to import variables or modules, create a clean runspace, get loaded items, use those to exclude items 202 | if ($ImportVariables -or $ImportModules) 203 | { 204 | $StandardUserEnv = [powershell]::Create().addscript({ 205 | 206 | #Get modules and snapins in this clean runspace 207 | $Modules = Get-Module | Select -ExpandProperty Name 208 | $Snapins = Get-PSSnapin | Select -ExpandProperty Name 209 | 210 | #Get variables in this clean runspace 211 | #Called last to get vars like $? into session 212 | $Variables = Get-Variable | Select -ExpandProperty Name 213 | 214 | #Return a hashtable where we can access each. 215 | @{ 216 | Variables = $Variables 217 | Modules = $Modules 218 | Snapins = $Snapins 219 | } 220 | }).invoke()[0] 221 | 222 | if ($ImportVariables) { 223 | #Exclude common parameters, bound parameters, and automatic variables 224 | Function _temp {[cmdletbinding()] param() } 225 | $VariablesToExclude = @( (Get-Command _temp | Select -ExpandProperty parameters).Keys + $PSBoundParameters.Keys + $StandardUserEnv.Variables ) 226 | Write-Verbose "Excluding variables $( ($VariablesToExclude | sort ) -join ", ")" 227 | 228 | # we don't use 'Get-Variable -Exclude', because it uses regexps. 229 | # One of the veriables that we pass is '$?'. 230 | # There could be other variables with such problems. 231 | # Scope 2 required if we move to a real module 232 | $UserVariables = @( Get-Variable | Where { -not ($VariablesToExclude -contains $_.Name) } ) 233 | Write-Verbose "Found variables to import: $( ($UserVariables | Select -expandproperty Name | Sort ) -join ", " | Out-String).`n" 234 | 235 | } 236 | 237 | if ($ImportModules) 238 | { 239 | $UserModules = @( Get-Module | Where {$StandardUserEnv.Modules -notcontains $_.Name -and (Test-Path $_.Path -ErrorAction SilentlyContinue)} | Select -ExpandProperty Path ) 240 | $UserSnapins = @( Get-PSSnapin | Select -ExpandProperty Name | Where {$StandardUserEnv.Snapins -notcontains $_ } ) 241 | } 242 | } 243 | 244 | #region functions 245 | 246 | Function Get-RunspaceData { 247 | [cmdletbinding()] 248 | param( [switch]$Wait ) 249 | 250 | #loop through runspaces 251 | #if $wait is specified, keep looping until all complete 252 | Do { 253 | 254 | #set more to false for tracking completion 255 | $more = $false 256 | 257 | #Progress bar if we have inputobject count (bound parameter) 258 | if (-not $Quiet) { 259 | Write-Progress -Activity "Running Query" -Status "Starting threads"` 260 | -CurrentOperation "$startedCount threads defined - $totalCount input objects - $script:completedCount input objects processed"` 261 | -PercentComplete $( Try { $script:completedCount / $totalCount * 100 } Catch {0} ) 262 | } 263 | 264 | #run through each runspace. 265 | Foreach($runspace in $runspaces) { 266 | 267 | #get the duration - inaccurate 268 | $currentdate = Get-Date 269 | $runtime = $currentdate - $runspace.startTime 270 | $runMin = [math]::Round( $runtime.totalminutes ,2 ) 271 | 272 | #set up log object 273 | $log = "" | select Date, Action, Runtime, Status, Details 274 | $log.Action = "Removing:'$($runspace.object)'" 275 | $log.Date = $currentdate 276 | $log.Runtime = "$runMin minutes" 277 | 278 | #If runspace completed, end invoke, dispose, recycle, counter++ 279 | If ($runspace.Runspace.isCompleted) { 280 | 281 | $script:completedCount++ 282 | 283 | #check if there were errors 284 | if($runspace.powershell.Streams.Error.Count -gt 0) { 285 | 286 | #set the logging info and move the file to completed 287 | $log.status = "CompletedWithErrors" 288 | Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] 289 | foreach($ErrorRecord in $runspace.powershell.Streams.Error) { 290 | Write-Error -ErrorRecord $ErrorRecord 291 | } 292 | } 293 | else { 294 | 295 | #add logging details and cleanup 296 | $log.status = "Completed" 297 | Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] 298 | } 299 | 300 | #everything is logged, clean up the runspace 301 | $runspace.powershell.EndInvoke($runspace.Runspace) 302 | $runspace.powershell.dispose() 303 | $runspace.Runspace = $null 304 | $runspace.powershell = $null 305 | 306 | } 307 | 308 | #If runtime exceeds max, dispose the runspace 309 | ElseIf ( $runspaceTimeout -ne 0 -and $runtime.totalseconds -gt $runspaceTimeout) { 310 | 311 | $script:completedCount++ 312 | $timedOutTasks = $true 313 | 314 | #add logging details and cleanup 315 | $log.status = "TimedOut" 316 | Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] 317 | Write-Error "Runspace timed out at $($runtime.totalseconds) seconds for the object:`n$($runspace.object | out-string)" 318 | 319 | #Depending on how it hangs, we could still get stuck here as dispose calls a synchronous method on the powershell instance 320 | if (!$noCloseOnTimeout) { $runspace.powershell.dispose() } 321 | $runspace.Runspace = $null 322 | $runspace.powershell = $null 323 | $completedCount++ 324 | 325 | } 326 | 327 | #If runspace isn't null set more to true 328 | ElseIf ($runspace.Runspace -ne $null ) { 329 | $log = $null 330 | $more = $true 331 | } 332 | 333 | #log the results if a log file was indicated 334 | if($logFile -and $log){ 335 | ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | out-file $LogFile -append 336 | } 337 | } 338 | 339 | #Clean out unused runspace jobs 340 | $temphash = $runspaces.clone() 341 | $temphash | Where { $_.runspace -eq $Null } | ForEach { 342 | $Runspaces.remove($_) 343 | } 344 | 345 | #sleep for a bit if we will loop again 346 | if($PSBoundParameters['Wait']){ Start-Sleep -milliseconds $SleepTimer } 347 | 348 | #Loop again only if -wait parameter and there are more runspaces to process 349 | } while ($more -and $PSBoundParameters['Wait']) 350 | 351 | #End of runspace function 352 | } 353 | 354 | #endregion functions 355 | 356 | #region Init 357 | 358 | if($PSCmdlet.ParameterSetName -eq 'ScriptFile') 359 | { 360 | $ScriptBlock = [scriptblock]::Create( $(Get-Content $ScriptFile | out-string) ) 361 | } 362 | elseif($PSCmdlet.ParameterSetName -eq 'ScriptBlock') 363 | { 364 | #Start building parameter names for the param block 365 | [string[]]$ParamsToAdd = '$_' 366 | if( $PSBoundParameters.ContainsKey('Parameter') ) 367 | { 368 | $ParamsToAdd += '$Parameter' 369 | } 370 | 371 | $UsingVariableData = $Null 372 | 373 | 374 | # This code enables $Using support through the AST. 375 | # This is entirely from Boe Prox, and his https://github.com/proxb/PoshRSJob module; all credit to Boe! 376 | 377 | if($PSVersionTable.PSVersion.Major -gt 2) 378 | { 379 | #Extract using references 380 | $UsingVariables = $ScriptBlock.ast.FindAll({$args[0] -is [System.Management.Automation.Language.UsingExpressionAst]},$True) 381 | 382 | If ($UsingVariables) 383 | { 384 | $List = New-Object 'System.Collections.Generic.List`1[System.Management.Automation.Language.VariableExpressionAst]' 385 | ForEach ($Ast in $UsingVariables) 386 | { 387 | [void]$list.Add($Ast.SubExpression) 388 | } 389 | 390 | $UsingVar = $UsingVariables | Group SubExpression | ForEach {$_.Group | Select -First 1} 391 | 392 | #Extract the name, value, and create replacements for each 393 | $UsingVariableData = ForEach ($Var in $UsingVar) { 394 | Try 395 | { 396 | $Value = Get-Variable -Name $Var.SubExpression.VariablePath.UserPath -ErrorAction Stop 397 | [pscustomobject]@{ 398 | Name = $Var.SubExpression.Extent.Text 399 | Value = $Value.Value 400 | NewName = ('$__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) 401 | NewVarName = ('__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) 402 | } 403 | } 404 | Catch 405 | { 406 | Write-Error "$($Var.SubExpression.Extent.Text) is not a valid Using: variable!" 407 | } 408 | } 409 | $ParamsToAdd += $UsingVariableData | Select -ExpandProperty NewName -Unique 410 | 411 | $NewParams = $UsingVariableData.NewName -join ', ' 412 | $Tuple = [Tuple]::Create($list, $NewParams) 413 | $bindingFlags = [Reflection.BindingFlags]"Default,NonPublic,Instance" 414 | $GetWithInputHandlingForInvokeCommandImpl = ($ScriptBlock.ast.gettype().GetMethod('GetWithInputHandlingForInvokeCommandImpl',$bindingFlags)) 415 | 416 | $StringScriptBlock = $GetWithInputHandlingForInvokeCommandImpl.Invoke($ScriptBlock.ast,@($Tuple)) 417 | 418 | $ScriptBlock = [scriptblock]::Create($StringScriptBlock) 419 | 420 | Write-Verbose $StringScriptBlock 421 | } 422 | } 423 | 424 | $ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param($($ParamsToAdd -Join ", "))`r`n" + $Scriptblock.ToString()) 425 | } 426 | else 427 | { 428 | Throw "Must provide ScriptBlock or ScriptFile"; Break 429 | } 430 | 431 | Write-Debug "`$ScriptBlock: $($ScriptBlock | Out-String)" 432 | Write-Verbose "Creating runspace pool and session states" 433 | 434 | #If specified, add variables and modules/snapins to session state 435 | $sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() 436 | if ($ImportVariables) 437 | { 438 | if($UserVariables.count -gt 0) 439 | { 440 | foreach($Variable in $UserVariables) 441 | { 442 | $sessionstate.Variables.Add( (New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Variable.Name, $Variable.Value, $null) ) 443 | } 444 | } 445 | } 446 | if ($ImportModules) 447 | { 448 | if($UserModules.count -gt 0) 449 | { 450 | foreach($ModulePath in $UserModules) 451 | { 452 | $sessionstate.ImportPSModule($ModulePath) 453 | } 454 | } 455 | if($UserSnapins.count -gt 0) 456 | { 457 | foreach($PSSnapin in $UserSnapins) 458 | { 459 | [void]$sessionstate.ImportPSSnapIn($PSSnapin, [ref]$null) 460 | } 461 | } 462 | } 463 | 464 | #Create runspace pool 465 | $runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host) 466 | $runspacepool.Open() 467 | 468 | Write-Verbose "Creating empty collection to hold runspace jobs" 469 | $Script:runspaces = New-Object System.Collections.ArrayList 470 | 471 | #If inputObject is bound get a total count and set bound to true 472 | $bound = $PSBoundParameters.keys -contains "InputObject" 473 | if(-not $bound) 474 | { 475 | [System.Collections.ArrayList]$allObjects = @() 476 | } 477 | 478 | #Set up log file if specified 479 | if( $LogFile ){ 480 | New-Item -ItemType file -path $logFile -force | Out-Null 481 | ("" | Select Date, Action, Runtime, Status, Details | ConvertTo-Csv -NoTypeInformation -Delimiter ";")[0] | Out-File $LogFile 482 | } 483 | 484 | #write initial log entry 485 | $log = "" | Select Date, Action, Runtime, Status, Details 486 | $log.Date = Get-Date 487 | $log.Action = "Batch processing started" 488 | $log.Runtime = $null 489 | $log.Status = "Started" 490 | $log.Details = $null 491 | if($logFile) { 492 | ($log | convertto-csv -Delimiter ";" -NoTypeInformation)[1] | Out-File $LogFile -Append 493 | } 494 | 495 | $timedOutTasks = $false 496 | 497 | #endregion INIT 498 | } 499 | 500 | Process { 501 | 502 | #add piped objects to all objects or set all objects to bound input object parameter 503 | if($bound) 504 | { 505 | $allObjects = $InputObject 506 | } 507 | Else 508 | { 509 | [void]$allObjects.add( $InputObject ) 510 | } 511 | } 512 | 513 | End { 514 | 515 | #Use Try/Finally to catch Ctrl+C and clean up. 516 | Try 517 | { 518 | #counts for progress 519 | $totalCount = $allObjects.count 520 | $script:completedCount = 0 521 | $startedCount = 0 522 | 523 | foreach($object in $allObjects){ 524 | 525 | #region add scripts to runspace pool 526 | 527 | #Create the powershell instance, set verbose if needed, supply the scriptblock and parameters 528 | $powershell = [powershell]::Create() 529 | 530 | if ($VerbosePreference -eq 'Continue') 531 | { 532 | [void]$PowerShell.AddScript({$VerbosePreference = 'Continue'}) 533 | } 534 | 535 | [void]$PowerShell.AddScript($ScriptBlock).AddArgument($object) 536 | 537 | if ($parameter) 538 | { 539 | [void]$PowerShell.AddArgument($parameter) 540 | } 541 | 542 | # $Using support from Boe Prox 543 | if ($UsingVariableData) 544 | { 545 | Foreach($UsingVariable in $UsingVariableData) { 546 | Write-Verbose "Adding $($UsingVariable.Name) with value: $($UsingVariable.Value)" 547 | [void]$PowerShell.AddArgument($UsingVariable.Value) 548 | } 549 | } 550 | 551 | #Add the runspace into the powershell instance 552 | $powershell.RunspacePool = $runspacepool 553 | 554 | #Create a temporary collection for each runspace 555 | $temp = "" | Select-Object PowerShell, StartTime, object, Runspace 556 | $temp.PowerShell = $powershell 557 | $temp.StartTime = Get-Date 558 | $temp.object = $object 559 | 560 | #Save the handle output when calling BeginInvoke() that will be used later to end the runspace 561 | $temp.Runspace = $powershell.BeginInvoke() 562 | $startedCount++ 563 | 564 | #Add the temp tracking info to $runspaces collection 565 | Write-Verbose ( "Adding {0} to collection at {1}" -f $temp.object, $temp.starttime.tostring() ) 566 | $runspaces.Add($temp) | Out-Null 567 | 568 | #loop through existing runspaces one time 569 | Get-RunspaceData 570 | 571 | #If we have more running than max queue (used to control timeout accuracy) 572 | #Script scope resolves odd PowerShell 2 issue 573 | $firstRun = $true 574 | while ($runspaces.count -ge $Script:MaxQueue) { 575 | 576 | #give verbose output 577 | if($firstRun){ 578 | Write-Verbose "$($runspaces.count) items running - exceeded $Script:MaxQueue limit." 579 | } 580 | $firstRun = $false 581 | 582 | #run get-runspace data and sleep for a short while 583 | Get-RunspaceData 584 | Start-Sleep -Milliseconds $sleepTimer 585 | 586 | } 587 | 588 | #endregion add scripts to runspace pool 589 | } 590 | 591 | Write-Verbose ( "Finish processing the remaining runspace jobs: {0}" -f ( @($runspaces | Where {$_.Runspace -ne $Null}).Count) ) 592 | Get-RunspaceData -wait 593 | 594 | if (-not $quiet) { 595 | Write-Progress -Activity "Running Query" -Status "Starting threads" -Completed 596 | } 597 | } 598 | Finally 599 | { 600 | #Close the runspace pool, unless we specified no close on timeout and something timed out 601 | if ( ($timedOutTasks -eq $false) -or ( ($timedOutTasks -eq $true) -and ($noCloseOnTimeout -eq $false) ) ) { 602 | Write-Verbose "Closing the runspace pool" 603 | $runspacepool.close() 604 | } 605 | 606 | #collect garbage 607 | [gc]::Collect() 608 | } 609 | } 610 | } -------------------------------------------------------------------------------- /Scripts/Load-PowerCLI.ps1: -------------------------------------------------------------------------------- 1 | # Add in vmware powercli 2 | Add-PSSnapin VMware.VimAutomation.Core 3 | . 'C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1' 4 | -------------------------------------------------------------------------------- /Scripts/Load-Vagrant.ps1: -------------------------------------------------------------------------------- 1 | Push-Location (Split-Path -Path $MyInvocation.MyCommand.Definition -Parent) 2 | 3 | try { 4 | Import-Module vagrant-status 5 | 6 | function prompt { 7 | Write-Host($pwd.ProviderPath) -nonewline 8 | Write-VagrantStatusDetailed 9 | return "> " 10 | } 11 | } 12 | catch {} 13 | 14 | Pop-Location 15 | -------------------------------------------------------------------------------- /Scripts/New-CodeSigningCertificate.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Creates a self-signed local CA and then creates a code signing certificate from it. 4 | .DESCRIPTION 5 | Creates a self-signed local CA and then creates a code signing certificate from it. 6 | .PARAMETER Force 7 | Forcefully creates another code signing certificate (AND trusted CA) even if a code signing certificate already exists. 8 | .PARAMETER Secure 9 | Prompt for password to perform a more secure self-signed code signing certificate configuration. 10 | 11 | .EXAMPLE 12 | New-CodeSigningCertificate 13 | 14 | Creates a self-signed root and trusted publisher certificate. The certificate is left stored in the 15 | CurrentUser's My store for future signing requests without any passwords. 16 | 17 | .EXAMPLE 18 | New-CodeSigningCertificate 19 | 20 | Creates a self-signed root and trusted publisher certificate. A password protected pfx named codesigningcert.pfx is saved 21 | in the current user's powershell profile and removed from the CurrentUser's My store. Future signing requests should use this 22 | PFX file (and thus be prompted for the password). 23 | #> 24 | [CmdletBinding()] 25 | Param ( 26 | [Parameter(position=0)] 27 | [switch]$Force, 28 | [Parameter(position=1)] 29 | [switch]$Secure 30 | ) 31 | 32 | Begin { 33 | try { 34 | $CodeSigningCerts = @(Get-ChildItem cert:\CurrentUser\My -codesigning) 35 | } 36 | catch { 37 | throw 'Unable to parse certificate store for a code signing cert' 38 | } 39 | function New-SelfSignedCertificateEx { 40 | <# 41 | .Synopsis 42 | This cmdlet generates a self-signed certificate. 43 | .Description 44 | This cmdlet generates a self-signed certificate with the required data. 45 | .Parameter Subject 46 | Specifies the certificate subject in a X500 distinguished name format. 47 | Example: CN=Test Cert, OU=Sandbox 48 | .Parameter NotBefore 49 | Specifies the date and time when the certificate become valid. By default previous day 50 | date is used. 51 | .Parameter NotAfter 52 | Specifies the date and time when the certificate expires. By default, the certificate is 53 | valid for 1 year. 54 | .Parameter SerialNumber 55 | Specifies the desired serial number in a hex format. 56 | Example: 01a4ff2 57 | .Parameter ProviderName 58 | Specifies the Cryptography Service Provider (CSP) name. You can use either legacy CSP 59 | and Key Storage Providers (KSP). By default "Microsoft Enhanced Cryptographic Provider v1.0" 60 | CSP is used. 61 | .Parameter AlgorithmName 62 | Specifies the public key algorithm. By default RSA algorithm is used. RSA is the only 63 | algorithm supported by legacy CSPs. With key storage providers (KSP) you can use CNG 64 | algorithms, like ECDH. For CNG algorithms you must use full name: 65 | ECDH_P256 66 | ECDH_P384 67 | ECDH_P521 68 | 69 | In addition, KeyLength parameter must be specified explicitly when non-RSA algorithm is used. 70 | .Parameter KeyLength 71 | Specifies the key length to generate. By default 2048-bit key is generated. 72 | .Parameter KeySpec 73 | Specifies the public key operations type. The possible values are: Exchange and Signature. 74 | Default value is Exchange. 75 | .Parameter EnhancedKeyUsage 76 | Specifies the intended uses of the public key contained in a certificate. You can 77 | specify either, EKU friendly name (for example 'Server Authentication') or 78 | object identifier (OID) value (for example '1.3.6.1.5.5.7.3.1'). 79 | .Parameter KeyUsages 80 | Specifies restrictions on the operations that can be performed by the public key contained in the certificate. 81 | Possible values (and their respective integer values to make bitwise operations) are: 82 | EncipherOnly 83 | CrlSign 84 | KeyCertSign 85 | KeyAgreement 86 | DataEncipherment 87 | KeyEncipherment 88 | NonRepudiation 89 | DigitalSignature 90 | DecipherOnly 91 | 92 | you can combine key usages values by using bitwise OR operation. when combining multiple 93 | flags, they must be enclosed in quotes and separated by a comma character. For example, 94 | to combine KeyEncipherment and DigitalSignature flags you should type: 95 | "KeyEncipherment, DigitalSignature". 96 | 97 | If the certificate is CA certificate (see IsCA parameter), key usages extension is generated 98 | automatically with the following key usages: Certificate Signing, Off-line CRL Signing, CRL Signing. 99 | .Parameter SubjectAlternativeName 100 | Specifies alternative names for the subject. Unlike Subject field, this extension 101 | allows to specify more than one name. Also, multiple types of alternative names 102 | are supported. The cmdlet supports the following SAN types: 103 | RFC822 Name 104 | IP address (both, IPv4 and IPv6) 105 | Guid 106 | Directory name 107 | DNS name 108 | .Parameter IsCA 109 | Specifies whether the certificate is CA (IsCA = $true) or end entity (IsCA = $false) 110 | certificate. If this parameter is set to $false, PathLength parameter is ignored. 111 | Basic Constraints extension is marked as critical. 112 | .PathLength 113 | Specifies the number of additional CA certificates in the chain under this certificate. If 114 | PathLength parameter is set to zero, then no additional (subordinate) CA certificates are 115 | permitted under this CA. 116 | .CustomExtension 117 | Specifies the custom extension to include to a self-signed certificate. This parameter 118 | must not be used to specify the extension that is supported via other parameters. In order 119 | to use this parameter, the extension must be formed in a collection of initialized 120 | System.Security.Cryptography.X509Certificates.X509Extension objects. 121 | .Parameter SignatureAlgorithm 122 | Specifies signature algorithm used to sign the certificate. By default 'SHA1' 123 | algorithm is used. 124 | .Parameter FriendlyName 125 | Specifies friendly name for the certificate. 126 | .Parameter StoreLocation 127 | Specifies the store location to store self-signed certificate. Possible values are: 128 | 'CurrentUser' and 'LocalMachine'. 'CurrentUser' store is intended for user certificates 129 | and computer (as well as CA) certificates must be stored in 'LocalMachine' store. 130 | .Parameter StoreName 131 | Specifies the container name in the certificate store. Possible container names are: 132 | AddressBook 133 | AuthRoot 134 | CertificateAuthority 135 | Disallowed 136 | My 137 | Root 138 | TrustedPeople 139 | TrustedPublisher 140 | .Parameter Path 141 | Specifies the path to a PFX file to export a self-signed certificate. 142 | .Parameter Password 143 | Specifies the password for PFX file. 144 | .Parameter AllowSMIME 145 | Enables Secure/Multipurpose Internet Mail Extensions for the certificate. 146 | .Parameter Exportable 147 | Marks private key as exportable. Smart card providers usually do not allow 148 | exportable keys. 149 | .Example 150 | New-SelfsignedCertificateEx -Subject "CN=Test Code Signing" -EKU "Code Signing" -KeySpec "Signature" ` 151 | -KeyUsage "DigitalSignature" -FriendlyName "Test code signing" -NotAfter [datetime]::now.AddYears(5) 152 | 153 | Creates a self-signed certificate intended for code signing and which is valid for 5 years. Certificate 154 | is saved in the Personal store of the current user account. 155 | .Example 156 | New-SelfsignedCertificateEx -Subject "CN=www.domain.com" -EKU "Server Authentication", "Client authentication" ` 157 | -KeyUsage "KeyEcipherment, DigitalSignature" -SAN "sub.domain.com","www.domain.com","192.168.1.1" ` 158 | -AllowSMIME -Path C:\test\ssl.pfx -Password (ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force) -Exportable ` 159 | -StoreLocation "LocalMachine" 160 | 161 | Creates a self-signed SSL certificate with multiple subject names and saves it to a file. Additionally, the 162 | certificate is saved in the Personal store of the Local Machine store. Private key is marked as exportable, 163 | so you can export the certificate with a associated private key to a file at any time. The certificate 164 | includes SMIME capabilities. 165 | .Example 166 | New-SelfsignedCertificateEx -Subject "CN=www.domain.com" -EKU "Server Authentication", "Client authentication" ` 167 | -KeyUsage "KeyEcipherment, DigitalSignature" -SAN "sub.domain.com","www.domain.com","192.168.1.1" ` 168 | -StoreLocation "LocalMachine" -ProviderName "Microsoft Software Key Storae Provider" -AlgorithmName ecdh_256 ` 169 | -KeyLength 256 -SignatureAlgorithm sha256 170 | 171 | Creates a self-signed SSL certificate with multiple subject names and saves it to a file. Additionally, the 172 | certificate is saved in the Personal store of the Local Machine store. Private key is marked as exportable, 173 | so you can export the certificate with a associated private key to a file at any time. Certificate uses 174 | Ellyptic Curve Cryptography (ECC) key algorithm ECDH with 256-bit key. The certificate is signed by using 175 | SHA256 algorithm. 176 | .Example 177 | New-SelfsignedCertificateEx -Subject "CN=Test Root CA, OU=Sandbox" -IsCA $true -ProviderName ` 178 | "Microsoft Software Key Storage Provider" -Exportable 179 | 180 | Creates self-signed root CA certificate. 181 | .Link 182 | https://www.sysadmins.lv/blog-en/self-signed-certificate-creation-with-powershell.aspx 183 | #> 184 | [CmdletBinding(DefaultParameterSetName = '__store')] 185 | param ( 186 | [Parameter(Mandatory = $true, Position = 0)] 187 | [string]$Subject, 188 | [Parameter(Position = 1)] 189 | [datetime]$NotBefore = [DateTime]::Now.AddDays(-1), 190 | [Parameter(Position = 2)] 191 | [datetime]$NotAfter = $NotBefore.AddDays(365), 192 | [string]$SerialNumber, 193 | [Alias('CSP')] 194 | [string]$ProviderName = "Microsoft Enhanced Cryptographic Provider v1.0", 195 | [string]$AlgorithmName = "RSA", 196 | [int]$KeyLength = 2048, 197 | [validateSet("Exchange","Signature")] 198 | [string]$KeySpec = "Exchange", 199 | [Alias('EKU')] 200 | [Security.Cryptography.Oid[]]$EnhancedKeyUsage, 201 | [Alias('KU')] 202 | [Security.Cryptography.X509Certificates.X509KeyUsageFlags]$KeyUsage, 203 | [Alias('SAN')] 204 | [String[]]$SubjectAlternativeName, 205 | [bool]$IsCA, 206 | [int]$PathLength = -1, 207 | [Security.Cryptography.X509Certificates.X509ExtensionCollection]$CustomExtension, 208 | [ValidateSet('MD5','SHA1','SHA256','SHA384','SHA512')] 209 | [string]$SignatureAlgorithm = "SHA1", 210 | [string]$FriendlyName, 211 | [Parameter(ParameterSetName = '__store')] 212 | [Security.Cryptography.X509Certificates.StoreLocation]$StoreLocation = "CurrentUser", 213 | [Parameter(ParameterSetName = '__store')] 214 | [Security.Cryptography.X509Certificates.StoreName]$StoreName = "My", 215 | [Parameter(Mandatory = $true, ParameterSetName = '__file')] 216 | [Alias('OutFile','OutPath','Out')] 217 | [IO.FileInfo]$Path, 218 | [Parameter(Mandatory = $true, ParameterSetName = '__file')] 219 | [Security.SecureString]$Password, 220 | [switch]$AllowSMIME, 221 | [switch]$Exportable 222 | ) 223 | $ErrorActionPreference = "Stop" 224 | if ([Environment]::OSVersion.Version.Major -lt 6) { 225 | $NotSupported = New-Object NotSupportedException -ArgumentList "Windows XP and Windows Server 2003 are not supported!" 226 | throw $NotSupported 227 | } 228 | $ExtensionsToAdd = @() 229 | 230 | #region constants 231 | # contexts 232 | New-Variable -Name UserContext -Value 0x1 -Option Constant 233 | New-Variable -Name MachineContext -Value 0x2 -Option Constant 234 | # encoding 235 | New-Variable -Name Base64Header -Value 0x0 -Option Constant 236 | New-Variable -Name Base64 -Value 0x1 -Option Constant 237 | New-Variable -Name Binary -Value 0x3 -Option Constant 238 | New-Variable -Name Base64RequestHeader -Value 0x4 -Option Constant 239 | # SANs 240 | New-Variable -Name OtherName -Value 0x1 -Option Constant 241 | New-Variable -Name RFC822Name -Value 0x2 -Option Constant 242 | New-Variable -Name DNSName -Value 0x3 -Option Constant 243 | New-Variable -Name DirectoryName -Value 0x5 -Option Constant 244 | New-Variable -Name URL -Value 0x7 -Option Constant 245 | New-Variable -Name IPAddress -Value 0x8 -Option Constant 246 | New-Variable -Name RegisteredID -Value 0x9 -Option Constant 247 | New-Variable -Name Guid -Value 0xa -Option Constant 248 | New-Variable -Name UPN -Value 0xb -Option Constant 249 | # installation options 250 | New-Variable -Name AllowNone -Value 0x0 -Option Constant 251 | New-Variable -Name AllowNoOutstandingRequest -Value 0x1 -Option Constant 252 | New-Variable -Name AllowUntrustedCertificate -Value 0x2 -Option Constant 253 | New-Variable -Name AllowUntrustedRoot -Value 0x4 -Option Constant 254 | # PFX export options 255 | New-Variable -Name PFXExportEEOnly -Value 0x0 -Option Constant 256 | New-Variable -Name PFXExportChainNoRoot -Value 0x1 -Option Constant 257 | New-Variable -Name PFXExportChainWithRoot -Value 0x2 -Option Constant 258 | #endregion 259 | 260 | #region Subject processing 261 | # http://msdn.microsoft.com/en-us/library/aa377051(VS.85).aspx 262 | $SubjectDN = New-Object -ComObject X509Enrollment.CX500DistinguishedName 263 | $SubjectDN.Encode($Subject, 0x0) 264 | #endregion 265 | 266 | #region Extensions 267 | 268 | #region Enhanced Key Usages processing 269 | if ($EnhancedKeyUsage) { 270 | $OIDs = New-Object -ComObject X509Enrollment.CObjectIDs 271 | $EnhancedKeyUsage | %{ 272 | $OID = New-Object -ComObject X509Enrollment.CObjectID 273 | $OID.InitializeFromValue($_.Value) 274 | # http://msdn.microsoft.com/en-us/library/aa376785(VS.85).aspx 275 | $OIDs.Add($OID) 276 | } 277 | # http://msdn.microsoft.com/en-us/library/aa378132(VS.85).aspx 278 | $EKU = New-Object -ComObject X509Enrollment.CX509ExtensionEnhancedKeyUsage 279 | $EKU.InitializeEncode($OIDs) 280 | $ExtensionsToAdd += "EKU" 281 | } 282 | #endregion 283 | 284 | #region Key Usages processing 285 | if ($KeyUsage -ne $null) { 286 | $KU = New-Object -ComObject X509Enrollment.CX509ExtensionKeyUsage 287 | $KU.InitializeEncode([int]$KeyUsage) 288 | $KU.Critical = $true 289 | $ExtensionsToAdd += "KU" 290 | } 291 | #endregion 292 | 293 | #region Basic Constraints processing 294 | if ($PSBoundParameters.Keys.Contains("IsCA")) { 295 | # http://msdn.microsoft.com/en-us/library/aa378108(v=vs.85).aspx 296 | $BasicConstraints = New-Object -ComObject X509Enrollment.CX509ExtensionBasicConstraints 297 | if (!$IsCA) {$PathLength = -1} 298 | $BasicConstraints.InitializeEncode($IsCA,$PathLength) 299 | $BasicConstraints.Critical = $IsCA 300 | $ExtensionsToAdd += "BasicConstraints" 301 | } 302 | #endregion 303 | 304 | #region SAN processing 305 | if ($SubjectAlternativeName) { 306 | $SAN = New-Object -ComObject X509Enrollment.CX509ExtensionAlternativeNames 307 | $Names = New-Object -ComObject X509Enrollment.CAlternativeNames 308 | foreach ($altname in $SubjectAlternativeName) { 309 | $Name = New-Object -ComObject X509Enrollment.CAlternativeName 310 | if ($altname.Contains("@")) { 311 | $Name.InitializeFromString($RFC822Name,$altname) 312 | } else { 313 | try { 314 | $Bytes = [Net.IPAddress]::Parse($altname).GetAddressBytes() 315 | $Name.InitializeFromRawData($IPAddress,$Base64,[Convert]::ToBase64String($Bytes)) 316 | } catch { 317 | try { 318 | $Bytes = [Guid]::Parse($altname).ToByteArray() 319 | $Name.InitializeFromRawData($Guid,$Base64,[Convert]::ToBase64String($Bytes)) 320 | } catch { 321 | try { 322 | $Bytes = ([Security.Cryptography.X509Certificates.X500DistinguishedName]$altname).RawData 323 | $Name.InitializeFromRawData($DirectoryName,$Base64,[Convert]::ToBase64String($Bytes)) 324 | } catch {$Name.InitializeFromString($DNSName,$altname)} 325 | } 326 | } 327 | } 328 | $Names.Add($Name) 329 | } 330 | $SAN.InitializeEncode($Names) 331 | $ExtensionsToAdd += "SAN" 332 | } 333 | #endregion 334 | 335 | #region Custom Extensions 336 | if ($CustomExtension) { 337 | $count = 0 338 | foreach ($ext in $CustomExtension) { 339 | # http://msdn.microsoft.com/en-us/library/aa378077(v=vs.85).aspx 340 | $Extension = New-Object -ComObject X509Enrollment.CX509Extension 341 | $EOID = New-Object -ComObject X509Enrollment.CObjectId 342 | $EOID.InitializeFromValue($ext.Oid.Value) 343 | $EValue = [Convert]::ToBase64String($ext.RawData) 344 | $Extension.Initialize($EOID,$Base64,$EValue) 345 | $Extension.Critical = $ext.Critical 346 | New-Variable -Name ("ext" + $count) -Value $Extension 347 | $ExtensionsToAdd += ("ext" + $count) 348 | $count++ 349 | } 350 | } 351 | #endregion 352 | 353 | #endregion 354 | 355 | #region Private Key 356 | # http://msdn.microsoft.com/en-us/library/aa378921(VS.85).aspx 357 | $PrivateKey = New-Object -ComObject X509Enrollment.CX509PrivateKey 358 | $PrivateKey.ProviderName = $ProviderName 359 | $AlgID = New-Object -ComObject X509Enrollment.CObjectId 360 | $AlgID.InitializeFromValue(([Security.Cryptography.Oid]$AlgorithmName).Value) 361 | $PrivateKey.Algorithm = $AlgID 362 | # http://msdn.microsoft.com/en-us/library/aa379409(VS.85).aspx 363 | $PrivateKey.KeySpec = switch ($KeySpec) {"Exchange" {1}; "Signature" {2}} 364 | $PrivateKey.Length = $KeyLength 365 | # key will be stored in current user certificate store 366 | switch ($PSCmdlet.ParameterSetName) { 367 | '__store' { 368 | $PrivateKey.MachineContext = if ($StoreLocation -eq "LocalMachine") {$true} else {$false} 369 | } 370 | '__file' { 371 | $PrivateKey.MachineContext = $false 372 | } 373 | } 374 | $PrivateKey.ExportPolicy = if ($Exportable) {1} else {0} 375 | $PrivateKey.Create() 376 | #endregion 377 | 378 | # http://msdn.microsoft.com/en-us/library/aa377124(VS.85).aspx 379 | $Cert = New-Object -ComObject X509Enrollment.CX509CertificateRequestCertificate 380 | if ($PrivateKey.MachineContext) { 381 | $Cert.InitializeFromPrivateKey($MachineContext,$PrivateKey,"") 382 | } else { 383 | $Cert.InitializeFromPrivateKey($UserContext,$PrivateKey,"") 384 | } 385 | $Cert.Subject = $SubjectDN 386 | $Cert.Issuer = $Cert.Subject 387 | $Cert.NotBefore = $NotBefore 388 | $Cert.NotAfter = $NotAfter 389 | foreach ($item in $ExtensionsToAdd) {$Cert.X509Extensions.Add((Get-Variable -Name $item -ValueOnly))} 390 | if (![string]::IsNullOrEmpty($SerialNumber)) { 391 | if ($SerialNumber -match "[^0-9a-fA-F]") {throw "Invalid serial number specified."} 392 | if ($SerialNumber.Length % 2) {$SerialNumber = "0" + $SerialNumber} 393 | $Bytes = $SerialNumber -split "(.{2})" | ?{$_} | %{[Convert]::ToByte($_,16)} 394 | $ByteString = [Convert]::ToBase64String($Bytes) 395 | $Cert.SerialNumber.InvokeSet($ByteString,1) 396 | } 397 | if ($AllowSMIME) {$Cert.SmimeCapabilities = $true} 398 | $SigOID = New-Object -ComObject X509Enrollment.CObjectId 399 | $SigOID.InitializeFromValue(([Security.Cryptography.Oid]$SignatureAlgorithm).Value) 400 | $Cert.SignatureInformation.HashAlgorithm = $SigOID 401 | # completing certificate request template building 402 | $Cert.Encode() 403 | 404 | # interface: http://msdn.microsoft.com/en-us/library/aa377809(VS.85).aspx 405 | $Request = New-Object -ComObject X509Enrollment.CX509enrollment 406 | $Request.InitializeFromRequest($Cert) 407 | $Request.CertificateFriendlyName = $FriendlyName 408 | $endCert = $Request.CreateRequest($Base64) 409 | $Request.InstallResponse($AllowUntrustedCertificate,$endCert,$Base64,"") 410 | switch ($PSCmdlet.ParameterSetName) { 411 | '__file' { 412 | $PFXString = $Request.CreatePFX( 413 | [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)), 414 | $PFXExportEEOnly, 415 | $Base64 416 | ) 417 | Set-Content -Path $Path -Value ([Convert]::FromBase64String($PFXString)) -Encoding Byte 418 | } 419 | } 420 | } 421 | if (($CodeSigningCerts.Count -ne 0) -and (-not $Force)) { 422 | Write-Warning 'A code signing cert was found to already exist. Run again with the -Force switch to create another one anyway' 423 | return 424 | } 425 | 426 | } 427 | Process {} 428 | End { 429 | # Create a new self-signed code signing certificate 430 | try { 431 | #New-SelfsignedCertificateEx -Subject "CN=Powershell Signing User Certificate" -ProviderName "Microsoft Software Key Storage Provider" -Exportable -EKU 1.3.6.1.5.5.7.3.3 -FriendlyName 'Powershell Local Certificate Root' -StoreName 'Root' -NotAfter ([DateTime]::Now.AddDays(1420)) 432 | $ProfPath = Split-Path $Profile 433 | $CertReqSplat = @{ 434 | Subject = 'CN=Powershell Signing User Certificate' 435 | ProviderName = 'Microsoft Software Key Storage Provider' 436 | SignatureAlgorithm = 'SHA256' 437 | KeyLength = 4096 438 | EKU = '1.3.6.1.5.5.7.3.3' 439 | FriendlyName = 'Powershell Local Certificate Root' 440 | NotAfter = ([DateTime]::Now.AddDays(1420)) 441 | Exportable = $true 442 | } 443 | if ($Secure) { 444 | $Pass1 = Read-Host -AsSecureString -Prompt 'Please provide a password for your PFX file' 445 | $Pass2 = Read-Host -AsSecureString -Prompt 'Please enter that password in again so you know you remember it!' 446 | $pwd1_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Pass1)) 447 | $pwd2_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Pass2)) 448 | if ($pwd1_text -ne $pwd2_text) { 449 | Write-Warning "Passwords do not match! Doing nothing!" 450 | return 451 | } 452 | if (Test-Path "$($ProfPath)\codesigningcert.pfx") { 453 | Remove-Item "$($ProfPath)\codesigningcert.pfx" -Confirm -ErrorAction Stop 454 | if (Test-Path "$($ProfPath)\codesigningcert.pfx") { 455 | Write-Error 'Existing code signing cert was found and you chose not to remove it. Stopping processing.' 456 | return 457 | } 458 | } 459 | $CertReqSplat.Path = "$($ProfPath)\codesigningcert.pfx" 460 | $CertReqSplat.Password = ConvertTo-SecureString -String $pwd1_text -Force –AsPlainText 461 | } 462 | 463 | New-SelfsignedCertificateEx @CertReqSplat 464 | 465 | Get-ChildItem Cert:\CurrentUser\My | Where {$_.Subject -eq 'CN=Powershell Signing User Certificate'} | Foreach { 466 | $thumbprint = $_.Thumbprint 467 | 468 | if ($Secure) { 469 | # So we need to get the public part of the certificate in the trusted publishers and root stores 470 | # to do this we need to export it, delete, then import it (in a way). The pfx should have already 471 | # been created if we got to this point. 472 | 473 | # Export the cert in just cer format (no private key) 474 | $cert = Get-Item "Cert:\CurrentUser\My\$($thumbprint)" 475 | $bytes = $cert.Export('Cert') 476 | 477 | # delete certificate 478 | $store = new-object system.security.cryptography.x509certificates.x509Store 'My', 'CurrentUser' 479 | $store.Open('ReadWrite') 480 | $store.Remove($cert) 481 | 482 | # re-import certificate (cer) 483 | $container = new-object system.security.cryptography.x509certificates.x509certificate2collection 484 | $container.Import($bytes) 485 | $store.Add($container[0]) 486 | $store.Close() 487 | 488 | # Now go ahead and move it to the trustedpublisher store. 489 | Move-Item -Path "Cert:\CurrentUser\My\$($thumbprint)" -Destination Cert:\CurrentUser\TrustedPublisher 490 | } 491 | else { 492 | # Insecurely we don't give a crap if we copy over the private key. 493 | Copy-Item -Path "Cert:\CurrentUser\My\$($thumbprint)" -Destination Cert:\CurrentUser\TrustedPublisher 494 | } 495 | 496 | # To copy the certificate we need to work around a limitation of the psdrive and this provider 497 | # http://social.technet.microsoft.com/wiki/contents/articles/28753.powershell-trick-copy-certificates-from-one-store-to-another.aspx 498 | $SourceStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList 'TrustedPublisher', 'CurrentUser' 499 | $SourceStore.Open('MaxAllowed') 500 | 501 | $copycert = $SourceStore.Certificates | Where {$_.Thumbprint -eq $thumbprint} 502 | 503 | $DestStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList 'Root', 'CurrentUser' 504 | $DestStore.Open('MaxAllowed') 505 | $DestStore.Add($copycert) 506 | 507 | $SourceStore.Close() 508 | $DestStore.Close() 509 | } 510 | } 511 | catch { 512 | throw 'There was an issue creating the code signing certificate!' 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /Scripts/New-PSGalleryProjectProfile.ps1: -------------------------------------------------------------------------------- 1 | #Requires -version 5 2 | <# 3 | .SYNOPSIS 4 | Create a powershell Gallery module upload profile 5 | .DESCRIPTION 6 | Create a powershell Gallery module upload profile 7 | .PARAMETER Name 8 | Module short name. 9 | .PARAMETER Path 10 | Path of module project files to upload. 11 | .PARAMETER ProjectUri 12 | Module project website. 13 | .PARAMETER Tags 14 | Tags used to search for the module (separated by spaces) 15 | .PARAMETER RequiredVersion 16 | Required powershell version (default is 2) 17 | .PARAMETER Repository 18 | Destination gallery (default is PSGallery) 19 | .PARAMETER ReleaseNotes 20 | Release notes. 21 | .PARAMETER LicenseUri 22 | License website. 23 | .PARAMETER IconUri 24 | Icon web path. 25 | .PARAMETER APIKey 26 | API key for the powershellgallery.com site. 27 | .PARAMETER OutputFile 28 | OutputFile (default is .psgallery) 29 | 30 | .EXAMPLE 31 | .NOTES 32 | Author: Zachary Loeber 33 | Site: http://www.the-little-things.net/ 34 | Version History 35 | 1.0.0 - Initial release 36 | #> 37 | [CmdletBinding()] 38 | param( 39 | [parameter(Position=0, Mandatory=$true, HelpMessage='Module short name.')] 40 | [string]$Name, 41 | [parameter(Position=1, Mandatory=$true, HelpMessage='Path of module project files to upload.')] 42 | [string]$Path, 43 | [parameter(Position=2, HelpMessage='Module project website.')] 44 | [string]$ProjectUri = '', 45 | [parameter(Position=3, HelpMessage='Tags used to search for the module (separated by spaces)')] 46 | [string]$Tags = '', 47 | [parameter(Position=4, HelpMessage='Required powershell version (default is 2)')] 48 | [string]$RequiredVersion = 2, 49 | [parameter(Position=5, HelpMessage='Destination gallery (default is PSGallery)')] 50 | [string]$Repository = 'PSGallery', 51 | [parameter(Position=6, HelpMessage='Release notes.')] 52 | [string]$ReleaseNotes = '', 53 | [parameter(Position=7, HelpMessage=' License website.')] 54 | [string]$LicenseUri = '', 55 | [parameter(Position=9, HelpMessage='Icon web path.')] 56 | [string]$IconUri = '', 57 | [parameter(Position=10, HelpMessage='API key for the powershellgallery.com site.')] 58 | [string]$APIKey = '', 59 | [parameter(Position=11, HelpMessage='OutputFile (default is .psgallery)')] 60 | [string]$OutputFile = '.psgallery' 61 | ) 62 | 63 | $PublishParams = @{ 64 | Name = $Name 65 | Path = $Path 66 | APIKey = $APIKey 67 | ProjectUri = $ProjectUri 68 | Tags = $Tags 69 | RequiredVersion = $RequiredVersion 70 | Repository = $Repository 71 | ReleaseNotes = $ReleaseNotes 72 | LicenseUri = $LicenseUri 73 | IconUri = $IconUri 74 | } 75 | 76 | if (Test-Path $OutputFile) { 77 | $PublishParams | Export-Clixml -Path $OutputFile -confirm 78 | } 79 | else { 80 | $PublishParams | Export-Clixml -Path $OutputFile 81 | } -------------------------------------------------------------------------------- /Scripts/Remove-OldModule.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 5 2 | 3 | <# 4 | .SYNOPSIS 5 | A small wrapper for PowerShellGet to remove all older installed modules. 6 | .DESCRIPTION 7 | A small wrapper for PowerShellGet to remove all older installed modules. 8 | .PARAMETER ModuleName 9 | Name of a module to check and remove old versions of. 10 | .EXAMPLE 11 | Remove-OldModules.ps1 12 | 13 | Description 14 | ------------- 15 | Removes old modules installed via PowerShellGet. 16 | .NOTES 17 | Author: Zachary Loeber 18 | Site: http://www.the-little-things.net/ 19 | Requires: Powershell 5.0 20 | 21 | Version History 22 | 1.0.0 - Initial release 23 | #> 24 | [CmdletBinding( SupportsShouldProcess = $true )] 25 | Param ( 26 | [Parameter(HelpMessage = 'Name of a module to check and remove old versions of.')] 27 | [string]$ModuleName = '*' 28 | ) 29 | 30 | try { 31 | Import-Module PowerShellGet 32 | } 33 | catch { 34 | Write-Warning 'Unable to load PowerShellGet. This script only works with PowerShell 5 and greater.' 35 | return 36 | } 37 | $WhatIfParam = @{} 38 | $WhatIfParam.WhatIf = $WhatIf 39 | Get-InstalledModule $ModuleName | foreach { 40 | $InstalledModules = get-module $_.Name -ListAvailable 41 | if ($InstalledModules.Count -gt 1) { 42 | $SortedModules = $InstalledModules | sort-object Version -Descending 43 | Write-Output "Multiple Module versions for the $($SortedModules[0].Name) module found. Highest version is: $($SortedModules[0].Version.ToString())" 44 | for ($index = 1; $index -lt $SortedModules.Count; $index++) { 45 | try { 46 | if ($pscmdlet.ShouldProcess( "$($SortedModules[$index].Name) - $($SortedModules[$index].Version)")) { 47 | Write-Output "..Attempting to uninstall $($SortedModules[$index].Name) - Version $($SortedModules[$index].Version)" 48 | Uninstall-Module -Name $SortedModules[$index].Name -MaximumVersion $SortedModules[$index].Version -ErrorAction Stop -Force 49 | } 50 | } 51 | catch { 52 | Write-Warning "Unable to remove module version $($SortedModules[$index].Version)" 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Scripts/Remove-ScriptSignature.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Finds all signed ps1 and psm1 files recursively from the current or defined path and removes any digital signatures attached to them. 4 | .DESCRIPTION 5 | Finds all signed ps1 and psm1 files recursively from the current or defined path and removes any digital signatures attached to them. 6 | .PARAMETER Path 7 | Path you want to parse for digital signatures. 8 | .PARAMETER Recurse 9 | Recurse through all subdirectories of the path provided. 10 | .EXAMPLE 11 | PS> Remove-ScriptSignature 12 | 13 | Removes all digital signatures from ps1/psm1 files found in the current path. 14 | 15 | .NOTES 16 | Author: Zachary Loeber 17 | .LINK 18 | http://www.the-little-things.net 19 | #> 20 | 21 | [CmdletBinding( SupportsShouldProcess = $true )] 22 | Param ( 23 | [Parameter(ValueFromPipeline = $True,ValueFromPipelineByPropertyName = $True)] 24 | [Alias('FilePath')] 25 | [string]$Path = $(Get-Location).Path, 26 | [Parameter()] 27 | [switch]$Recurse 28 | ) 29 | Begin { 30 | $RecurseParam = @{} 31 | if ($Recurse) { 32 | $RecurseParam.Recurse = $true 33 | } 34 | } 35 | 36 | Process { 37 | $FilesToProcess = Get-ChildItem -Path $Path -File -Include '*.psm1','*.ps1','*.psd1','*.ps1xml' @RecurseParam 38 | 39 | $FilesToProcess | ForEach-Object -Process { 40 | $SignatureStatus = (Get-AuthenticodeSignature $_).Status 41 | $ScriptFileFullName = $_.FullName 42 | if ($SignatureStatus -ne 'NotSigned') { 43 | try { 44 | $Content = Get-Content $ScriptFileFullName -ErrorAction Stop 45 | $StringBuilder = New-Object -TypeName System.Text.StringBuilder -ErrorAction Stop 46 | 47 | Foreach ($Line in $Content) { 48 | if ($Line -match '^# SIG # Begin signature block|^') { 49 | Break 50 | } 51 | else { 52 | $null = $StringBuilder.AppendLine($Line) 53 | } 54 | } 55 | if ($pscmdlet.ShouldProcess( "$ScriptFileFullName")) { 56 | Set-Content -Path $ScriptFileFullName -Value $StringBuilder.ToString() 57 | Write-Output "$ScriptFileFullName -> Removed Signature!" 58 | } 59 | } 60 | catch { 61 | Write-Output "$ScriptFileFullName -> Unable to process signed file!" 62 | Write-Error -Message $_.Exception.Message 63 | } 64 | } 65 | else { 66 | Write-Verbose "$ScriptFileFullName -> No signature, nothing done." 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Scripts/Set-ProfileScriptSignature.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Re-run this if you have updated any scripts in your profile this script will attempt to sign the following files with the first available code signing certificate if 3 | they are not in a currently valid signed state: 4 | - $Profile (Typically C:\Users\\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1) 5 | - "$(Split-Path $Profile)\Modules\Environment\Environment.psm1" (typically C:\Users\\Documents\WindowsPowerShell\Modules\Environment\Environment.psm1) 6 | #> 7 | [CmdletBinding()] 8 | Param ( 9 | [Parameter(position=0)] 10 | [string]$ScriptToSign, 11 | [Parameter(position=1)] 12 | [switch]$Secure, 13 | [Parameter(position=2)] 14 | [string]$PFX = ("$(Split-Path $PROFILE)\codesigningcert.pfx") 15 | ) 16 | Begin { 17 | $ProfilePath = Split-Path $PROFILE 18 | if ([string]::isNullorEmpty($ScriptToSign)) { 19 | Write-Verbose 'No files specified so we will validate and update only the profile script and the environment.psm1 files.' 20 | $PSFiles = @() 21 | $ProfileFiles = "$(Split-Path $Profile)\Modules\Environment\Environment.psm1",$Profile 22 | $PSFiles = @() 23 | $ProfileFiles | Foreach { 24 | $PSFile = Get-ChildItem $_ -File 25 | $PSFiles += $PSFile 26 | Write-Verbose "File $($PSFile.Name) - $($PSFile.Status)" 27 | } 28 | #$PSFiles = Get-ChildItem -File -Recurse -path (Split-Path $Profile) -Include '*.ps1','*.psm1' 29 | $ScriptsToSign = @($PSFiles | Get-AuthenticodeSignature | Where-Object { 'HashMismatch', 'NotSigned', 'UnknownError' -contains $_.Status }) 30 | } 31 | else { 32 | $ScriptsToSign = @($ScriptToSign | Get-AuthenticodeSignature) 33 | } 34 | if ($Secure) { 35 | if (Test-Path $PFX) { 36 | Write-Verbose "Attempting to load the pfx file $PFX" 37 | try { 38 | $CodeSigningCert = Get-PfxCertificate $PFX 39 | Write-Verbose "PFX certificate loaded, thumbprint = $($CodeSigningCert.thumbprint)" 40 | } 41 | catch { 42 | Write-Error 'Unable to load PFX file!' 43 | return 44 | } 45 | } 46 | else { 47 | Write-Error "$PFX file not found!" 48 | return 49 | } 50 | } 51 | else { 52 | $CodeSigningCerts = @(get-childitem cert:\CurrentUser\My -codesigning) 53 | if ($CodeSigningCerts.Count -eq 0) { 54 | Write-Error 'No code signing certs found!' 55 | return 56 | } 57 | if ($CodeSigningCerts.Count -ne 1) { 58 | Write-Verbose "More than one code signing certificate found, using the first one in the list." 59 | } 60 | $CodeSigningCert = $CodeSigningCerts[0] 61 | } 62 | } 63 | Process {} 64 | End { 65 | $ScriptsToSign | foreach { 66 | Write-Output "The following script signed status is $($_.Status) - $($_.Path)" 67 | $Null = Set-AuthenticodeSignature $_.Path $CodeSigningCert 68 | } 69 | } -------------------------------------------------------------------------------- /Scripts/Set-ScriptSignature.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Attempts to sign the specified PowerShell scripts. 4 | .DESCRIPTION 5 | Attempts to sign the specified PowerShell scripts. 6 | .PARAMETER Path 7 | Path or script you want to try and digitally sign. 8 | .PARAMETER Recurse 9 | Recurse through all subdirectories of the path provided. 10 | .PARAMETER Force 11 | Attempts to sign the script even if it is already properly signed. 12 | .PARAMETER UsePFX 13 | Use the specified PFX file, otherwise the first found code signing certificate for the current user will be used. 14 | .PARAMETER PFX 15 | A valid path to a PFX file to use for code signing. Ignored if UsePFX is not used. 16 | .EXAMPLE 17 | PS> Set-ScriptSignature 18 | 19 | Attempts to sign all powershell file sin in the current path with the first code signing certificate found in the current users certificate store. 20 | 21 | .NOTES 22 | Author: Zachary Loeber 23 | .LINK 24 | http://www.the-little-things.net 25 | #> 26 | 27 | [CmdletBinding( SupportsShouldProcess = $true )] 28 | Param ( 29 | [Parameter(ValueFromPipeline = $True,ValueFromPipelineByPropertyName = $True)] 30 | [Alias('FilePath')] 31 | [string]$Path = $(Get-Location).Path, 32 | [Parameter()] 33 | [switch]$Recurse, 34 | [Parameter()] 35 | [switch]$Force, 36 | [Parameter()] 37 | [switch]$UsePFX, 38 | [Parameter()] 39 | [string]$PFX = ("$(Split-Path $PROFILE)\codesigningcert.pfx") 40 | ) 41 | Begin { 42 | $RecurseParam = @{} 43 | if ($Recurse) { 44 | $RecurseParam.Recurse = $true 45 | } 46 | if ($UsePFX) { 47 | if (Test-Path $PFX) { 48 | Write-Verbose "Attempting to load the pfx file $PFX" 49 | try { 50 | $CodeSigningCert = Get-PfxCertificate $PFX 51 | Write-Verbose "PFX certificate loaded, thumbprint = $($CodeSigningCert.thumbprint)" 52 | } 53 | catch { 54 | Write-Error 'Unable to load PFX file!' 55 | return 56 | } 57 | } 58 | else { 59 | Write-Error "$PFX file not found!" 60 | return 61 | } 62 | } 63 | else { 64 | $CodeSigningCerts = @(get-childitem cert:\CurrentUser\My -codesigning -ErrorAction Stop) 65 | if ($CodeSigningCerts.Count -eq 0) { 66 | Write-Error 'No code signing certs found!' 67 | return 68 | } 69 | if ($CodeSigningCerts.Count -ne 1) { 70 | Write-Output "More than one code signing certificate found, using the first one in the list." 71 | } 72 | $CodeSigningCert = $CodeSigningCerts[0] 73 | } 74 | } 75 | 76 | Process { 77 | $FilesToProcess = Get-ChildItem -Path $Path -File -Include '*.psm1','*.ps1','*.psd1','*.ps1xml' @RecurseParam 78 | 79 | $FilesToProcess | ForEach-Object -Process { 80 | $SignatureStatus = (Get-AuthenticodeSignature $_).Status 81 | $ScriptFileFullName = $_.FullName 82 | Write-Output "$ScriptFileFullName -> Current signed status = $SignatureStatus" 83 | if ($Force -or ('HashMismatch', 'NotSigned', 'UnknownError' -contains $SignatureStatus)) { 84 | if ($pscmdlet.ShouldProcess( "Would attempt to update $ScriptFileFullName with your signing certificate.")) { 85 | Write-Output "$ScriptFileFullName -> Attempting to sign the script..." 86 | try { 87 | $Null = Set-AuthenticodeSignature -FilePath $ScriptFileFullName -Certificate $CodeSigningCert -ErrorAction:Stop 88 | Write-Output "$ScriptFileFullName -> Now Signed!" 89 | } 90 | catch { 91 | Write-Error -Message $_.Exception.Message 92 | } 93 | } 94 | } 95 | else { 96 | Write-Output "$ScriptFileFullName -> Not signing as it is already signed without any detected issues." 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /Scripts/Update-PSGalleryProjectProfile.ps1: -------------------------------------------------------------------------------- 1 | #Requires -version 5 2 | <# 3 | .SYNOPSIS 4 | Update a powershell Gallery module upload profile 5 | .DESCRIPTION 6 | Update a powershell Gallery module upload profile 7 | .PARAMETER Name 8 | Module short name. 9 | .PARAMETER Path 10 | Path of module project files to upload. 11 | .PARAMETER ProjectUri 12 | Module project website. 13 | .PARAMETER Tags 14 | Tags used to search for the module (separated by spaces) 15 | .PARAMETER RequiredVersion 16 | Required powershell version (default is 2) 17 | .PARAMETER Repository 18 | Destination gallery (default is PSGallery) 19 | .PARAMETER ReleaseNotes 20 | Release notes. 21 | .PARAMETER LicenseUri 22 | License website. 23 | .PARAMETER IconUri 24 | Icon web path. 25 | .PARAMETER APIKey 26 | API key for the powershellgallery.com site. 27 | .PARAMETER OutputFile 28 | Input module configuration file (default is .psgallery) 29 | 30 | .EXAMPLE 31 | .NOTES 32 | Author: Zachary Loeber 33 | Site: http://www.the-little-things.net/ 34 | Version History 35 | 1.0.0 - Initial release 36 | #> 37 | [CmdletBinding()] 38 | param( 39 | [parameter(Position=0, HelpMessage='Module short name.')] 40 | [string]$Name, 41 | [parameter(Position=1, HelpMessage='Path of module project files to upload.')] 42 | [string]$Path, 43 | [parameter(Position=2, HelpMessage='Module project website.')] 44 | [string]$ProjectUri, 45 | [parameter(Position=3, HelpMessage='Tags used to search for the module (separated by spaces)')] 46 | [string]$Tags, 47 | [parameter(Position=4, HelpMessage='Required powershell version (default is 2)')] 48 | [string]$RequiredVersion, 49 | [parameter(Position=5, HelpMessage='Destination gallery (default is PSGallery)')] 50 | [string]$Repository, 51 | [parameter(Position=6, HelpMessage='Release notes.')] 52 | [string]$ReleaseNotes, 53 | [parameter(Position=7, HelpMessage=' License website.')] 54 | [string]$LicenseUri, 55 | [parameter(Position=9, HelpMessage='Icon web path.')] 56 | [string]$IconUri, 57 | [parameter(Position=10, HelpMessage='API key for the powershellgallery.com site.')] 58 | [string]$APIKey, 59 | [parameter(Position=11, HelpMessage='Input module configuration file (default is .psgallery)')] 60 | [string]$InputFile = '.psgallery' 61 | ) 62 | 63 | if (Test-Path $InputFile) { 64 | $PublishParams = Import-Clixml $InputFile 65 | $MyParams = $PSCmdlet.MyInvocation.BoundParameters 66 | $MyParams.Keys | Where {$_ -ne 'InputFile'} | ForEach { 67 | Write-Verbose "Updating $($_)" 68 | $PublishParams.$_ = $MyParams[$_] 69 | } 70 | 71 | $PublishParams | Export-Clixml -Path $InputFile -confirm 72 | } 73 | else { 74 | Write-Warning "InputFile was not found: $($InputFile)" 75 | } -------------------------------------------------------------------------------- /Scripts/Upgrade-InstalledModules.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 5 2 | 3 | <# 4 | .SYNOPSIS 5 | A small wrapper for PowerShellGet to upgrade all installed modules and scripts. 6 | .DESCRIPTION 7 | A small wrapper for PowerShellGet to upgrade all installed modules and scripts. 8 | .PARAMETER WhatIf 9 | Show modules which would get upgraded. 10 | .EXAMPLE 11 | Upgrade-InstalledModules.ps1 12 | 13 | Description 14 | ------------- 15 | Updates modules installed via PowerShellGet. 16 | .NOTES 17 | Author: Zachary Loeber 18 | Site: http://www.the-little-things.net/ 19 | Requires: Powershell 5.0 20 | 21 | Version History 22 | 1.0.0 - Initial release 23 | #> 24 | [CmdletBinding()] 25 | Param ( 26 | [Parameter(HelpMessage = 'Show modules which would get upgraded.')] 27 | [switch]$WhatIf 28 | ) 29 | 30 | try { 31 | Import-Module PowerShellGet 32 | } 33 | catch { 34 | Write-Warning 'Unable to load PowerShellGet. This script only works with PowerShell 5 and greater.' 35 | return 36 | } 37 | 38 | $WhatIfParam = @{WhatIf=$WhatIf} 39 | 40 | Get-InstalledModule | Update-Module -Force @WhatIfParam 41 | Get-InstalledScript | Update-Script @WhatIfParam -------------------------------------------------------------------------------- /Scripts/Upgrade-System.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | A small wrapper for choco upgrade all -y (or cup all -y) to prevent my PowerShell profile from getting tampered with 4 | .DESCRIPTION 5 | A small wrapper for choco upgrade all -y (or cup all -y) to prevent my PowerShell profile from getting tampered with 6 | .PARAMETER WhatIf 7 | Show applications which would get upgraded. 8 | .EXAMPLE 9 | Upgrade-System 10 | 11 | Description 12 | ------------- 13 | Updates system packages using chocolatey 14 | .NOTES 15 | Author: Zachary Loeber 16 | Site: http://www.the-little-things.net/ 17 | Requires: Powershell 3.0 18 | 19 | Version History 20 | 1.0.0 - Initial release 21 | #> 22 | [CmdletBinding()] 23 | Param ( 24 | [Parameter(HelpMessage = 'Show applications which would get upgraded.')] 25 | [switch]$WhatIf 26 | ) 27 | 28 | $SourceDir = Split-Path $Profile 29 | $SourceFile = Split-Path -Leaf $PROFILE 30 | 31 | try { 32 | $null = get-command cup -ErrorAction:Stop 33 | } 34 | catch { 35 | Write-Output 'Chocolatey is not installed!' 36 | Write-Output 'You can install it by running the following: ' 37 | Write-Output " iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" 38 | return 39 | } 40 | 41 | if (-not $WhatIf) { 42 | Write-Host -ForegroundColor Magenta 'Backing up the existing PowerShell profile before upgrading software' 43 | Write-Host -ForegroundColor Magenta 'This is largely to prevent some packages (posh-git) messing around with it.' 44 | Write-Host -ForegroundColor Magenta 'The profile script name will change to Profile.backup temporarily...' 45 | 46 | Rename-Item -Path $PROFILE -NewName 'Profile.backup' 47 | Write-Host -ForegroundColor Magenta 'Press any key to start a full upgrade of software on this system' 48 | Pause 49 | choco upgrade all -y 50 | Write-Host -ForegroundColor Magenta 'The profile script name will now be changed back after we remove any newly created profiles.' 51 | if (Test-Path $PROFILE) { 52 | Remove-Item $Profile -Confirm 53 | } 54 | Rename-Item -Path "$($SourceDir)\Profile.backup" -NewName $SourceFile 55 | } 56 | else { 57 | choco upgrade all -noop | Select-String -NotMatch "is the latest version" 58 | } -------------------------------------------------------------------------------- /Scripts/Upload-ProjectToPSGallery.ps1: -------------------------------------------------------------------------------- 1 | #Requires -version 5 2 | <# 3 | .SYNOPSIS 4 | Upload module project to Powershell Gallery 5 | .DESCRIPTION 6 | Upload module project to Powershell Gallery 7 | .PARAMETER ModulePath 8 | Path to module to upload. 9 | .PARAMETER APIKey 10 | API key for the powershellgallery.com site. 11 | .PARAMETER Tags 12 | Tags for your module 13 | .PARAMETER ProjectURI 14 | Project site (like github). 15 | .EXAMPLE 16 | .\Upload-ProjectToPSGallery.ps1 17 | .NOTES 18 | Author: Zachary Loeber 19 | Site: http://www.the-little-things.net/ 20 | Requires: Powershell 5.0 21 | 22 | Version History 23 | 1.0.0 - Initial release 24 | #> 25 | [CmdletBinding(DefaultParameterSetName='PSGalleryProfile')] 26 | param( 27 | [parameter(Mandatory=$true, 28 | HelpMessage='Module short name.', 29 | ParameterSetName='ManualInput')] 30 | [string]$Name, 31 | [parameter(Mandatory=$true, 32 | HelpMessage='Path of module project files to upload.', 33 | ParameterSetName='ManualInput')] 34 | [string]$Path, 35 | [parameter(HelpMessage='Module project website.', 36 | ParameterSetName='ManualInput')] 37 | [string]$ProjectUri, 38 | [parameter(HelpMessage='Tags used to search for the module (separated by spaces)', 39 | ParameterSetName='ManualInput')] 40 | [string]$Tags, 41 | [parameter(HelpMessage='Required powershell version (default is 2)', 42 | ParameterSetName='ManualInput')] 43 | [string]$RequiredVersion, 44 | [parameter(HelpMessage='Destination gallery (default is PSGallery)', 45 | ParameterSetName='ManualInput')] 46 | [string]$Repository = 'PSGallery', 47 | [parameter(HelpMessage='Release notes.', 48 | ParameterSetName='ManualInput')] 49 | [string]$ReleaseNotes, 50 | [parameter(HelpMessage=' License website.', 51 | ParameterSetName='ManualInput')] 52 | [string]$LicenseUri, 53 | [parameter(HelpMessage='Icon web path.', 54 | ParameterSetName='ManualInput')] 55 | [string]$IconUri, 56 | [parameter(Mandatory = $true, 57 | HelpMessage='API key for the powershellgallery.com site.', 58 | ParameterSetName='ManualInput')] 59 | [parameter(HelpMessage='API key for the powershellgallery.com site.', 60 | ParameterSetName='PSGalleryProfile')] 61 | [string]$NuGetApiKey, 62 | 63 | [parameter(HelpMessage='Path to CliXML file containing your psgallery project information.', 64 | ParameterSetName='PSGalleryProfile')] 65 | [string]$PSGalleryProfilePath = '.psgallery' 66 | ) 67 | 68 | Write-Verbose "Using parameterset $($PSCmdlet.ParameterSetName)" 69 | if ($PSCmdlet.ParameterSetName -eq 'PSGalleryProfile') { 70 | if (Test-Path $PSGalleryProfilePath) { 71 | Write-Verbose "Loading PSGallery profile information from $PSGalleryProfilePath" 72 | $PublishParams = Import-Clixml $PSGalleryProfilePath 73 | } 74 | else { 75 | Write-Error "$PSGalleryProfilePath not found" 76 | return 77 | } 78 | } 79 | else { 80 | $MyParams = $PSCmdlet.MyInvocation.BoundParameters 81 | $MyParams.Keys | ForEach { 82 | Write-Verbose "Adding manually defined parameter $($_)" 83 | $PublishParams.$_ = $MyParams[$_] 84 | } 85 | } 86 | 87 | # if no API key is defined then look for psgalleryapi.txt in the local profile directory and try to use it instead. 88 | if ([string]::IsNullOrEmpty($PublishParams.NuGetApiKey)) { 89 | $psgalleryapipath = "$(Split-Path $Profile)\psgalleryapi.txt" 90 | Write-Verbose "No PSGallery API key specified. Attempting to load one from the following location: $($psgalleryapipath)" 91 | if (-not (test-path $psgalleryapipath)) { 92 | Write-Error "$psgalleryapipath wasn't found and there was no defined API key, please rerun script with a defined APIKey parameter." 93 | return 94 | } 95 | else { 96 | $PublishParams.NuGetApiKey = get-content -raw $psgalleryapipath 97 | } 98 | } 99 | 100 | $PublishParams.Tags = $PublishParams.Tags -split ',' 101 | 102 | # If we made it this far then try to publish the module wth our loaded parameters 103 | Publish-Module @PublishParams --------------------------------------------------------------------------------