-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathAzFilesHybrid.psm1
More file actions
5761 lines (4745 loc) · 220 KB
/
Copy pathAzFilesHybrid.psm1
File metadata and controls
5761 lines (4745 loc) · 220 KB
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
using namespace System
using namespace System.Collections
using namespace System.Collections.Generic
using namespace System.Collections.Specialized
using namespace System.Text
using namespace System.Security
param(
[Parameter(Mandatory=$false, Position=0)]
[hashtable]$OverrideModuleConfig = @{}
)
# This module contains many cmdlets which may be used in different scenarios. Since the purpose
# of this module is to provide cmdlets that cross the cloud/on-premises boundary, you may want
# to take a look at what that cmdlets are doing prior to running them. For the ease of your
# inspection, we have grouped them into several regions:
# - General cmdlets, used across multiple scenarios. These check or assert information about
# your environment, or wrap OS functionality (like *-OSFeature) to provide a common way of
# dealing with things across OS environments.
# - Azure Files Active Directory cmdlets, which make it possible to domain join your storage
# accounts to replace a file server.
# - General Azure cmdlets, which provide functionality that make working with Azure resources
# easier.
# - DNS cmdlets, which wrap Azure and on-premises DNS functions to make it possible to configure
# DNS to access Azure resources on-premises and vice versa.
# - DFS-N cmdlets, which wrap Azure and Windows Server DFS-N to make it a more seamless process
# to adopt Azure Files to replace on-premises file servers.
#region General cmdlets
function Get-IsElevatedSession {
<#
.SYNOPSIS
Get the elevation status of the PowerShell session.
.DESCRIPTION
This cmdlet will check to see if the PowerShell session is running as administrator, generally allowing PowerShell code
to check to see if it's got enough permissions to do the things it needs to do. This cmdlet is not yet defined on Linux/macOS
sessions.
.EXAMPLE
if ((Get-IsElevatedSession)) {
# Some code requiring elevation
} else {
# Some alternative code, or a nice error message.
}
.OUTPUTS
System.Boolean, indicating whether the session is elevated.
#>
[CmdletBinding()]
param()
switch((Get-OSPlatform)) {
"Windows" {
$currentPrincipal = [Security.Principal.WindowsPrincipal]::new(
[Security.Principal.WindowsIdentity]::GetCurrent())
$isAdmin = $currentPrincipal.IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)
return $isAdmin
}
"Linux" {
throw [System.PlatformNotSupportedException]::new()
}
"OSX" {
throw [System.PlatformNotSupportedException]::new()
}
default {
throw [System.PlatformNotSupportedException]::new()
}
}
}
function Assert-IsElevatedSession {
<#
.SYNOPSIS
Check if the session is elevated and throw an error if it isn't.
.DESCRIPTION
This cmdlet uses the Get-IsElevatedSession cmdlet to throw a nice error message to the user if the session isn't elevated.
.EXAMPLE
Assert-IsElevatedSession
# User sees either nothing (session is elevated), or an error message (session is not elevated).
#>
[CmdletBinding()]
param()
if (!(Get-IsElevatedSession)) {
Write-Error `
-Message "This cmdlet requires an elevated PowerShell session." `
-ErrorAction Stop
}
}
function Get-OSPlatform {
<#
.SYNOPSIS
Get the OS running the current PowerShell session.
.DESCRIPTION
This cmdlet is a wrapper around the System.Runtime.InteropServices.RuntimeInformation .NET standard class that makes it easier to work with in PowerShell 5.1/6/7/etc. $IsWindows, etc. is defined in PS6+, however since it's not defined in PowerShell 5.1, it's not incredibly useful for writing PowerShell code meant to be executed in either language version. As older versions of .NET Framework do not support the RuntimeInformation .NET standard class, if the PSEdition is "Desktop", by default you're running on Windows, since only "Core" releases are cross-platform.
.EXAMPLE
if ((Get-OSPlatform) -eq "Windows") {
# Do some Windows specific stuff
}
.OUTPUTS
System.String, indicating the OS Platform name as defined by System.Runtime.InteropServices.RuntimeInformation.
#>
[CmdletBinding()]
param()
if ($PSVersionTable.PSEdition -eq "Desktop") {
return "Windows"
} else {
$windows = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform(
[System.Runtime.InteropServices.OSPlatform]::Windows)
if ($windows) {
return "Windows"
}
$linux = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform(
[System.Runtime.InteropServices.OSPlatform]::Linux)
if ($linux) {
return "Linux"
}
$osx = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform(
[System.Runtime.InteropServices.OSPlatform]::OSX)
if ($osx) {
return "OSX"
}
return "Unknown"
}
}
function Assert-IsWindows {
<#
.SYNOPSIS
Check if the session is being run on Windows and throw an error if it isn't.
.DESCRIPTION
This cmdlet uses the Get-OSPlatform cmdlet to throw a nice error message to the user if the session isn't Windows.
.EXAMPLE
Assert-IsWindows
# User either sees nothing or an error message.
#>
[CmdletBinding()]
param()
if ((Get-OSPlatform) -ne "Windows") {
throw [PlatformNotSupportedException]::new()
}
}
function Get-IsDomainJoined {
<#
.SYNOPSIS
Checks that script is being run in on computer that is domain-joined.
.DESCRIPTION
This cmdlet returns true if the cmdlet is running in a domain-joined session or false if it's not.
.EXAMPLE
if ((Get-IsDomainJoined)) {
# Do something if computer is domain joined.
} else {
# Do something else if the computer is not domain joined.
}
.OUTPUTS
System.Boolean, indicating whether or not the computer is domain joined.
#>
[CmdletBinding()]
param()
switch((Get-OSPlatform)) {
"Windows" {
$computer = Get-CimInstance -ClassName "win32_computersystem"
if ($computer.PartOfDomain) {
Write-Verbose -Message "Session is running in a domain-joined environment."
} else {
Write-Verbose -Message "Session is not running in a domain-joined environment."
}
return $computer.PartOfDomain
}
default {
throw [PlatformNotSupportedException]::new()
}
}
}
function Assert-IsDomainJoined {
<#
.SYNOPSIS
Check if the session is being run on a domain joined machine and throw an error if it isn't.
.DESCRIPTION
This cmdlet uses the Get-IsDomainJoined cmdlet to throw a nice error message to the user if the session isn't domain joined.
.EXAMPLE
Assert-IsDomainJoined
#>
[CmdletBinding()]
param()
if (!(Get-IsDomainJoined)) {
Write-Error `
-Message "The cmdlet, script, or module must be run in a domain-joined environment." `
-ErrorAction Stop
}
}
function Get-OSVersion {
<#
.SYNOPSIS
Get the version number of the OS.
.DESCRIPTION
This cmdlet provides the OS's internal version number, for example 10.0.18363.0 for Windows 10, version 1909 (the public release). This cmdlet is not yet defined on Linux/macOS sessions.
.EXAMPLE
if ((Get-OSVersion) -ge [System.Version]::new(10,0,0,0)) {
# Do some Windows 10 specific stuff
}
.OUTPUTS
System.Version, indicating the OS's internal version number.
#>
[CmdletBinding()]
param()
switch((Get-OSPlatform)) {
"Windows" {
return [System.Environment]::OSVersion.Version
}
"Linux" {
throw [System.PlatformNotSupportedException]::new()
}
"OSX" {
throw [System.PlatformNotSupportedException]::new()
}
default {
throw [System.PlatformNotSupportedException]::new()
}
}
}
function Get-WindowsInstallationType {
<#
.SYNOPSIS
Get the Windows installation type (ex. Client, Server, ServerCore, etc.).
.DESCRIPTION
This cmdlet provides the installation type of the Windows OS, primarily to allow for cmdlet behavior changes depending on whether the cmdlet is being run on a Windows client ("Client") or a Windows Server ("Server", "ServerCore"). This cmdlet is (obviously) only available for Windows PowerShell sessions and will return a PlatformNotSupportedException for non-Windows sessions.
.EXAMPLE
switch ((Get-WindowsInstallationType)) {
"Client" {
# Do some stuff for Windows client.
}
{ ($_ -eq "Server") -or ($_ -eq "Server Core") } {
# Do some stuff for Windows Server.
}
}
.OUTPUTS
System.String, indicating the Windows installation type.
#>
[CmdletBinding()]
param()
Assert-IsWindows
$installType = Get-ItemProperty `
-Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\" `
-Name InstallationType | `
Select-Object -ExpandProperty InstallationType
return $installType
}
function Assert-IsWindowsServer {
[CmdletBinding()]
param()
Assert-IsWindows
$installationType = Get-WindowsInstallationType
if ($installationType -ne "Server" -and $installationType -ne "Server Core") {
Write-Error `
-Message "The cmdlet, script, or module must be run on a Windows Server installation." `
-ErrorAction Stop
}
}
# This PowerShell enumeration provides the various types of OS features. Currently, only Windows features
# are supported.
enum OSFeatureKind {
WindowsServerFeature
WindowsClientCapability
WindowsClientOptionalFeature
}
# This PowerShell class provides a wrapper around the OS's internal feature mechanism. Currently, this class
# is only being used for Windows features, adding support for non-Windows features may require additional
# properties/methods. Ultimately, this is useful since even within Windows, there are (at least) 3 different
# ways of representing features, and this is extremely painful to work with in scripts/modules.
class OSFeature {
# A human friendly name of the feature. Some of the Windows features do not have human friendly names.
[string]$Name
# The internal OS name for the feature. This is what the operating system calls the feature if you use
# the native cmdlets/commands to access it.
[string]$InternalOSName
# The version of the feature. Depending on the OS feature kind, this may or may not be an issue.
[string]$Version
# Whether or not the feature is installed.
[bool]$Installed
# The kind of feature being represented.
[OSFeatureKind]$FeatureKind
# A default constructor to make this object.
OSFeature(
[string]$name,
[string]$internalOSName,
[string]$version,
[bool]$installed,
[OSFeatureKind]$featureKind
) {
$this.Name = $name
$this.InternalOSName = $internalOSName
$this.Version = $version
$this.Installed = $installed
$this.FeatureKind = $featureKind
}
}
function Get-OSFeature {
<#
.SYNOPSIS
Get the list of available/installed features for your OS.
.DESCRIPTION
Get the list of available/installed features for your OS. Currently this cmdlet only works for Windows OSes, but works for both Windows client and Windows Server, which among them provide three different ways of enabling/disabling features (if there are more than three, this cmdlet doesn't suppor them yet).
.EXAMPLE
# Check to see if the Windows 10 client RSAT AD PowerShell module is installed.
if ((Get-OSPlatform) -eq "Windows" -and (Get-WindowsInstallationType) -eq "Client") {
$rsatADFeature = Get-OSFeature | `
Where-Object { $_.Name -eq "Rsat.ActiveDirectory.DS-LDS.Tools" }
if ($null -eq $rsatADFeature) {
# Feature is not installed.
} else {
# Feature is installed
}
}
.OUTPUTS
OSFeature (defined in this PowerShell module), representing a feature available/installed in your OS.
#>
[CmdletBinding()]
param()
switch((Get-OSPlatform)) {
"Windows" {
$winVer = Get-OSVersion
switch((Get-WindowsInstallationType)) {
"Client" {
# Windows client only allows the underlying cmdlets to run if the session
# is elevated, therefore this check is added.
Assert-IsElevatedSession
# WindowsCapabilities are only available on Windows 10.
if ($winVer -ge [Version]::new(10,0,0,0)) {
# Get-WindowsCapability appends additional fields to the actual name of the feature, ex.
# Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0. This code strips that out to hopefully get
# to something easier to use. This behavior may be changed in the future. Features exposed
# through Get-WindowsCapability appear to be dynamic, exposed through the internet, although
# it's unclear how frequently they're updated, or if the version number is guaranteed to change
# if they are.
$features = Get-WindowsCapability -Online | `
Select-Object `
@{ Name= "InternalName"; Expression = { $_.Name } },
@{ Name = "Name"; Expression = { $_.Name.Split("~")[0] } },
@{ Name = "Field1"; Expression = { $_.Name.Split("~")[1] } },
@{ Name = "Field2"; Expression = { $_.Name.Split("~")[2] } },
@{ Name = "Language"; Expression = { $_.Name.Split("~")[3] } },
@{ Name = "Version"; Expression = { $_.Name.Split("~")[4] } },
@{ Name = "Installed"; Expression = { $_.State -eq "Installed" } } | `
ForEach-Object {
if (![string]::IsNullOrEmpty($_.Language)) {
$Name = ($_.Name + "-" + $_.Language)
} else {
$Name = $_.Name
}
[OSFeature]::new(
$Name,
$_.InternalName,
$_.Version,
$_.Installed,
[OSFeatureKind]::WindowsClientCapability)
}
}
# Features exposed via Get-WindowsOptionalFeature aren't versioned independently of the OS.
# Updates may occur to these features, but happen inside of the normal OS process.
$features += Get-WindowsOptionalFeature -Online |
Select-Object `
@{ Name = "InternalName"; Expression = { $_.FeatureName } },
@{ Name = "Name"; Expression = { $_.FeatureName } },
@{ Name = "Installed"; Expression = { $_.State -eq "Enabled" } } | `
ForEach-Object {
[OSFeature]::new(
$_.Name,
$_.InternalName,
$winVer,
$_.Installed,
[OSFeatureKind]::WindowsClientOptionalFeature)
}
}
{ ($_ -eq "Server") -or ($_ -eq "Server Core") } {
# Server is comparatively simpler than Windows client: Get-WindowsFeature doesn't require
# an elevated session and features that aren't split between these two different mechanisms.
# Most or all of the features should be available in most places, and of course Windows Server has
# unique features (Server Roles).
$features = Get-WindowsFeature | `
Select-Object Name, Installed | `
ForEach-Object {
[OSFeature]::new(
$_.Name,
$_.Name,
$winVer,
$_.Installed,
[OSFeatureKind]::WindowsServerFeature)
}
}
}
}
"Linux" {
throw [System.NotImplementedException]::new()
}
"OSX" {
throw [System.NotImplementedException]::new()
}
default {
throw [System.NotImplementedException]::new()
}
}
return $features
}
function Install-OSFeature {
<#
.SYNOPSIS
Install a requested operating system feature.
.DESCRIPTION
This cmdlet will use the underlying OS-specific feature installation methods to install the requested feature(s). This is currently Windows only.
.PARAMETER OSFeature
The feature(s) to be installed.
.EXAMPLE
# Install the RSAT AD PowerShell module.
if ((Get-OSPlatform) -eq "Windows" -and (Get-WindowsInstallationType) -eq "Client") {
$rsatADFeature = Get-OSFeature | `
Where-Object { $_.Name -eq "Rsat.ActiveDirectory.DS-LDS.Tools" } | `
Install-OSFeature
}
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, ParameterSetName="OSFeature", ValueFromPipeline=$true)]
[OSFeature[]]$OSFeature
)
process {
switch ((Get-OSPlatform)) {
"Windows" {
Assert-IsElevatedSession
$winVer = Get-OSVersion
switch((Get-WindowsInstallationType)) {
"Client" {
if ($winVer -ge [version]::new(10,0,0,0)) {
$OSFeature | `
Where-Object { !$_.Installed } | `
Where-Object { $_.FeatureKind -eq [OSFeatureKind]::WindowsClientCapability } | `
Select-Object @{ Name = "Name"; Expression = { $_.InternalOSName } } | `
Add-WindowsCapability -Online | `
Out-Null
} else {
$foundCapabilities = $OSFeature | `
Where-Object { $_.FeatureKind -eq [OSFeatureKind]::WindowsClientCapability }
if ($null -ne $foundCapabilities) {
Write-Error `
-Message "Windows capabilities are not supported on Windows versions prior to Windows 10." `
-ErrorAction Stop
}
}
$optionalFeatureNames = $OSFeature | `
Where-Object { !$_.Installed } | `
Where-Object { $_.FeatureKind -eq [OSFeatureKind]::WindowsClientOptionalFeature } | `
Select-Object @{ Name = "FeatureName"; Expression = { $_.InternalOSName } } | `
Enable-WindowsOptionalFeature -Online | `
Out-Null
}
{ ($_ -eq "Server") -or ($_ -eq "Server Core") } {
$OSFeature | `
Where-Object { !$_.Installed } | `
Where-Object { $_.FeatureKind -eq [OSFeatureKind]::WindowsServerFeature } | `
Select-Object -ExpandProperty InternalOSName | `
Install-WindowsFeature | `
Out-Null
}
default {
Write-Error -Message "Unknown Windows installation type $_" -ErrorAction Stop
}
}
}
"Linux" {
throw [System.PlatformNotSupportedException]::new()
}
"OSX" {
throw [System.PlatformNotSupportedException]::new()
}
default {
throw [System.PlatformNotSupportedException]::new()
}
}
}
}
function Request-OSFeature {
<#
.SYNOPSIS
Request the features to be installed that are required for a cmdlet/script.
.DESCRIPTION
This cmdlet is a wrapper around the Install-OSFeature cmdlet, primarily to be used in cmdlets/scripts to ensure the required OS feature prerequisites are installed before the rest of the cmdlet executes. The required features, independent of the actual OS running, can be described, and this cmdlet figures out the rest.
.PARAMETER WindowsClientCapability
The names of features which are Windows client capabilities.
.PARAMETER WindowsClientOptionalFeature
The names of features which are Windows client optional features.
.PARAMETER WindowsServerFeature
The names of features which are Windows Server features.
.EXAMPLE
Request-OSFeature `
-WindowsClientCapability "Rsat.ActiveDirectory.DS-LDS.Tools" `
-WindowsServerFeature "RSAT-AD-PowerShell"
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)]
[string[]]$WindowsClientCapability,
[Parameter(Mandatory=$false)]
[string[]]$WindowsClientOptionalFeature,
[Parameter(Mandatory=$false)]
[string[]]$WindowsServerFeature
)
$features = Get-OSFeature
$foundFeatures = @()
$notFoundFeatures = @()
switch((Get-OSPlatform)) {
"Windows" {
switch((Get-WindowsInstallationType)) {
"Client" {
$foundFeatures += $features | `
Where-Object { $_.Name -in $WindowsClientCapability -or $_.Name -in $WindowsClientOptionalFeature }
if ($PSBoundParameters.ContainsKey("WindowsClientCapability")) {
$notFoundFeatures += $WindowsClientCapability | `
Where-Object { $_ -notin ($foundFeatures | Select-Object -ExpandProperty Name) }
}
if ($PSBoundParameters.ContainsKey("WindowsClientOptionalFeature")) {
$notFoundFeatures += $WindowsClientOptionalFeature | `
Where-Object { $_ -notin ($foundFeatures | Select-Object -ExpandProperty Name) }
}
}
{ ($_ -eq "Server") -or ($_ -eq "Server Core") } {
$foundFeatures += $features | `
Where-Object { $_.Name -in $WindowsServerFeature }
$notFoundFeatures += $WindowsServerFeature | `
Where-Object { $_ -notin ($foundFeatures | Select-Object -ExpandProperty Name) }
}
}
}
"Linux" {
throw [System.NotImplementedException]::new()
}
"OSX" {
throw [System.NotImplementedException]::new()
}
default {
throw [System.NotImplementedException]::new()
}
}
Install-OSFeature -OSFeature $foundFeatures
if ($null -ne $notFoundFeatures -and $notFoundFeatures.Length -gt 0) {
$notFoundBuilder = [StringBuilder]::new()
$notFoundBuilder.Append("The following features could not be found: ") | Out-Null
for($i=0; $i -lt $notFoundFeatures.Length; $i++) {
if ($i -gt 0) {
$notFoundBuilder.Append(", ") | Out-Null
}
$notFoundBuilder.Append($notFoundFeatures[$i]) | Out-Null
}
Write-Error -Message $notFoundBuilder.ToString() -ErrorAction Stop
}
}
function Assert-OSFeature {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)]
[string[]]$WindowsClientCapability,
[Parameter(Mandatory=$false)]
[string[]]$WindowsClientOptionalFeature,
[Parameter(Mandatory=$false)]
[string[]]$WindowsServerFeature
)
$features = Get-OSFeature
$foundFeatures = @()
$notFoundFeatures = @()
switch((Get-OSPlatform)) {
"Windows" {
switch ((Get-WindowsInstallationType)) {
"Client" {
$foundFeatures += $features | `
Where-Object { $_.Name -in $WindowsClientCapability -or $_.Name -in $WindowsClientOptionalFeature }
if ($PSBoundParameters.ContainsKey("WindowsClientCapability")) {
$notFoundFeatures += $WindowsClientCapability | `
Where-Object { $_ -notin ($foundFeatures | Select-Object -ExpandProperty Name) }
}
if ($PSBoundParameters.ContainsKey("WindowsClientOptionalFeature")) {
$notFoundFeatures += $WindowsClientOptionalFeature | `
Where-Object { $_ -notin ($foundFeatures | Select-Object -ExpandProperty Name) }
}
}
{ ($_ -eq "Server") -or ($_ -eq "Server Core") } {
$foundFeatures += $features | `
Where-Object { $_.Name -in $WindowsServerFeature }
$notFoundFeatures += $WindowsServerFeature | `
Where-Object { $_ -notin ($foundFeatures | Select-Object -ExpandProperty Name) }
}
default {
throw [PlatformNotSupportedException]::new("Windows installation type $_ is not currently supported.")
}
}
}
"Linux" {
throw [PlatformNotSupportedException]::new()
}
"OSX" {
throw [PlatformNotSupportedException]::new()
}
default {
throw [PlatformNotSupportedException]::new()
}
}
if ($null -ne $notFoundFeatures -and $notFoundFeatures.Length -gt 0) {
$errorBuilder = [StringBuilder]::new()
$errorBuilder.Append("The following features could not be found: ") | Out-Null
$i=0
$notFoundFeatures | ForEach-Object {
if ($i -gt 0) {
$errorBuilder.Append(", ") | Out-Null
}
$errorBuilder.Append($_) | Out-Null
}
$errorBuilder.Append(".") | Out-Null
Write-Error -Message $errorBuilder.ToString() -ErrorAction Stop
}
}
function Request-ADFeature {
<#
.SYNOPSIS
Ensure the ActiveDirectory PowerShell module is installed prior to running the rest of the caller cmdlet.
.DESCRIPTION
This cmdlet is helper around Request-OSFeature specifically meant for the RSAT AD PowerShell module. It uses the optimization of checking if the ActiveDirectory module is available before using the Request-OSFeature cmdlet, since this is quite a bit faster (and does not require session elevation on Windows client) before using the Request-OSFeature cmdlet. This cmdlet is not exported.
.EXAMPLE
Request-ADFeature
#>
[CmdletBinding()]
param()
Assert-IsWindows
$adModule = Get-Module -Name ActiveDirectory -ListAvailable
if ($null -eq $adModule) {
# OSVersion 10.0.18362 is Windows 10, version 1903. All releases below, such as 17763.x, where x is some
# OS build revision number, require manual installation of the RSAT package as indicated in the error message.
if ((Get-WindowsInstallationType) -eq "Client" -and (Get-OSVersion) -lt [Version]::new(10, 0, 18362, 0)) {
Write-Error `
-Message "This PowerShell module requires the ActiveDirectory RSAT module. On versions of Windows 10 prior to 1809, RSAT can be downloaded via https://www.microsoft.com/download/details.aspx?id=45520." `
-ErrorAction Stop
}
Request-OSFeature `
-WindowsClientCapability "Rsat.ActiveDirectory.DS-LDS.Tools" `
-WindowsServerFeature "RSAT-AD-PowerShell"
}
$adModule = Get-Module -Name ActiveDirectory
if ($null -eq $adModule) {
Import-Module -Name ActiveDirectory
}
}
function Request-PowerShellGetModule {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact="High")]
param()
$psGetModule = Get-Module -Name PowerShellGet -ListAvailable | `
Sort-Object -Property Version -Descending
if ($null -eq $psGetModule -or $psGetModule[0].Version -lt [Version]::new(1,6,0)) {
$caption = "Install updated version of PowerShellGet"
$verboseConfirmMessage = "This module requires PowerShellGet 1.6.0+. This can be installed now if you are running as an administrator. At the end of the installation, importing this module will fail as you must close all open instances of PowerShell for the updated version of PowerShellGet to be available."
if ($PSCmdlet.ShouldProcess($verboseConfirmMessage, $verboseConfirmMessage, $caption)) {
if (!(Get-IsElevatedSession)) {
Write-Error -Message "To install PowerShellGet, you must import this module as an administrator. This module package does not generally require administrator privileges, so successive imports of this module can be from a non-elevated session." -ErrorAction Stop
}
try {
Remove-Module -Name PowerShellGet, PackageManagement -Force -ErrorAction SilentlyContinue
Install-PackageProvider -Name NuGet -Force | Out-Null
Install-Module `
-Name PowerShellGet `
-Repository PSGallery `
-Force `
-ErrorAction Stop `
-SkipPublisherCheck
} catch {
Write-Error -Message "PowerShellGet was not successfully installed, and is a requirement of this module. See https://docs.microsoft.com/powershell/scripting/gallery/installing-psget for information on how to manually troubleshoot the PowerShellGet installation." -ErrorAction Stop
}
Write-Verbose -Message "Installed latest version of PowerShellGet module."
Write-Error -Message "PowerShellGet was successfully installed, however you must close all open PowerShell sessions to use the new version. The next import of this module will be able to use PowerShellGet." -ErrorAction Stop
}
}
Remove-Module -Name PowerShellGet -ErrorAction SilentlyContinue
Remove-Module -Name PackageManagement -ErrorAction SilentlyContinue
}
function Request-AzureADModule {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact="High")]
param()
if ($PSVersionTable.PSVersion -gt [Version]::new(6,0,0)) {
$winCompat = Get-Module -Name WindowsCompatibility -ListAvailable
}
$azureADModule = Get-Module -Name AzureAD -ListAvailable
if ($PSVersionTable.PSVersion -gt [Version]::new(6,0,0) -and $null -ne $winCompat) {
$azureADModule = Invoke-WinCommand -Verbose:$false -ScriptBlock {
Get-Module -Name AzureAD -ListAvailable
}
}
if (
($PSVersionTable.PSVersion -gt [Version]::new(6,0,0,0) -and $null -eq $winCompat) -or
$null -eq $azureADModule
) {
$caption = "Install AzureAD PowerShell module"
$verboseConfirmMessage = "This cmdlet requires the Azure AD PowerShell module. This can be automatically installed now if you are running in an elevated sessions."
if ($PSCmdlet.ShouldProcess($verboseConfirmMessage, $verboseConfirmMessage, $caption)) {
if (!(Get-IsElevatedSession)) {
Write-Error `
-Message "To install AzureAD, you must run this cmdlet as an administrator. This cmdlet may not generally require administrator privileges." `
-ErrorAction Stop
}
if ($PSVersionTable.PSVersion -gt [Version]::new(6,0,0) -and $null -eq $winCompat) {
Install-Module `
-Name WindowsCompatibility `
-Repository PSGallery `
-AllowClobber `
-Force `
-ErrorAction Stop
Import-Module -Name WindowsCompatibility
}
$scriptBlock = {
$azureADModule = Get-Module -Name AzureAD -ListAvailable
if ($null -eq $azureADModule) {
Install-Module `
-Name AzureAD `
-Repository PSGallery `
-AllowClobber `
-Force `
-ErrorAction Stop
}
}
if ($PSVersionTable.PSVersion -gt [Version]::new(6,0,0)) {
Invoke-WinCommand `
-ScriptBlock $scriptBlock `
-Verbose:$false `
-ErrorAction Stop
} else {
$scriptBlock.Invoke()
}
}
}
Remove-Module -Name PowerShellGet -ErrorAction SilentlyContinue
Remove-Module -Name PackageManagement -ErrorAction SilentlyContinue
}
function Request-AzPowerShellModule {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact="High")]
param()
# There is an known issue where versions less than PS 6.2 don't have the Az rollup module installed:
# https://github.com/Azure/azure-powershell/issues/9835
if ($PSVersionTable.PSVersion -gt [Version]::new(6,2)) {
$azModule = Get-Module -Name Az -ListAvailable
} else {
$azModule = Get-Module -Name Az.* -ListAvailable
}
$storageModule = Get-Module -Name Az.Storage -ListAvailable | `
Where-Object {
$_.Version -ge [Version]::new(2,0,0)
}
# Do should process if modules must be installed
if ($null -eq $azModule -or $null -eq $storageModule) {
$caption = "Install Azure PowerShell modules"
$verboseConfirmMessage = "This module requires Azure PowerShell (`"Az`" module) 2.8.0+ and Az.Storage 2.0.0+. This can be installed now if you are running as an administrator."
if ($PSCmdlet.ShouldProcess($verboseConfirmMessage, $verboseConfirmMessage, $caption)) {
if (!(Get-IsElevatedSession)) {
Write-Error `
-Message "To install the required Azure PowerShell modules, you must run this module as an administrator. This module does not generally require administrator privileges." `
-ErrorAction Stop
}
if ($null -eq $azModule) {
Get-Module -Name Az.* | Remove-Module
Install-Module -Name Az -Repository PSGallery -AllowClobber -Force -ErrorAction Stop
Write-Verbose -Message "Installed latest version of Az module."
}
if ($null -eq $storageModule) {
Remove-Module `
-Name Az.Storage `
-Force `
-ErrorAction SilentlyContinue
try {
Uninstall-Module `
-Name Az.Storage `
-Force `
-ErrorAction SilentlyContinue
} catch {
Write-Error `
-Message "Unable to uninstall the existing Az.Storage module which has a version lower than 2.0.0." `
-ErrorAction Stop
}
Install-Module `
-Name Az.Storage `
-Repository PSGallery `
-AllowClobber `
-Force `
-MinimumVersion "2.0.0" `
-SkipPublisherCheck `
-ErrorAction Stop
}
}
}
Remove-Module -Name PowerShellGet -ErrorAction SilentlyContinue
Remove-Module -Name PackageManagement -ErrorAction SilentlyContinue
Remove-Module -Name Az.Storage -Force -ErrorAction SilentlyContinue
Remove-Module -Name Az.Accounts -Force -ErrorAction SilentlyContinue
Remove-Module -Name Az.Network -Force -ErrorAction SilentlyContinue
$storageModule = ,(Get-Module -Name Az.Storage -ListAvailable | `
Where-Object {
$_.Version -ge [Version]::new(2,0,0)
} | `
Sort-Object -Property Version -Descending)
Import-Module -ModuleInfo $storageModule[0] -Global -ErrorAction Stop
Import-Module -Name Az.Network -Global -ErrorAction Stop
}
function Assert-DotNetFrameworkVersion {
<#
.SYNOPSIS
Require a particular .NET Framework version or throw an error if it's not available.
.DESCRIPTION
This cmdlet makes it possible to throw an error if a particular .NET Framework version is not installed on Windows. It wraps the registry using the information about .NET Framework here: https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed#query-the-registry-using-code. This cmdlet is not PowerShell 5.1 only, since it's reasonable to imagine a case where a PS6+ cmdlet/module would want to require a particular version of .NET.
.PARAMETER DotNetFrameworkVersion
The minimum version of .NET Framework to require. If a newer version is found, that will satisify the request.
.EXAMPLE
Assert-DotNetFrameworkVersion
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateSet(
"Framework4.5",
"Framework4.5.1",
"Framework4.5.2",
"Framework4.6",
"Framework4.6.1",
"Framework4.6.2",
"Framework4.7",
"Framework4.7.1",
"Framework4.7.2",
"Framework4.8")]
[string]$DotNetFrameworkVersion
)
Assert-IsWindows
$v4 = Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP" | `
Where-Object { $_.PSChildName -eq "v4" }
if ($null -eq $v4) {
Write-Error `
-Message "This module/cmdlet requires at least .NET 4.0 to be installed." `
-ErrorAction Stop
}
$full = Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4" | `
Where-Object { $_.PSChildName -eq "Full" }
if ($null -eq $full) {
Write-Error `
-Message "This module/cmdlet requires at least .NET 4.5 to be installed." `
-ErrorAction Stop
}
$release = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" | `