-
Notifications
You must be signed in to change notification settings - Fork 2
/
BeginSystemConfig.ps1
1618 lines (1441 loc) · 203 KB
/
BeginSystemConfig.ps1
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
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
########################################
#
# BeginSystemConfig.ps1
# iex ((New-Object System.Net.WebClient).DownloadString('https://git.io/JqCtf'));
#
# Author: roysubs@hotmail.com
#
# 2019-11-25 Initial setup
#
########################################
#
# The current Windows Defender heavily blocks this (and many other installs):
# https://theitbros.com/managing-windows-defender-using-powershell/
# https://technoresult.com/how-to-disable-windows-defender-using-powershell-command-line/
# https://evotec.xyz/import-module-this-script-contains-malicious-content-and-has-been-blocked-by-your-antivirus-software/
# https://superuser.com/questions/1503345/i-disabled-real-time-monitoring-of-windows-defender-but-a-powershell-script-is
#
# Even the initial Chocolatey installer can be affected if Windows Defender is not turned off:
# https://github.com/chocolatey/choco/issues/2132
#
# Must disable Windows Defender before running anything (iex is a possible reason for this block):
# sc.exe stop|start|query WinDefend # Note use of sc.exe and not sc (Set-Content alias in PowerShell)
# Set-MpPreference -DisableRealtimeMonitoring $true (to disable) or $false (to enable) # RealtimeMonitoring
#
########################################
#
# To install git project https://github.com/roysubs/Custom-Tools :
# iex ((New-Object System.Net.WebClient).DownloadString('https://git.io/JqCLn')) # Note: this git.io points to "main", which is good
#
# The https://git.io url shortener:
# https://raw.githubusercontent.com/roysubs/Custom-Tools/main/BeginSystemConfig.ps1
#
# ??? Quick download of BeginSystemConfig.ps1 + ProfileExtensions.ps1 + Custom-Tools.psm1
# ??? https://gist.githubusercontent.com/roysubs/1a5eef75a70065f8f2979ccf2703f322/raw/4d02c6237732e39be069fec7aa14a367b6258f19/Get-ExtensionsAndCustomTools.ps1
# ??? iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/roysubs/Custom-Tools/main/BeginSystemConfig.ps1'))
# Try to be compatible with PowerShell v2 (for Win 7 / Win 2012 etc, but this gets more difficult).
# Try to be compatible with PowerShell v7 (need to remove)
# Note: For testing on a system with PS v3+, can mimic v2 by opening a console with: powershell.exe -version 2
# Should do no *changes* to system in this, except updating PS to latest version + Chocolatey/Boxstarter Package Manager
#
# Key sections:
# Create as function as required in two locations depending on whether the console needs to be elevated or not.
function Write-Header {
Write-Host ""
Write-Host "========================================" -ForegroundColor Green
Write-Host 'Auto-Configure Custom-Tools Toolkit Setup' -ForegroundColor Green
Write-Host 'Add profile extensions, setup Custom-Tools.psm1, update PowerShell, Chocolatey, Boxstarter' -ForegroundColor Green
Write-Host 'Add PowerShell Gallery scripts to default Scripts folder, and essential Modules.' -ForegroundColor Green
Write-Host 'PowerShell Gallery retiremen and closure (2020-11-24)' -ForegroundColor Red
Write-Host 'https://docs.microsoft.com/en-us/teamblog/technet-gallery-retirement'
Write-Host ""
Write-Host '1. Add two lines to $Profile to handle profile extensions and Set-ExecutionPolicy RemoteSigned' -ForegroundColor Yellow
Write-Host ' This way the profile is minimally affected and changes can be rolled back simply by deleting' -Foreground Yellow
Write-Host ' the single profile extensions handler line from $Profile (notepad $Profile and delete the line)' -ForegroundColor Yellow
Write-Host '2. Apply latest PowerShell to system (also add .NET 4.0 / 4.5 required by various tools)' -ForegroundColor Yellow
Write-Host ' Install or update to latest Chocolatey and Boxstarter.' -ForegroundColor Yellow
Write-Host '3. Configure selected Modules to User Modules folder.' -ForegroundColor Yellow
Write-Host '4. Download selected scripts from PowerShell Gallery.' -ForegroundColor Yellow
Write-Host '5. (ToDo): Additional tools, Boxstarter configuration etc.' -ForegroundColor Yellow
Write-Host "========================================" -ForegroundColor Green
Write-Host ""
}
####################
#
# To ready the project:
# iwr 'https://git.io/JqCLn' | select -expand content | more # View BeginSystemConfig.ps1 on PS v3+
# Invoke-WebRequest (iwr) is not available on PowerShell v2. v2 compatible syntax is:
# (New-Object System.Net.WebClient).DownloadString('https://bit.ly/2R7znLX')
#
# GitHub access uses TLS. Sometimes (not always) have to specify this:
# [Net.ServicePointManager]::SecurityProtocol # To view current settings
# [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # Set type to TLS 1.2
# # Note that the above Tls12 is incompatible with Windows 7 which only has SSL3 and TLS as options.
# [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls # Set type to TLS
#
# GitHub pages are cached at the endpoint so after uploading to GitHub, the new data will not be immediately
# available and can take from ~10s to 180s to update. Clearing the DNS Client Cache might help:
# On PowerShell v2, use: [System.Net.ServicePointManager]::DnsRefreshTimeout = 0;
# On newer versions of PowerShell, can use: Clear-DnsClientCached
#
# Note the caching issues above. Some people sugget using a -Headers switch with iwr to try and get
# around that, but it has now worked reliably for me:
# - i.e. add -Headers @{"Cache-Control"="no-cache"} to iwr.
# iwr "https://gist.github.com/roysubs/61ef677591f22927afadc9ef2b657cd9/raw" -Headers @{"Cache-Control"="no-cache"} | select -Expand Content
# - Adding the iwr parameter "-DisableKeepAlive" is suggested as a fix to clear the caching, though I have not tested.
# - Preventing DNS caching seems to be a reliable fix so that iex / iwr are not using cached data. One of these should be run before iex / iwr
# to ensure that the version that iex / iwr pull is the latest.
# [System.Net.ServicePointManager]::DnsRefreshTimeout = 0
# Clear-DnsClientCache
# https://stackoverflow.com/questions/18556456/invoke-webrequest-working-after-server-is-unreachable
#
# Note that I have seen the following error on Windows 7 / 10:
# Exception calling "DownloadString" with "1" argument(s): "The request was aborted: Could not create SSL/TLS secure channel."
# Specifically fixing the security protocol to TLS in that session fixed this in some cases:
# [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls # (or optionally Tls12 on Windows 10)
# [Net.ServicePointManager]::SecurityProtocol # This will now show Tls instead of "Ssl3, Tls" and then can download
#
####################
####################
#
# Building this used the Posh-Gist Module which allows me to create and update source files (see Modules section below).
# I created a local folder and kept the project files in there and can either manually create the Gists online, or can
# use Posh-Gist with New-Gist. Created Gist-Push.ps1 and Gist-Pull.ps scripts to sync up project.
#
# # Gist-Push.ps1
# $cred = Get-Credential
# Update-Gist -Credential $cred -Id 61ef677591f22927afadc9ef2b657cd9 -Update .\BeginSystemConfig.ps1
# Update-Gist -Credential $cred -Id c37470c98c56214f09f0740fcb21ec4f -Update .\ProfileExtensions.ps1
# Update-Gist -Credential $cred -Id 5c6a16ea0964cf6d8c1f9eed7103aec8 -Update .\Custom-Tools.psm1
# Clear-DnsClientCache
#
# # Gist-Pull.ps1 (PowerShell v2 compatible
# $now = Get-Date -Format "yyyy-MM-dd__HH-mm-ss" # Adding datetime as pull is *dangerous*, can overwrite current work!
# (New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/roysubs/Custom-Tools/main/BeginSystemConfig.ps1') | Out-File .\BeginSystemConfig_$now.ps1
# (New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/roysubs/Custom-Tools/main/ProfileExtensions.ps1') | Out-File .\ProfileExtensions_$now.ps1
# (New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/roysubs/Custom-Tools/main/Custom-Tools.psm1') | Out-File .\Custom-Tools_$now.psm1
# # # Variant if using PowerShell 3+ which has Invoke-WebRequest (iwr):
# # iwr 'https://raw.githubusercontent.com/roysubs/Custom-Tools/main/BeginSystemConfig.ps1' | select -expand content | Out-File .\temp1.txt
# # iwr 'https://raw.githubusercontent.com/roysubs/Custom-Tools/main/ProfileExtensions.ps1' | select -expand content | Out-File .\temp2.txt
# # iwr 'https://raw.githubusercontent.com/roysubs/Custom-Tools/main/Custom-Tools.psm1' | select -expand content | Out-File .\temp3.txt
# # # -Headers @{"Cache-Control"="no-cache"} does not seem to help for caching issues
#
####################
####################
#
# Problems with PowerShell v2
#
# Invoke-WebRequest (iwr) does not exist on PS v2, but Invoke-Expression (iex) does
# System.Net.WebClient is the easiest way to do it for simple GET request but if you need to do a POST request
# for a form then you will need to use System.Net.HttpWebRequest. We only need WebClient here.
# Alternatively, could download curl or wget or replicate in PowerShell. Good discussion of methods at end of this:
# https://social.technet.microsoft.com/Forums/ie/en-US/55c7e306-cab5-4bdb-9825-1909d41fa2ca/simple-curl-in-powershell-20?forum=winserverpowershell
# (New-Object System.Net.WebClient).DownloadString('https://bit.ly/2R7znLX')
# or ...
# $url = "https://gist.githubusercontent.com/roysubs/61ef677591f22927afadc9ef2b657cd9/raw"
# $output = "C:\0\BeginSystemConfig.txt"
# # $start_time = Get-Date
# $wc = New-Object System.Net.WebClient
# $wc.DownloadFile($url, $output) # or ... (New-Object System.Net.WebClient).DownloadFile($url, $output)
# # Write-Output "Time taken: $((Get-Date).Subtract($start_time).Seconds) second(s)"# Often saw the following error on Gist addresses:
# Another issue even for more modern systems is that Invoke-WebRequest does not work if IE is not 'initialized'(!).
# So when writing pipeline or DSC scripts you have to do some extra legwork first or you can use Invoke-RestMethod instead.
# On using BITS:
# Import-Module BitsTransfer (not required on PS v5 setups)
# Start-BitsTransfer -Source <url> -Destination <file-to-create-on-disk>
#
# https://social.technet.microsoft.com/Forums/en-US/eda9171e-879f-473b-bb48-687ad87fedd7/using-serviceui-powershell-and-bits-exception-from-hresult-0x800704dd?forum=ConfigMgrAppManagement
# https://social.technet.microsoft.com/Forums/ie/en-US/f006636c-27b1-46fc-8e7e-0530269c380c/startbitstransfer-powershell-remote-action-errors?forum=winserverpowershell
# https://4sysops.com/archives/use-powershell-to-download-a-file-with-http-https-and-ftp/
# https://www.thewindowsclub.com/download-file-using-windows-powershell
# https://blog.jourdant.me/post/3-ways-to-download-files-with-powershell
# https://superuser.com/questions/362152/native-alternative-to-wget-in-windows-powershell
# https://gallery.technet.microsoft.com/scriptcenter/Downloading-Files-from-dcaaf44c
# https://stackoverflow.com/questions/7715695/http-requests-with-powershell
# https://stackoverflow.com/questions/4988286/what-difference-is-there-between-webclient-and-httpwebrequest-classes-in-net
# https://stackoverflow.com/questions/7715695/http-requests-with-powershell/20941552#20941552
#
# 'Pause' is not available on PowerShell v2 so can use the below, or can just use Confirm-Choice function below (easier)
# Write-Host "Press Enter to continue...:" ; cmd /c pause | out-null
#
# "Install-Module" does not exist in PowerShell v2
#
# Get-CimInstance fails on PowerShell v2. Need to fix this (todo) for PS v2 for the Adminstrator check.
# Replace: ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber)
# With: (Get-CimInstance Win32_OperatingSystem).BuildNumber)
# Or: remove that check altogether, it is for
#
####################
####################
####################
####################
####################
#
# Header Block
#
# param() must be the first line in a script if used.
# Confirm-Choice and Elevation should be right after that.
#
####################
# param([string]$runsilently)
# $runsilenty = $false
#
# Have attempted ways to call a script online *with* parameters, but have not found a solution yet.
# Simple workaround, just put a Confirm-Choice at start to ask if want to run attended or not. Works well.
# Other options:
# - Create unnamed scriptblock from the url when invoke with call operator &), have not got this working:
# & $([scriptblock]::Create((New-Object Net.WebClient).DownloadString(https://bit.ly/2R7znLX))) silent
# https://stackoverflow.com/questions/40958417/run-powershell-script-from-url-passing-parameters
# - Download all files to temp folder then use parameters, but again cannot just kick-off from iex, so is
# also not ideal. Downloading and running would work something like:
# (New-Object System.Net.WebClient).DownloadString('https://bit.ly/2R7znLX') | Out-File "$($env:TEMP)\BeginSystemConfig.ps1" ; "$($env:TEMP)\BeingSystemConfig.ps1" <param1> <param2> ...
# https://stackoverflow.com/questions/33205298/how-to-use-powershell-to-download-a-script-file-then-execute-it-with-passing-in
####################
#
# Note optional settings to force strict limits on script execution:
# Set-StrictMode -Version Latest
# $ErrorActionPreference = 'Stop'
#
####################
####################
#
# Note on Exit / Return / Throw:
# Exit will normally exit the script and the console when in main body of a script. The only ways to get the script to exit
# but not exit the console are to put an exit inside a function, or to use a throw statement to terminate script processing.
# Using 'exit' in a function is cleaner overall (but note that in a function library called by another script, then 'return'
# should be used). BUT(!), have now discovered that the above is true for running locally, where using 'exit' is best, but
# when calling via iex, the 'exit' will kill the console(!), so falling back to using 'throw'.
# https://stackoverflow.com/questions/2022326/terminating-a-script-in-powershell
# Alao note the global solution here (solution 3). https://blog.danskingdom.com/keep-powershell-console-window-open-after-script-finishes-running/
#
####################
# Variables, create HomeFix in case of network shares (as always want to use C:\ drive, so get the name (Leaf) from $HOME)
# Need paths that fix the "Edwin issue" i.e. UsernName has changed from the path that in $env:USERPROFILE
# And also to fix the issue with VPN network paths, so check for "\\" in the profile, # $UserNameFriendly = $env:UserName
$HomeFix = $HOME
$HomeLeaf = split-path $HOME -leaf # Just get the correct username in spite of any changes to username (as on Edwin's system where username =/= foldername)
$WinId = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name # This returns Hostname\WindowsIdentity, where WindowsIdentity can be different from UserProfile folder name
if ($HomeFix -like "\\*") { $HomeFix = "C:\Users\$(Split-Path $HOME -Leaf)" }
$UserModulesPath = "$HomeFix\Documents\WindowsPowerShell\Modules" # $UserModulesPath = "C:\Users\$HomeLeaf\Documents\WindowsPowerShell\Modules"
$UserScriptsPath = "$HomeFix\Documents\WindowsPowerShell\Scripts"
$AdminModulesPath = "C:\Program Files\WindowsPowerShell\Modules"
# The default Modules and Scripts paths are not created by default in Windows
if (!(Test-Path $UserModulesPath)) { md $UserModulesPath -Force -EA silent | Out-Null }
if (!(Test-Path $UserScriptsPath)) { md $UserScriptsPath -Force -EA silent | Out-Null }
$OSver = (Get-CimInstance win32_operatingsystem).Name
$PSver = $PSVersionTable.PSVersion.Major
# Get-Content -Path $profile | Select-String -Pattern "^function.+" | ForEach-Object {
# [Regex]::Matches($_, "^function ([a-z.-]+)","IgnoreCase").Groups[1].Value
# } | Where-Object { $_ -ine "prompt" } | Sort-Object
function Test-Administrator {
$user = [Security.Principal.WindowsIdentity]::GetCurrent();
(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
# https://codingbee.net/powershell/powershell-make-a-permanent-change-to-the-path-environment-variable
# https://www.computerperformance.co.uk/powershell/env-path/
function AddTo-Path {
param (
[string]$PathToAdd,
[Parameter(Mandatory=$true)][ValidateSet('System','User')] [string]$UserType,
[Parameter(Mandatory=$true)][ValidateSet('Path','PSModulePath')][string]$PathType
)
# AddTo-Path "C:\XXX" 'System' "PSModulePath"
if ($UserType -eq "User" ) { $RegPropertyLocation = 'HKCU:\Environment' } # also note: Registry::HKEY_LOCAL_MACHINE\ format
if ($UserType -eq "System" ) { $RegPropertyLocation = 'HKLM:\System\CurrentControlSet\Control\Session Manager\Environment' }
"`nAdd '$PathToAdd' (if not already present) into the $UserType `$$PathType"
"The '$UserType' environment variables are held in the registry at '$RegPropertyLocation'"
try { $PathOld = (Get-ItemProperty -Path $RegPropertyLocation -Name $PathType -EA silent).$PathType } catch { "ItemProperty is missing" }
"`n$UserType `$$PathType Before:`n$PathOld`n"
$PathOld = $PathOld -replace "^;", "" -replace ";$", "" # After displaying actual old path, remove leading/trailing ";" (also .trimstart / .trimend)
$PathArray = $PathOld -split ";" -replace "\\+$", "" # Create the array, removing network locations???
if ($PathArray -notcontains $PathToAdd) {
"$UserType $PathType Now:" # ; sleep -Milliseconds 100 # Might need pause to prevent text being after Path output(!)
$PathNew = "$PathOld;$PathToAdd"
Set-ItemProperty -Path $RegPropertyLocation -Name $PathType -Value $PathNew
Get-ItemProperty -Path $RegPropertyLocation -Name $PathType | select -ExpandProperty $PathType
if ($PathType -eq "Path") { $env:Path += ";$PathToAdd" } # Add to Path also for this current session
if ($PathType -eq "PSModulePath") { $env:PSModulePath += ";$PathToAdd" } # Add to PSModulePath also for this current session
"`n$PathToAdd has been added to the $UserType $PathType"
}
else {
"'$PathToAdd' is already in the $UserType $PathType. Nothing to do."
}
}
# Update PSModulePath (User), check/add the default Modules location $UserModulesPath to the user PSModulePath
AddTo-Path $UserModulesPath "User" "PSModulePath" | Out-Null # Add the correct Used Modules path to PSModulePath
# Update Path (User), check/add the default Scripts location $UserScriptsPath to the user Path
AddTo-Path $UserScriptsPath "User" "Path" | Out-Null # Add the correct Used Scripts path to Path
function ThrowScriptErrorAndStop {
""
throw "This is not an error. Using the 'throw' command here to halt script execution`nas 'return' / 'exit' have issues when run with Invoke-Expression from a URL ..."
}
function Confirm-Choice {
param ( [string]$Message )
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Yes";
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "No";
$choices = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no);
$caption = "" # Did not need this before, but now getting odd errors without it.
$answer = $host.ui.PromptForChoice($caption, $message, $choices, 0) # Set to 0 to default to "yes" and 1 to default to "no"
switch ($answer) {
0 {return 'yes'; break} # 0 is position 0, i.e. "yes"
1 {return 'no'; break} # 1 is position 1, i.e. "no"
}
}
$unattended = $false # default condition is to ask user for input
$confirm = 'Do you want to continue?' # Apart from unattended question, this is used for all other $Message values in Confirm-Choice.
if ((Get-ExecutionPolicy -Scope LocalMachine) -eq "Restricted") {
"Get-ExecutionPolicy -Scope LocalMachine => Restricted"
"To allow scripts to run, you need to change the Execution Policy from an Administrator console."
"'Set-ExecutionPolicy RemoteSigned' or 'Set-ExecutionPolicy Unrestricted' will permit this."
"One of these is required to run `$profile and additional scripts in this configuration."
""
if ($(Confirm-Choice "Stop this configuration run until the ExecutionPolicy is configured?`nSelect 'n' to continue anyway (expect errors).") -eq "no") { $unattended = $true }
}
function Write-Wrap {
<#
.SYNOPSIS
Wraps a string or an array of strings at the console width so that no word is broken at line enddings and neatly folds to multiple lines
https://stackoverflow.com/questions/1059663/is-there-a-way-to-wordwrap-results-of-a-powershell-cmdlet#1059686
.PARAMETER chunk
A string or an array of strings
.EXAMPLE
Write-Wrap -chunk $string
.EXAMPLE
$string | Write-Wrap
#>
[CmdletBinding()]Param(
[parameter(Mandatory=1, ValueFromPipeline=1, ValueFromPipelineByPropertyName=1)] [Object[]]$chunk
)
PROCESS {
$Lines = @()
foreach ($line in $chunk) {
$str = ''
$counter = 0
$line -split '\s+' | % {
$counter += $_.Length + 1
if ($counter -gt $Host.UI.RawUI.BufferSize.Width) {
$Lines += ,$str.trim()
$str = ''
$counter = $_.Length + 1
}
$str = "$str$_ "
}
$Lines += ,$str.trim()
}
$Lines
}
}
####################
#
# Note on using Write-Host: Write-Host is considered bad, or at least, it does not play nicely with the pipeline. Write-Host
# ignores the pipeline and just fires things onto the screen. This is bad from a pipeline perspective as the pipeline does
# things in a different fashion waiting for the pipeline to close etc. So, using Write-Host means that you will see some
# outputs muddled up. i.e. Outputs from the pipeline can happen after a Write-Host that comes later on in a script.
# There are good workarounds (mainly to make a function like Write-Host that is pipeline compliant), and they might be
# worth using, but for now I'm just sticking with Write-Host and using the "pipeline cludge" which is to put " | Out-Host"
# on the end of a few pipeline commands that might get jumbled up in ordering to make them adhere to Out-Host sequential
# ordering. i.e. With " | Out-Host" on the end of a Pipeline Cmdlet line, then the output will be ordered sequentially
# along with Write-Host commands.
# Alternatives (pipeline compliant colour outputting Cmdlets):
# https://stackoverflow.com/questions/59220186/usage-of-write-output-is-very-unreliable-compared-to-write-host/59228534#59228534
# https://stackoverflow.com/questions/2688547/multiple-foreground-colors-in-powershell-in-one-command/46046113#46046113
# https://jdhitsolutions.com/blog/powershell/3462/friday-fun-out-conditionalcolor/
# https://www.powershellgallery.com/packages?q=write-coloredoutput
#
# The best approach might be to bypass Write-Host with a custom function (functions take priority over Cmdlets as
# shown here): https://stackoverflow.com/questions/33747257/can-i-override-a-powershell-native-cmdlet-but-call-it-from-my-override
#
####################
""
# Test the path that this script is running from. If this is $null, then it was stated by iex from a URL.
$BeginPath = $MyInvocation.MyCommand.Path # echo $BeginPath
$UrlConfig = 'https://raw.githubusercontent.com/roysubs/Custom-Tools/main/BeginSystemConfig.ps1' # Check master/main confusion in case project changes
$UrlProfileExtensions = 'https://raw.githubusercontent.com/roysubs/Custom-Tools/main/ProfileExtensions.ps1'
$UrlCustomTools = 'https://raw.githubusercontent.com/roysubs/Custom-Tools/main/Custom-Tools.psm1'
if ($BeginPath -eq $null) {
"Scripts are being downloaded and installed from internet (github)."
# This is case when $BeginPath is null, i.e. the script was run via the web, so have to download all scripts.
# (New-Object System.Net.WebClient).DownloadString('https://bit.ly/2R7znLX') | Out-File "$env:TEMP\BeginSystemConfig.ps1"
(New-Object System.Net.WebClient).DownloadString($UrlConfig) | Out-File "$env:TEMP\BeginSystemConfig.ps1" -Force
(New-Object System.Net.WebClient).DownloadString($UrlProfileExtensions) | Out-File "$env:TEMP\ProfileExtensions.ps1" -Force
(New-Object System.Net.WebClient).DownloadString($UrlCustomTools) | Out-File "$env:TEMP\Custom-Tools.psm1" -Force
$BeginPath = "$env:TEMP\BeginSystemConfig.ps1"
$ScriptSetupPath = Split-Path $BeginPath # Copy the files to TEMP as staging area for local or downloaded files
}
else {
"Scripts are being installed locally from: $BeginPath"
# If the path is not null, then the script was run from the filesystem, so the scripts should be here.
# First, test if all scripts are here, if they are not, then no point in continuing.
$ScriptSetupPath = Split-Path $BeginPath # Copy the files to TEMP as staging area for local or downloaded files
if ((Test-Path "$ScriptSetupPath\BeginSystemConfig.ps1") -and (Test-Path "$ScriptSetupPath\ProfileExtensions.ps1") -and (Test-Path "$ScriptSetupPath\Custom-Tools.psm1")) {
# If the running scripts are not in TEMP, then copy them there and overwrite
# Slight issue! When elevating to admin, the called scripts are in TEMP(!), so skip copying as will be to same location!
if (Test-Path "$ScriptSetupPath\BeginSystemConfig.ps1") { Copy-Item "$ScriptSetupPath\BeginSystemConfig.ps1" "$env:TEMP\BeginSystemConfig.ps1" -Force }
if (Test-Path "$ScriptSetupPath\ProfileExtensions.ps1") { Copy-Item "$ScriptSetupPath\ProfileExtensions.ps1" "$env:TEMP\ProfileExtensions.ps1" -Force }
if (Test-Path "$ScriptSetupPath\Custom-Tools.psm1") { Copy-Item "$ScriptSetupPath\Custom-Tools.psm1" "$env:TEMP\Custom-Tools.psm1" -Force }
}
else {
"Scripts not found."
" - BeginSystemConfig.ps1"
" - ProfileExtensions.ps1"
" - Custom-Tools.psm1"
"Make sure that all required scripts are available."
"Exiting script..."
pause
exit
}
}
####################
#
# Self-elevate script if required.
#
####################
####################
#
# With the new setup, use the users Module folder instead of C:\ProgramData\WindowsPowerShell\Modules
# \\ad.ing.net\WPS\NL\P\UD\200024\YA64UG\Home\My Documents\WindowsPowerShell\Modules
# Redirect to => C:\Users\YA64UG\Documents\WindowsPowerShell\Modules
# Profile loading times are *extremely* slow when running on an ING EndPoint connection.
# Laptop without EndPoint => PowerShell loads in <1 sec
# Laptop with EndPoint => PowerShell loads in 6.5 to 9 sec
# Laptop with EndPoint => PowerShell loads in 3.5 sec (if move rofile extensions to C:\ locally)
# Laptop with EndPoint => PowerShell loads in 1 sec (if also load C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1)
# Plan is then:
# a) Try to move to profile.ps1 under System32 (will only work if TestAdministrator -eq True)
# b) Use default user folder if cannot do that. i.e. \\ad.ing.net\WPS\NL\P\UD\200024\YA64UG\Home\My Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
# But this is not a problem because b) is only a problem if on a laptop with VPN because if on a server etc, the default user folder is fine!
# So, if on my laptop:
# Profile = [C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1] + [C:\User\YA64UG\WindowsPowerShell\profile.ps1]
# if (Test-File $Profile) { rename $Profile $Profile-disabled }
# Modules = [C:\Users\YA64UG\Documents\WindowsPowerShell\Modules]
# Check that this is in $env:PSModulePath
# Delete the H: version of Modules completely!
#
# 1. if (Test-Administrator -eq True) => ask if want Admin install C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1 or Default User install
# Suggest to only do an Admin install on personal laptop
# 2.
# 3. Scripts => C:\Users\YA64UG\Documents\WindowsPowerShell\Scripts
# Make sure that this is on the path
# mklinks => C:\PS\ (so profile will be at C:\PS\profile.ps1), C:\PS\Scripts, C:\PS\Modules
# No, can't do this as might not be admin!
# Just leave at normal locations but have go definitions to jump to these locations.
#
# # To get around the ING problem with working from home.
# By mirroring the normal profile location, then everything else will work normally.
# Profile extensions will be placed in the local profile location rather than the ING one.
# "Microsoft.PowerShell_profile.ps1_extensions.ps1"
# By only appyling this to systems from ING, everything else will work normally.
# So the below is what should be pushed to C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
#
# # # # if ($env:USERNAME -eq "ya64ug") {
# if ($(get-ciminstance -class Win32_ComputerSystem).PrimaryOwnerName -eq "ING")
# $PROFILE.CurrentUserAllHosts = "$HomeFix\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
# $PROFILE.CurrentUserCurrentHost = "$HomeFix\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
# . $PROFILE
# cd $HomeFix
# }
#
# $PROFILE = "$HomeFix\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
#
# Note that the above breaks the $PROFILE definition!
# ($PROFILE).GetType()
# IsPublic IsSerial Name BaseType
# -------- -------- ---- --------
# True True String System.Object
#
# $PROFILE | Format-List * -Force
# AllUsersAllHosts : C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
# AllUsersCurrentHost : C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1
# CurrentUserAllHosts : \\ad.ing.net\WPS\NL\P\UD\200024\YA64UG\Home\My Documents\WindowsPowerShell\profile.ps1
# CurrentUserCurrentHost : \\ad.ing.net\WPS\NL\P\UD\200024\YA64UG\Home\My Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
# Length : 107
#
# But, after the change above, making $PROFILE a single string:
# $PROFILE | Format-List * -Force
# C:\Users\ya64ug\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
#
# So instead, just change those parts:
# $PROFILE.CurrentUserAllHosts = "$HomeFix\Documents\WindowsPowerShell\profile.ps1"
# $PROFILE.CurrentUserCurrentHost = "$HomeFix\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
#
# More here: https://devblogs.microsoft.com/scripting/understanding-the-six-powershell-profiles/
#
####################
# Define general Test-Administrator here and the full code to auto-elevate the script to run as Admin
# Moving away from auto-elevation so can manage everything in User folders (so scripts can run on any system)
function Test-Administrator {
$user = [Security.Principal.WindowsIdentity]::GetCurrent();
(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
# if ( ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -ne $True) {
# Write-Header
# Write-Host "Script can only continue if user is Elevated / Administrator ..." -ForegroundColor White -BackgroundColor Red
# Write-Host "Current console is not Elevated so will attempt to auto-elevate." -ForegroundColor Red -BackgroundColor White
# Write-Host ""
# # if (!(Test-Path("$($env:TEMP)\ElevatedScriptStop.txt"))) { New-Item -ItemType File "$($env:TEMP)\ElevatedScriptStop.txt" }
# if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
# # if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) { # Avoid Get-CimInstance for PS v2 compatibility
# # if (((Get-CimInstance Win32_OperatingSystem).BuildNumber) -ge 6000) {
#
# # If the script was run locally, just copy that (and all other scripts) to $env:TEMP and use them.
# # If the script was run from a url, then download latest copies from there.
# if ($BeginPath -eq $null) {
# (New-Object System.Net.WebClient).DownloadString('https://bit.ly/2R7znLX') | Out-File "$($env:TEMP)\BeginSystemConfig.ps1"
# }
# # Scripts were copied to TEMP earlier, will only be changed if was downloaded from Gist
# $CommandLine = "-File `"" + "$($env:TEMP)\BeginSystemConfig.ps1" + "`""
# if (Test-Path (Join-Path -Path "$($PSHOME)" -ChildPath "powershell.exe")) { $HostBinary = Join-Path -Path "$($PSHOME)" -ChildPath "powershell.exe" } # PowerShell 5.1
# if (Test-Path (Join-Path -Path "$($PSHOME)" -ChildPath "pwsh.exe")) { $HostBinary = Join-Path -Path "$($PSHOME)" -ChildPath "pwsh.exe" } # PowerShell Core 6 / 7
# try { Start-Process -FilePath $HostBinary -Verb Runas -ArgumentList $CommandLine -EA silent }
# catch { echo "wtf" }
# "Stopping script here using a 'throw' statement as exit/return can close window"
# "when script is called from internet URL."
# ""
# ThrowScriptErrorAndStop
# }
# }
# An argument for downloading and then running scripts is that we can then self-elevate the script.
# This is not possible if calling the script from a URL via Invoke-Expression, so for this situation
# modify the block to download the script and then call the script.
# Add this snippet at the beginning of any script that requires elevation to run properly. It works by
# starting a new elevated PowerShell window and then re-executes the script in this new window, if necessary.
# If User Account Control (UAC) is enabled, you will get a UAC prompt. If the script is already running in an
# elevated PowerShell session or UAC is disabled, the script will run normally. This code also allows you to
# right-click the script in File Explorer and select "Run with PowerShell".
# http://www.expta.com/2017/03/how-to-self-elevate-powershell-script.html
# This is the unmodified code block from the above. Try to use with as little modification sa possible.
# In fact, no need to modify, just prompt at start of script if want to run unattended or attended.
#
# if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
# if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
# $CommandLine = "-File `"" + $MyInvocation.MyCommand.Path + "`" " + $MyInvocation.UnboundArguments
# Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList $CommandLine
# Exit
# }
# }
# In fact, *must* modify. If elevating, the script will restart, but if called from iex, then it has nothing
# to restart! In that case, must download the scripts and run them locally.
# iwr 'https://gist.github.com/roysubs/c37470c98c56214f09f0740fcb21ec4f/raw' | select -expand content | Out-File .\temp\ProfileExtensions.ps1
# iwr 'https://gist.github.com/roysubs/5c6a16ea0964cf6d8c1f9eed7103aec8/raw' | select -expand content | Out-File .\temp\Custom-Tools.psm1
# iwr 'https://gist.github.com/roysubs/908525ae135e7d31a4fd13bd111b50e9/raw' | select -expand content | Out-File .\temp\Gist-Push.ps1
Write-Header
# Elevation will restart the script so don't ask this question until after that point
if ($(Confirm-Choice "Prompt all main action steps during setup?`nSelect 'n' to make all actions unattended.") -eq "no") { $unattended = $true }
####################
#
# Main Script
#
####################
$start_time = Get-Date # Put following lines at end of script to time it
# $hr = (Get-Date).Subtract($start_time).Hours ; $min = (Get-Date).Subtract($start_time).Minutes ; $sec = (Get-Date).Subtract($start_time).Seconds
# if ($hr -ne 0) { $times += "$hr hr " } ; if ($min -ne 0) { $times += "$min min " } ; $times += "$sec sec"
# "Script took $times to complete" # $((Get-Date).Subtract($start_time).TotalSeconds)
# Set-ExecutionPolicy RemoteSigned
Write-Host ""
Write-Host "Try to set the execution policy to RemoteSigned for this system" -ForegroundColor Yellow -BackgroundColor Black
try {
Set-ExecutionPolicy RemoteSigned -Force # Need
} catch {
Write-Host "`nWARNING: 'Set-ExecutionPolicy RemoteSigned' failed to execute." -ForegroundColor Yellow
Write-Host "This is often due to Group Policy restrictions on corporate builds.`n" -ForegroundColor Yellow
Write-Host "'Get-ExecutionPolicy -List' (show current execution policy list):`n"
Get-ExecutionPolicy -List | ft
}
# Set TLS for GitHub compatibility
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls } catch { } # Windows 7 compatible
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { } # Windows 10 compatible
# BeingSystemConfig.ps1 to initialise everything, including chocolatey / boxstarter setup.
# Boxstarter module should be installed to get access to Install-BoxstarterPackage
Write-Host ""
Write-Host 'The object of this script is to configure basic System and PowerShell configurations.'
Write-Host 'Avoiding extensive modifications and sticking to core functionality. This control'
Write-Host 'script calls online Gists for all configuration (can also be run offline if the'
Write-Host 'internet is unavailable and the files are all in the same folder). To customise for'
Write-Host 'your own needs, the three Gists can be copied, edited, and then executed using a single'
Write-Host 'command to distribute all required settings to a new system.'
Write-Host ""
Write-Host "# The current script is used to chain together these system configurations" -ForegroundColor DarkGreen
Write-Host "iwr '$UrlConfig' | select -expandproperty content | more" -ForegroundColor Magenta
Write-Host "(New-Object System.Net.WebClient).DownloadString('$UrlConfig')"
Write-Host "# The Profile Extension script extends `$profile" -ForegroundColor DarkGreen
Write-Host "iwr '$UrlProfileExtensions' | select -expandproperty content | more" -ForegroundColor Magenta
Write-Host "(New-Object System.Net.WebClient).DownloadString('$UrlProfileExtensions')"
Write-Host "# Custom-Tools.psm1 Module to make useful generic functions available to console." -ForegroundColor DarkGreen
Write-Host "iwr '$UrlCustomTools' | select -expandproperty content | more" -ForegroundColor Magenta
Write-Host "(New-Object System.Net.WebClient).DownloadString('$UrlCustomTools')"
Write-Host ""
# Deprecated requirement to install as Admin, but leave this for reference.
# Write-Host "Script can only continue if user is Elevated / Administrator ..." -ForegroundColor White -BackgroundColor Red
# if ( ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -ne $True) {
# Write-Host "Current console is not Elevated so script will exit. Rerun as Administrator to fix this." -ForegroundColor Red -BackgroundColor White
# Write-Host ""
# ThrowScriptErrorAndStop
# } else {
# Write-Host "Current console is Elevated so script can continue." -ForegroundColor Green
# }
Write-Host 'Please review the above URLs to check that the changes are what you expect before continuing.'
Write-Host ""
# https://stackoverflow.com/questions/12522539/github-gist-editing-without-changing-url
if ($unattended -eq $false) { if ($(Confirm-Choice $confirm) -eq "no") { ThrowScriptErrorAndStop } }
####################
#
# Setup Profile Extensions and Custom Tools Module
#
####################
Write-Host ''
Write-Host ''
Write-Host "`n========================================" -ForegroundColor Green
Write-Host ''
Write-Host '1. Configure Custom-Tools and setup Profile Extensions in $Profile.' -ForegroundColor Green
Write-Host ''
Write-Host ' Check for the profile extensions in same folder as $Profile. If not present, the' -ForegroundColor Yellow
Write-Host ' latest profile extensions will be downloaded from Gist. A single line will then be' -ForegroundColor Yellow
Write-Host ' added into $Profile to load the profile extensions from $Profile into all new consoles.' -ForegroundColor Yellow
Write-Host ''
Write-Host ' To force the latest profile extensions, either rerun this script which will overwrite it' -ForegroundColor Yellow
Write-Host ' or delete the profile extensions and it will be downloaded on opening the next console.' -ForegroundColor Yellow
Write-Host ''
Write-Host ' The reason for having the profile extensions separately is to keep the profile clean and' -ForegroundColor Yellow
Write-Host ' to keep additions synced against a known working online copy.' -ForegroundColor Yellow
if ($OSver -like "*Server*") {
Write-Host ''
Write-Host " The Operating System is of type 'Server' so profile extensions will *not* load" -ForegroundColour Yello -BackgroundColor Black
Write-Host " by default in any console. Profile extensions can be loaded on demand by running" -ForegroundColour Yellow -BackgroundColor Black
Write-Host " the 'Enable-Extensions' function that will now be setup in `$profile." -ForegroundColour Yellow -BackgroundColor Black
}
Write-Host ''
Write-Host "========================================`n" -ForegroundColor Green
Write-Host ''
Write-Host ""
Write-Host "Update and reinstall Custom-Tools.psm1 Module." -ForegroundColor Yellow -BackgroundColor Black
Write-Host "The Module contains many generic functions (including some required later in this setup)."
Write-Host "View functions in the module with 'myfunctions' / 'mods' / 'mod custom-tools' or view module contents with:"
Write-Host " get-command -module custom-tools" -ForegroundColor Yellow
$CustomTools = "$HomeFix\Documents\WindowsPowerShell\Modules\Custom-Tools\Custom-Tools.psm1"
if ([bool](Get-Module Custom-Tools -ListAvailable) -eq $true) {
if ($PSver -gt 4) { Uninstall-Module Custom-Tools -EA Silent -Force -Verbose } # Uninstall-Module is only in PS v4+
else { "Need to be running PS v5 or higher to run Uninstall-Module" }
}
if (!(Test-Path (Split-Path $CustomTools))) { New-Item (Split-Path $CustomTools) -ItemType Directory -Force } # Create folder if required
if (Test-Path ($CustomTools)) { rm $CustomTools } # Delete old version of the .psm1 if it was already there
"$BeginPath is currently running script."
"$(Split-Path $BeginPath)\Custom-Tools.psm1 will be used to load Custom-Tools Module."
if ($BeginPath -eq $null) {
# Case when scripts were downloaded
# try { (New-Object System.Net.WebClient).DownloadString("$UrlCustomTools") | Out-File $CustomTools ; echo "Downloaded Custom-Tools.psm1 Module from internet ..." }
# catch { Write-Host "Failed to download! Check internet/TLS settings before retrying." -ForegroundColor Red }
Copy-Item "$ScriptSetupPath\Custom-Tools.psm1" $CustomTools -Force
}
else {
# First try to use the version in same folder as BeginSystemConfig
Copy-Item "$ScriptSetupPath\Custom-Tools.psm1" $CustomTools -Force
Write-Host "$ScriptSetupPath\Custom-Tools.psm1 was copied to Custom-Tools Path: $CustomTools"
Write-Host "Using local version of Custom-Tools.psm1 from $BeginPath ..."
}
# If still have nothing, try to download from Github
# else {
# try { (New-Object System.Net.WebClient).DownloadString("$UrlCustomTools") | Out-File $CustomTools }
# catch { Write-Host "Failed to download! Check internet/TLS settings before retrying." -ForegroundColor Red }
# }
# }
Import-Module Custom-Tools -Force -Verbose # Don't require full path, it should search for it in standard PSModulePaths
$x = ""; foreach ($i in (get-command -module Custom-Tools).Name) {$x = "$x, $i"} ; "" ; Write-Wrap $x.trimstart(", ") ; ""
# Alternative method to dotsource all scripts in a given folder along with $Profile
# $MyFunctionsDir = "$env:USERPROFILE\Documents\WindowsPowerShell\Functions"
# Get-ChildItem "$MyFunctionsDir\*.ps1" | % {.$_}
if ($unattended -eq $false) { if ($(Confirm-Choice $confirm) -eq "no") { ThrowScriptErrorAndStop } }
$ProfileFolder = $Profile | Split-Path -Parent
$ProfileFile = $Profile | Split-Path -Leaf
$ProfileExtensions = Join-Path $ProfileFolder "$($ProfileFile)_extensions.ps1"
Write-Host "Profile : $Profile"
Write-Host "ProfileExtensions : $ProfileExtensions"
Write-Host ""
Write-Host "Note that the Profile path below is determined by the currently running 'Host'."
Write-Host "i.e. The host is usually either the PowerShell Console, or PowerShell ISE, or"
Write-Host "Visual Studio Code, each of which will have their own Profile path which you can"
Write-Host "see by typing `$Profile from within that given host. Other hosts can exist, such"
Write-Host "as PowerShell Core running under Linux, but the above are the most usual on Wwindows."
Write-Host ""
Write-Host "You can see more information on the current host by typing '`$host' at the console:"
$host # Will show the current host
# Create $Profile folder and file if they do not exist
if (!(Test-Path $ProfileFolder)) { New-Item -Type Directory $ProfileFolder -Force }
if (!(Test-Path $Profile)) { New-Item -Type File $Profile -Force }
# Create a backup of the extensions in case user has modified this directly.
if (Test-Path ($ProfileExtensions)) {
Write-Host "`nCreating backup of existing profile extensions in case download fails ..."
Move-Item -Path "$($ProfileExtensions)" -Destination "$($ProfileExtensions)_$(Get-Date -format "yyyy-MM-dd__HH-mm-ss").txt"
}
Write-Host "`nGet latest profile extensions (locally if available or download) ..."
if ($BeginPath -eq $null) { # If BeginPath is null, files should still be in env:TEMP from previous download
# try { (New-Object System.Net.WebClient).DownloadString("$UrlProfileExtensions") | Out-File $ProfileExtensions -Force ; echo "Downloaded profile extensions from internet ..." }
# catch { Write-Host "Failed to download! Check internet/TLS settings before retrying." -ForegroundColor Red }
Copy-Item "$ScriptSetupPath\ProfileExtensions.ps1" $ProfileExtensions -Force
} else {
# First try to use the version in same folder as BeginSystemConfig
Copy-Item "$ScriptSetupPath\ProfileExtensions.ps1" $ProfileExtensions -Force
Write-Host "$ScriptSetupPath\ProfileExtensions.ps1 was copied to $ProfileExtensions"
Write-Host "Using local version of Profile Extensions from $BeginPath ..."
}
# # If still have nothing, try to download from Github
# else {
# try { (New-Object System.Net.WebClient).DownloadString("$UrlProfileExtensions") | Out-File $UrlProfileExtensions }
# catch { Write-Host "Failed to download! Check internet/TLS settings before retrying." -ForegroundColor Red }
# }
# If the script was run from a url, then download latest copies from there.
# For working offline, use the latest profile extensions locally if available by copying to TEMP and using same logic.
# if ($null -eq $BeginPath ) {
# if (Test-Path "$($env:TEMP)\ProfileExtensions.ps1") {
# Copy-Item "$($env:TEMP)\ProfileExtensions.ps1" $ProfileExtensions -Force
# echo "`nUsing local profile extensions from $($env:TEMP)\ProfileExtensions.ps1..."
# } else {
# # If $ProfileExtensions was not there, just get from online
# try { (New-Object System.Net.WebClient).DownloadString("$UrlProfileExtensions") | Out-File $ProfileExtensions ; echo "Downloaded profile extensions from internet ..."}
# catch { Write-Host "Failed to download! Check internet/TLS settings before retrying." -ForegroundColor Red }
# }
# }
if (Test-Path ($ProfileExtensions)) {
Write-Host "`nDotsourcing new profile extensions into this current session ..."
. $ProfileExtensions
}
else {
Write-Host "`nProfile extensions could not be loaded locally (missing) or downloaded from internet."
Write-Host "If downloaded from internet, check internet/TLS settings before retrying."
pause
}
# If running on a server OS, or "Administrator" or hostname is "SJ*CAL", do not autoload extensions.
# In that case, put a function into $profile to enable: . Enable-Extensions
if ($OSver -like "*Server*") {
# Remove the Enable-Extensions line by getting the content *minus* the line to remove "-NotMatch" and adding it back into the profile
Write-Host "`nRemoving profile extensions handler line from `$Profile to ensure that it is positioned at the end of the script ..."
Set-Content -Path $profile -Value (Get-Content -Path $profile | Select-String -Pattern '^if \(\!\(Test-Path \("\$\(\$Profile\)_extensions\.ps1\"\)\)\) \{ try { \(New' -NotMatch)
Set-Content -Path $profile -Value (Get-Content -Path $profile | Select-String -Pattern '^function Enable-Extensions { if' -NotMatch)
# Set-Content -Path $profile -Value (Get-Content -Path $profile | Select-String -Pattern '^if \($MyInvocation.InvocationName -eq "Enable-Extensions"\)' -NotMatch)
# Append the Enable-Extensions line to end of $profile
Write-Host "`nAdding updated profile extensions handler line to `$Profile ...`n"
# Found that the System.Net.WebClient was the source of big slowdown at profile loading, so replaced this just by a message on how to install if required.
# $ProfileExtensionsHandler = "if (!(Test-Path (""`$(`$Profile)_extensions.ps1""))) { try { (New-Object System.Net.WebClient).DownloadString('$UrlProfileExtensions') | Out-File ""`$(`$Profile)_extensions.ps1"" } "
# $ProfileExtensionsHandler += 'catch { "Could not download profile extensions, check internet/TLS before opening a new console." } } ; '
# $ProfileExtensionsHandler += '. "$($Profile)_extensions.ps1" -EA silent'
# Add-Content -Path $profile -Value $ProfileExtensionsHandler -PassThru
# $ProfileExtensionsHandler = 'if ($MyInvocation.InvocationName -eq "Enable-Extensions") { "`nWarning: Must dotsource Enable-Extensions or it will not be added!`n`n. Enable-Extensions`n" } else { . "$($Profile)_extensions.ps1" -EA silent } } }'
$ProfileExtensionsHandler = "function Enable-Extensions { if (Test-Path (""`$(`$Profile)_extensions.ps1"")) { echo `"``nProfile extensions not found, install as follows (check internet/TLS if this fails):`n`n# (New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/roysubs/Custom-Tools/main/ProfileExtensions.ps1') | Out-File `"`$(`$Profile)_extensions.ps1`"`n`" } "
Add-Content -Path $profile -Value $ProfileExtensionsHandler -PassThru
$ProfileExtensionsHandler = "if (Test-Path (`"`$(`$Profile)_extensions.ps1`")) { . `"`$(`$Profile)_extensions.ps1`" }"
Add-Content -Path $profile -Value $ProfileExtensionsHandler -PassThru
Write-Host ""
Write-Host "The profile extensions handler has *not* been added to `$Profile as the Operating System" -ForegroundColor Yellow -BackgroundColor Black
Write-Host "is of type 'Server'. However, the 'Enable-Extensions' function has been added to `$Profile" -ForegroundColor Yellow -BackgroundColor Black
Write-Host "and can be loaded at any time using by dotsourcing into the current session:" -ForegroundColor Yellow -BackgroundColor Black
Write-Host " . Enable-Extensions" -ForegroundColor Cyan -BackgroundColor Black
Write-Host ""
} else {
# Want to put these lines at the end of $profile, so strip the matching lines and rewrite to the same file
# Set-Content -Path "C:\myfile.txt" -Value (Get-Content -Path "C:\myfile.txt" | Select-String -Pattern 'H\|159' -NotMatch)
Write-Host "`nRemoving profile extensions handler line from `$Profile to ensure that it is positioned at the end of the script ..."
# Set-Content -Path $profile -Value (Get-Content -Path $profile | Select-String -Pattern '^\[Net\.ServicePointManager\]::SecurityProtocol' -NotMatch)
# Remove the Tls setting as not compatible with PowerShell v2
# Set-Content -Path $profile -Value (Get-Content -Path $profile | Select-String -Pattern '^\$UrlProfileExtensions \= ' -NotMatch) -EA SilentlyContinue
Set-Content -Path $profile -Value (Get-Content -Path $profile | Select-String -Pattern '^if \(\!\(Test-Path \("\$\(\$Profile\)_extensions\.ps1\"\)\)\) \{ ' -NotMatch)
Set-Content -Path $profile -Value (Get-Content -Path $profile | Select-String -Pattern '^if \(Test-Path \("\$\(\$Profile\)_extensions\.ps1\"\)\) \{ ' -NotMatch)
# Then append the lines in the correct order to the end of $profile. Note "-PassTru" switch to display the result as well as writing to file
Write-Host "`nAdding updated profile extensions handler line to `$Profile ...`n"
# $ProfileExtensionsHandler = "`$UrlProfileExtensions = ""$UrlProfileExtensions"" ; "
# $ProfileExtensionsHandler = "if (!(Test-Path (""`$(`$Profile)_extensions.ps1""))) { echo `"Profile extensions not found, install as follows (check internet/TLS if this fails):``n(New-Object System.Net.WebClient).DownloadString('$UrlProfileExtensions') | Out-File `"`$(`$Profile)_extensions.ps1`"`" }"
# Found that the System.Net.WebClient was the source of big slowdown at profile loading, so replaced this just by a message on how to install if required.
# Keep this here as template though as was hard to work out how to embed things properly.
# $ProfileExtensionsHandler = "if (!(Test-Path (""`$(`$Profile)_extensions.ps1""))) { try { (New-Object System.Net.WebClient).DownloadString('$UrlProfileExtensions') | Out-File ""`$(`$Profile)_extensions.ps1"" } "
# $ProfileExtensionsHandler += 'catch { "Could not download profile extensions, check internet/TLS before opening a new console." } } ; '
# $ProfileExtensionsHandler += '. "$($Profile)_extensions.ps1" -EA silent'
$ProfileExtensionsHandler = "" # Reset in case this script has already been dot-sourced
$ProfileExtensionsHandler = "if (!(Test-Path (`"`$(`$Profile)_extensions.ps1`"))) { echo `"``nProfile extensions not found, install as follows (check internet/TLS if this fails):``n``n# (New-Object System.Net.WebClient).DownloadString('$UrlProfileExtensions') | Out-File ``""```$(```$Profile)_extensions.ps1``""``n`" }"
Add-Content -Path $profile -Value $ProfileExtensionsHandler -PassThru
$ProfileExtensionsHandler = 'if (Test-Path ("$($Profile)_extensions.ps1")) { . "$($Profile)_extensions.ps1" }' # No need for `" `$ etc as using ''
Add-Content -Path $profile -Value $ProfileExtensionsHandler -PassThru
Write-Host ""
Write-Host "The profile extensions handler has been added to `$Profile and so will be" -ForegroundColor Yellow -BackgroundColor Black
Write-Host "loaded by default in all console sessions." -ForegroundColor Yellow -BackgroundColor Black
Write-Host ""
# Considerations for building strings: https://powershellexplained.com/2017-11-20-Powershell-StringBuilder/
# Alternatively, doing three Add-Content lines with -NoNewLine would also work fine.
# Added ErrorAction (EA) SilentlyContinue to suppress errors if cannot reach the URL
# Removed the Tls setting as this is not compatible with PowerShell v2
# Add-Content -Path $profile -Value "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12" -PassThru
}
Write-Host ""
if ($unattended -eq $false) { if ($(Confirm-Choice $confirm) -eq "no") { ThrowScriptErrorAndStop } }
# Update-Help to download latest help files
# Note that Update-Help does not exist in vanilla Windows 7 / PowerShell v2 (!)
# Check Last Modified Date of "$($env:TEMP)\Update-Help-Check.flag"
# Create / Touch file in $env:TEMP
# https://superuser.com/questions/251263/the-last-access-date-is-not-changed-even-after-reading-the-file-on-windows-7
####################
#
# DEPRECATE ALL OF THIS !!!
# Takes too long to complete, and anyway, 'm' in the Custom-Tools performs the same task to update all help files.
#
####################
# $updatefile = "$($env:TEMP)\ps_Update-Help-$($PSVersionTable.PSVersion.ToString()).flag"
# if (Test-Path $updatefile) { [datetime]$updatetime = (Get-Item $updatefile).LastWriteTime }
# [int]$helpolderthan = 20
# [datetime]$dateinpast = (Get-Date).AddDays(-$helpolderthan)
#
# if (Test-Administrator -eq $true) {
# Write-Host ""
# Write-Host ""
# Write-Host "n========================================" -ForegroundColor Green
# Write-Host "Update Help Files if more than $helpolderthan days old." -F Yellow -B Black
# Write-Host "Checking PowerShell Help definitions ..." -F Yellow -B Black
# Write-Host ""
# Write-Host "Note: This section will only show if you are running as Administrator."
# Write-Host "========================================" -ForegroundColor Green
# Write-Host ""
#
# if ($PSVersionTable.PSVersion.Major -eq 2) {
# Write-Host "Update-Help Cmdlet does not exist on PowerShell v2." -ForegroundColor Red
# Write-Host "Skipping help definitions update ..." -ForegroundColor Red
# } else {
#
# if (Test-Path $updatefile) {
# "Current Date minus $helpolderthan : $((Get-Date).AddDays(-$helpolderthan))" # Note that this has a "-" so this is back 20 days from the current day
# "Date on help update flag file : $updatetime"
#
# if ($updatetime -lt $dateinpast) {
# Write-Host "Help files only update if more than $($helpolderthan.ToString()) days old. A flag file is" -ForegroundColor Green
# Write-Host "kept in the user Temp folder timestamped at last update time to check this ..." -ForegroundColor Green
# Write-Host "The flag file is more than days old, so Help file definitions will update ..." -ForegroundColor Green
# (Get-ChildItem $updatefile).LastWriteTime = Get-Date # touch the flat file to today's date
# Update-Help -ErrorAction SilentlyContinue
# # Run this with -EA silent due to the various errors that always happen as a result of bad manifests, which will be similar to:
# # Note that this will not suppress all error messages! so need to add | Out-Null also
# # update-help : Failed to update Help for the module(s) 'AppvClient, ConfigDefender, Defender, HgsClient, HgsDiagnostics, HostNetworkingService,
# # Microsoft.PowerShell.ODataUtils, Microsoft.PowerShell.Operation.Validation, UEV, Whea, WindowsDeveloperLicense' with UI culture(s) {en-GB} : Unable to
# # connect to Help content. The server on which Help content is stored might not be available. Verify that the server is available, or wait until the server is
# # back online, and then try the command again.
# } else {
# Write-Wrap "The flag file used to determine if help definitions should be updated is less than $($helpolderthan.ToString()) days old so help file will not be updated ..."
# }
# } else {
# Write-Host "No help definitions checkfile found, creating a new checkfile and Update-Help will run ..."
# New-Item -ItemType File $updatefile
# Update-Help -ErrorAction SilentlyContinue
# }
# }
# Write-Host ""
# if ($unattended -eq $false) { if ($(Confirm-Choice $confirm) -eq "no") { ThrowScriptErrorAndStop } }
# }
# else {
# "Must be Administrator to update help files"
# }
####################
#
# Update PowerShell, Chocolatey, Boxstarter
#
####################
function Test-Administrator {
$user = [Security.Principal.WindowsIdentity]::GetCurrent();
(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
if (Test-Administrator -eq $true) {
Write-Host ''
Write-Host ''
Write-Host "`n========================================" -ForegroundColor Green
Write-Host ''
Write-Host '2. Update PowerShell, Chocolatey, Boxstarter' -ForegroundColor Green
Write-Host ''
Write-Host ' Only run the web-installers if not already found to be installed.' -ForegroundColor Yellow
Write-Host ' Check various system configurations and update if required.' -ForegroundColor Yellow
Write-Host ''
Write-Host ' Also, if on Windows 7, test for Service Pack 1, .NET 4.5 etc.' -ForegroundColor Yellow
Write-Host ''
Write-Host "========================================`n" -ForegroundColor Green
Write-Host ''
if ($unattended -eq $false) { if ($(Confirm-Choice $confirm) -eq "no") { ThrowScriptErrorAndStop } }
Write-Host ""
Write-Host "Test for profile, create if it does not currently exist" -ForegroundColor Yellow -BackgroundColor Black
Write-Host "$profile"
if (!(Test-Path (Split-Path $profile))) { New-Item -ItemType Directory (Split-Path $profile) } # Create profile folder if not already there
if (!(Test-Path $profile)) { New-Item -ItemType File $profile } # Create $profile if not already there
Write-Host ""
try {
Set-ExecutionPolicy Bypass -Scope Process -Force
} catch {
Write-Host "`nWARNING: 'Set-ExecutionPolicy Bypass -Scope Process -Force' failed to execute." -ForegroundColor Yellow
Write-Host "This is often due to Group Policy restrictions on corporate builds.`n" -ForegroundColor Yellow
Write-Host "'Get-ExecutionPolicy -List' (show current execution policy list):`n"
Get-ExecutionPolicy -List | ft
}
# The actual error in this case was:
# Set-ExecutionPolicy : Windows PowerShell updated your execution policy successfully, but the setting is overridden
# by a policy defined at a more specific scope. Due to the override, your shell will retain its current effective
# execution policy of Unrestricted. Type "Get-ExecutionPolicy -List" to view your execution policy settings. For more
# information please see "Get-Help Set-ExecutionPolicy".
# Note that the 'Unrestricted' setting is more open than the setting attempted in the above.
# However, iff you have 'Administrator' access (which is required to run this script), then it should run ok.
# To take and run the string was a little awkward. Tried:
# dotsourcing, &, Start-Job, but in the end Invoke-Expression (iex) was required!
# https://stackoverflow.com/questions/12850487/invoke-a-second-script-with-arguments-from-a-script
function IfExistSkipCommand ($toCheck, $toRun) {
if (Test-Path($toCheck)) {
Write-Host "Item exists : $toCheck" -ForegroundColor Green
Write-Host "Will skip installer: $toRun`n" -ForegroundColor Cyan
} else {
Write-Host "Item does not exist: $toCheck" -ForegroundColor Green
Write-Host "Will run installer : $toRun`n" -ForegroundColor Cyan
Invoke-Expression $toRun
}
}
# Chocolatey, assume ok if choco.exe is there
Write-Host "Test for Chocolatey, download and install if it does not currently exist" -ForegroundColor Yellow -BackgroundColor Black
IfExistSkipCommand "C:\ProgramData\chocolatey\choco.exe" "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))"
# Manually apply the chocolateyProfile
if (Test-Path("C:\ProgramData\chocolatey\helpers\chocolateyProfile.psm1")) { Import-Module "C:\ProgramData\chocolatey\helpers\chocolateyProfile.psm1" }
$env:Path += ";C:\ProgramData\chocolatey" # Add to Path just for this script session
# To update System Path: [Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\bin", "Machine")
# To update User Path: [Environment]::SetEnvironmentVariable("Path", [Environment]::GetEnvironmentVariable("Path", "User") + ";C:\bin", "User")
# Boxstarter, assume ok if BoxstarterShell.ps1 is there
Write-Host ""