├── .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
--------------------------------------------------------------------------------