Private/GetNestedVirtCapabilities.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
function GetNestedVirtCapabilities {
    [CmdletBinding(DefaultParameterSetName='Default')]
    Param(
        [Parameter(
            Mandatory = $False,
            ParameterSetName = 'Default'
        )]
        [string]$TargetHostNameOrIP,

        [Parameter(
            Mandatory=$True,
            ParameterSetName = 'UsingVMName'
        )]
        [string]$TargetVMName,

        [Parameter(Mandatory=$False)]
        [string]$HypervisorFQDNOrIP,

        [Parameter(Mandatory=$False)]
        $TargetHostNameCreds,

        [Parameter(Mandatory=$False)]
        $HypervisorCreds,

        # -TryWithoutHypervisorInfo MIGHT result in creating a Local NAT
        # with an Internal vSwitch on the Target Machine (assuming it's a Guest VM). It depends if
        # Get-NestedVirtCapabilities detemines whether the Target Machine can use an External vSwitch or not.
        # If it can, then a Local NAT will NOT be created.
        # If a NAT already exists on the Target Machine, that NAT will be changed to
        # 10.0.75.0/24 with IP 10.0.75.1 if it isn't already
        [Parameter(Mandatory=$False)]
        [switch]$TryWithoutHypervisorInfo,

        [Parameter(Mandatory=$False)]
        [switch]$AllowRestarts,

        [Parameter(
            Mandatory=$True,
            ParameterSetName = 'InfoAlreadyCollected'
        )]
        $GuestVMAndHVInfo, # Uses output of Get-GuestVMAndHypervisorInfo function

        [Parameter(Mandatory=$False)]
        [switch]$SkipHyperVInstallCheck
    )

    ##### BEGIN Variable/Parameter Transforms and PreRun Prep #####

    if ($PSBoundParameters['TargetHostNameCreds']) {
        if ($TargetHostNameCreds.GetType().FullName -ne "System.Management.Automation.PSCredential") {
            Write-Error "The object provided to the -TargetHostNameCreds parameter must be a System.Management.Automation.PSCredential! Halting!"
            $global:FunctionResult = "1"
            return
        }
    }
    if ($PSBoundParameters['HypervisorCreds']) {
        if ($HypervisorCreds.GetType().FullName -ne "System.Management.Automation.PSCredential") {
            Write-Error "The object provided to the -HypervisorCreds parameter must be a System.Management.Automation.PSCredential! Halting!"
            $global:FunctionResult = "1"
            return
        }
    }

    <#
    # From: https://msdn.microsoft.com/en-us/library/ms724358(VS.85).aspx
    $WindowsEditionTable = @{
        Windows10Pro = "30"
        Windows10Enterprise = "4"
        Windows10S = "B2"
        Windows10SN = "B3"
        ServerHyperCoreV = "40"
        ServerDatacenterEvaluation = "50"
        ServerDatacenterFull = "8"
        ServerDatacenterCore = "C"
        Windows10EnterpriseE = "46"
        Windows10EnterpriseEvaluation = "48"
        Windows10EnterpriseN = "1B"
        Windows10EnterpriseNEvaluation = "54"
        ServerEnterpriseFull = "A"
        ServerEnterpriseCore = "E"
        MicrosoftHyperVServer = "2A"
        Windows10ProN = "31"
        ServerStandardEvaluation = "4F"
        ServerStandard = "7"
        ServerStandardCore = "D"
    }
    #>


    # Get Guest VM and hypervisor info

    if (!$GuestVMAndHVInfo) {
        if (!$TargetHostNameOrIP -and !$TargetVMName) {
            $TargetHostNameOrIP = $env:ComputerName
        }

        try {
            $HostNameNetworkInfo = ResolveHost -HostNameOrIP $TargetHostNameOrIP
        }
        catch {
            Write-Error $_
            $global:FunctionResult = "1"
            return
        }

        $GetWorkingCredsSplatParams = @{
            RemoteHostNameOrIP          = $HostNameNetworkInfo.FQDN
            ErrorAction                 = "Stop"
        }
        if ($TargetHostNameCreds) {
            $GetWorkingCredsSplatParams.Add("AltCredentials",$TargetHostNameCreds)
        }
        
        try {
            $GetTargetHostCredsInfo = GetWorkingCredentials @GetWorkingCredsSplatParams
            if (!$GetTargetHostCredsInfo.DeterminedCredsThatWorkedOnRemoteHost) {throw "Can't determine working credentials for $($HostNameNetworkInfo.FQDN)!"}
            
            if ($GetTargetHostCredsInfo.CurrentLoggedInUserCredsWorked -eq $True) {
                $TargetHostNameCreds = $null
            }

            $TargetHostInvCmdLocation = $GetTargetHostCredsInfo.RemoteHostWorkingLocation
        }
        catch {
            Write-Error $_
            if ($PSBoundParameters['TargetHostNameCreds']) {
                Write-Error "The GetWorkingCredentials function failed! Check the credentials provided to the -TargetHostNameCreds parameter! Halting!"
            }
            else {
                Write-Error "The GetWorkingCredentials function failed! Try using the -TargetHostNameCreds parameter! Halting!"
            }
            $global:FunctionResult = "1"
            return
        }
    }

    if (![bool]$PSBoundParameters['GuestVMAndHVInfo']) {
        $GetVMAndHVSplatParams = @{}
        
        if ($($TargetHostNameOrIP -or $TargetHostInvCmdLocation) -and $GetVMAndHVSplatParams.Keys -notcontains "TargetHostNameOrIP") {
            if ($TargetHostInvCmdLocation) {
                $GetVMAndHVSplatParams.Add("TargetHostNameOrIP",$TargetHostInvCmdLocation)
            }
            elseif ($TargetHostNameOrIP) {
                $GetVMAndHVSplatParams.Add("TargetHostNameOrIP",$TargetHostNameOrIP)
            }
        }
        elseif ($TargetVMName -and $GetVMAndHVSplatParams.Keys -notcontains "TargetVMName") {
            $GetVMAndHVSplatParams.Add("TargetVMName",$TargetVMName)
        }

        if ($TargetHostNameCreds -and $GetVMAndHVSplatParams.Keys -notcontains "TargetHostNameCreds") {
            $GetVMAndHVSplatParams.Add("TargetHostNameCreds",$TargetHostNameCreds)
        }

        if ($($HypervisorFQDNOrIP -or $HypervisorInvCmdLocation) -and $GetVMAndHVSplatParams.Keys -notcontains "HypervisorFQDNOrIP") {
            if ($HypervisorInvCmdLocation) {
                $GetVMAndHVSplatParams.Add("HypervisorFQDNOrIP",$HypervisorInvCmdLocation)
            }
            elseif ($HypervisorFQDNOrIP) {
                $GetVMAndHVSplatParams.Add("HypervisorFQDNOrIP",$HypervisorFQDNOrIP)
            }
        }

        if ($HypervisorCreds -and $GetVMAndHVSplatParams.Keys -notcontains "HypervisorCreds") {
            $GetVMAndHVSplatParams.Add("HypervisorCreds",$HypervisorCreds)
        }
        
        if ($($TryWithoutHypervisorInfo -and $GetVMAndHVSplatParams.Keys -notcontains "TryWithoutHypervisorInfo") -or 
        $($(ConfirmAWSVM -EA SilentlyContinue) -or $(ConfirmAzureVM -EA SilentlyContinue) -or
        $(ConfirmGoogleComputeVM -EA SilentlyContinue))
        ) {
            $GetVMAndHVSplatParams.Add("TryWithoutHypervisorInfo",$True)
        }

        if ($AllowRestarts -and $GetVMAndHVSplatParams.Keys -notcontains "AllowRestarts") {
            $GetVMAndHVSplatParams.Add("AllowRestarts",$True)
        }

        if ($NoMacAddressSpoofing -and $GetVMAndHVSplatParams.Keys -notcontains "NoMacAddressSpoofing") {
            $GetVMAndHVSplatParams.Add("NoMacAddressSpoofing",$True)
        }

        if ($SkipHyperVInstallCheck -and $GetVMAndHVSplatParams.Keys -notcontains "SkipHyperVInstallCheck") {
            $GetVMAndHVSplatParams.Add("SkipHyperVInstallCheck",$True)
        }

        if ($SkipExternalvSwitchCheck -and $GetVMAndHVSplatParams.Keys -notcontains "SkipExternalvSwitchCheck") {
            $GetVMAndHVSplatParams.Add("SkipExternalvSwitchCheck",$True)
        }

        try {
            $GuestVMAndHVInfo = Get-GuestVMAndHypervisorInfo @GetVMAndHVSplatParams -ErrorAction SilentlyContinue -ErrorVariable GGIErr
            if (!$GuestVMAndHVInfo) {throw "The Get-GuestVMAndHypervisorInfo function failed! Halting!"}

            if ($PSVersionTable.PSEdition -eq "Core") {
                $GetPendingRebootAsString = ${Function:GetPendingReboot}.Ast.Extent.Text
                
                $RebootPendingCheck = Invoke-WinCommand -ComputerName localhost -ScriptBlock {
                    Invoke-Expression $args[0]
                    $(GetPendingReboot).RebootPending
                } -ArgumentList $GetPendingRebootAsString

                $RebootPendingFileCheck = Invoke-WinCommand -ComputerName localhost -ScriptBlock {
                    Invoke-Expression $args[0]
                    $(GetPendingReboot).PendFileRenVal
                } -ArgumentList $GetPendingRebootAsString
            }
            else {
                $RebootPendingCheck = $(GetPendingReboot).RebootPending
                $RebootPendingFileCheck = $(GetPendingReboot).PendFileRenVal
            }

            if ($GuestVMAndHVInfo.RestartNeeded -and $RebootPendingCheck -and $RebootPendingFileCheck -ne $null -and !$AllowRestarts) {
                Write-Verbose "You might need to restart $env:ComputerName before the GetNestedVirtCapabilities function can proceed! Halting!"
            }
        }
        catch {
            Write-Error $_
            if ($($GGIErr | Out-String) -match "The operation has timed out") {
                Write-Error "There was a problem downloading the Vagrant Box to be used to test the External vSwitch! Halting!"
                $global:FunctionResult = "1"
                return
            }
            else {
                Write-Host "Errors for the Get-GuestVMAndHypervisorInfo function are as follows:"
                Write-Error $($GGIErr | Out-String)
                $global:FunctionResult = "1"
                return
            }
        }
    }
    else {
        $ValidGuestVMAndHVInfoNoteProperties = @(
            "HypervisorNetworkInfo"
            "HypervisorInvCmdLocation"
            "HypervisorComputerInfo"
            "HypervisorOSInfo"
            "TargetVMInfoFromHyperV"
            "VMProcessorInfo"
            "VMNetworkAdapterInfo"
            "VMMemoryInfo"
            "HypervisorCreds"
            "HostNameNetworkInfo"
            "TargetHostInvCmdLocation"
            "HostNameComputerInfo"
            "HostNameOSInfo"
            "HostNameProcessorInfo"
            "HostNameBIOSInfo"
            "TargetHostNameCreds"
            "RestartNeeded"
            "RestartOccurred"
            "VirtualizationExtensionsExposed"
            "MacAddressSpoofingEnabled"
        )
        [System.Collections.ArrayList]$FoundIssueWithGuestVMAndHVInfo = @()
        $ParamObjMembers = $($GuestVMAndHVInfo | Get-Member -MemberType NoteProperty).Name
        foreach ($noteProp in $ParamObjMembers) {
            if ($ValidGuestVMAndHVInfoNoteProperties -notcontains $noteProp) {
                $null = $FoundIssueWithGuestVMAndHVInfo.Add($noteProp)
            }
        }
        if ($FoundIssueWithGuestVMAndHVInfo.Count -gt 3) {
            $ParamObjMembers
            Write-Error "The object provided to the -GuestVMAndHVInfo parameter is invalid! It must be output from the Get-GuestVMAndHypervisorInfo function! Halting!"
            $global:FunctionResult = "1"
            return
        }
    }

    if (!$GuestVMAndHVInfo) {
        Write-Error "There was a problem with the Get-GuestVMandHypervisorInfo function! Halting!"
        $global:FunctionResult = "1"
        return
    }

    if ($GuestVMAndHVInfo.HypervisorCreds -ne $null) {
        $HypervisorCreds = $GuestVMAndHVInfo.HypervisorCreds
    }
    if ($GuestVMAndHVInfo.TargetHostNameCreds -ne $null) {
        $TargetHostNameCreds = $GuestVMAndHVInfo.TargetHostNameCreds
        if ($TargetHostNameCreds -eq $(whoami)) {
            $TargetHostNameCreds = $null
        }
    }

    if ($GuestVMAndHVInfo.HostNameBIOSInfo.IsVirtual -eq $False) {
        Write-Error "The GetNestedVirtCapabilities function should only be used to determine if a Guest VM is capable of Nested Virtualization. $($GuestVMAndHVInfo.HostNameNetworkInfo.FQDN) is a physical machine! Halting!"
        $global:FunctionResult = "1"
        return
    }

    ##### END Variable/Parameter Transforms and PreRun Prep #####


    ##### BEGIN Main Body #####

    [System.Collections.ArrayList]$HypervisorSoftwareThatWillWorkOnGuestVM = @()
    [System.Collections.ArrayList]$PossibleOSArchitectureOfNestedVMs = @()
    [System.Collections.ArrayList]$StepsToAllow64BitNestedVMs = @()

    ## BEGIN Analyzing info about the Guest VM - i.e. the Virtual Machine Configuration itself ##

    # We need to determine if we have access to the Hyper-V hypervisor. If we're in a public cloud (like AWS, Azure, etc)
    # we can assume that we don't, in which case we cannot use Mac Address Spoofing for Nested VM networking, which means
    # that we need to run Get-GuestVMAndHypervisorInfo with the -TryWithoutHypervisorInfo switch and that we need to
    # setup a NAT Adapter on the Guest VM Hyper-V Host (as opposed to an External vSwitch). This info all needs to be
    # in this function's output.
    [System.Collections.ArrayList]$NetworkingOptions = @()
    if ($(ConfirmAWSVM -EA SilentlyContinue) -or $(ConfirmAzureVM -EA SilentlyContinue) -or 
    $(ConfirmGoogleComputeVM -EA SilentlyContinue) -or $GuestVMAndHVInfo.HypervisorNetworkInfo -eq $null -or
    $GuestVMAndHVInfo.HypervisorComputerInfo -eq $null -or $GuestVMAndHVInfo.HypervisorOSInfo -eq $null -or
    $TryWithoutHypervisorInfo
    ) {
        $null = $NetworkingOptions.Add("Network Address Translation")
        
        $null = $StepsToAllow64BitNestedVMs.Add("Might need Disable Dynamic Memory")
        $null = $StepsToAllow64BitNestedVMs.Add("Might need to Remove Save States")
    }

    if ($GuestVMAndHVInfo.MacAddressSpoofingEnabled -eq $True -or $GuestVMAndHVInfo.VMNetworkAdapterInfo.MacAddressSpoofing -eq $True) {
        $null = $NetworkingOptions.Add("Mac Address Spoofing")
    }
    elseif ($GuestVMAndHVInfo.MacAddressSpoofingEnabled -ne $True -or
    $($GuestVMAndHVInfo.MacAddressSpoofingEnabled -ne $True -and $GuestVMAndHVInfo.VMNetworkAdapterInfo -eq $null)
    ) {
        $null = $StepsToAllow64BitNestedVMs.Add("Might need to Turn On MacAddress Spoofing")
    }

    if ($($GuestVMAndHVInfo.VMProcessorInfo -ne $null -and 
    $GuestVMAndHVInfo.VMProcessorInfo.ExposeVirtualizationExtensions -eq $False) -or
    $GuestVMAndHVInfo.VirtualizationExtensionsExposed -match "Unknown" -or
    $GuestVMAndHVInfo.VirtualizationExtensionsExposed -eq $False
    ) {
        $null = $StepsToAllow64BitNestedVMs.Add("Expose Virtualization Extensions")
        $NestedVirtualizationPossible = $False
    }
    elseif ($GuestVMAndHVInfo.VirtualizationExtensionsExposed -eq $null) {
        $null = $StepsToAllow64BitNestedVMs.Add("Might need to Expose Virtualization Extensions")
        $null = $PossibleOSArchitectureOfNestedVMs.Add("MAYBE_64-bit")
    }

    # Other information about the hypervisor...
    if ($GuestVMAndHVInfo.HypervisorOSInfo -ne $null -and $GuestVMAndHVInfo.HypervisorOSInfo.OSArchitecture -ne "64-bit") {
        $null = $StepsToAllow64BitNestedVMs.Add("The hypervisor must be running on a 64-bit Operating System.")
    }

    if ($GuestVMAndHVInfo.HypervisorOSInfo -ne $null) {
        $HypervisorOSNameCheck = $GuestVMAndHVInfo.HypervisorOSInfo.Caption -match "Windows 10||Windows Server 2016"
        $HypervisorOSTypeCheck = $GuestVMAndHVInfo.HypervisorOSInfo.Caption -match "Pro|Enterprise|Server"

        if (!$($HypervisorOSNameCheck -and $HypervisorOSTypeCheck) -and $GuestVMAndHVInfo.HypervisorOSInfo -ne $null) {
            $NestedVirtualizationPossible = $False
            $null = $StepsToAllow64BitNestedVMs.Add("The hypervisor must be running on Windows 10 Pro/Enterprise or Windows Server 2016.")
        }
    }

    # Guest VM bust be version 8.0 or higher
    if ($GuestVMAndHVInfo.TargetVMInfoFromHyperV -ne $null) {
        if ($([version]$GuestVMAndHVInfo.TargetVMInfoFromHyperV.Version).Major -lt 8) {
            $null = $StepsToAllow64BitNestedVMs.Add("Guest VM Configuration must be Version 8.0 or higher")
        }
    }

    ## END Analyzing info about the Guest VM - i.e. the Virtual Machine Configuration itself ##

    ## BEGIN Analyzing info about the Guest VM OS ##

    if ($GuestVMAndHVInfo.HostNameOSInfo.OSArchitecture -ne "64-bit") {
        $NestedVirtualizationPossible = $False
        $null = $StepsToAllow64BitNestedVMs.Add("Change the Guest VM OS Architecture to 64-bit")
    }

    # If at this point, $StepsToAllow64BitNestedVMs.Count -eq 0, or just "Might need to Turn On MacAddress Spoofing"
    # then we know the following to be true
    if ($StepsToAllow64BitNestedVMs.Count -eq 0 -or
    $($StepsToAllow64BitNestedVMs.Count -eq 1 -and $StepsToAllow64BitNestedVMs[0] -eq "Might need to Turn On MacAddress Spoofing")) {
        $NestedVirtualizationPossible = $True
        $null = $PossibleOSArchitectureOfNestedVMs.Add("32-bit")
        $null = $HypervisorSoftwareThatWillWorkOnGuestVM.Add("VMWare")
        $null = $HypervisorSoftwareThatWillWorkOnGuestVM.Add("Xen")
        $null = $HypervisorSoftwareThatWillWorkOnGuestVM.Add("VirtualBox")
    }

    $GuestVMOSNameCheck = $GuestVMAndHVInfo.HostNameOSInfo.Caption -match "Windows 10||Windows Server 2016"
    $GuestVMOSTypeCheck = $GuestVMAndHVInfo.HostNameOSInfo.Caption -match "Pro|Enterprise|Server"

    ## END Analyzing info about the Guest VM OS ##

    ## BEGIN Mixed Analysis ##

    # If the Hypervisor and VM are Windows 2016, then 64-bit Nested VMs are also possible
    if (!$($GuestVMOSNameCheck -and $GuestVMOSTypeCheck)) {
        $null = $StepsToAllow64BitNestedVMs.Add("Run Windows 10 Pro/Enterprise or Windows Server 2016 as the Guest VM OS.")
    }
    else {
        if (!$GuestVMAndHVInfo.HostNameProcessorInfo.VirtualizationFirmwareEnabled) {
            $null = $StepsToAllow64BitNestedVMs.Add("Might need to Enable VirtualizationFirmware")
        }

        $null = $HypervisorSoftwareThatWillWorkOnGuestVM.Add("Hyper-V")
        if ($PossibleOSArchitectureOfNestedVMs -notcontains "MAYBE_64-bit") {
            $null = $PossibleOSArchitectureOfNestedVMs.Add("64-bit")
        }
    }

    if ($GuestVMAndHVInfo.HypervisorOSInfo -eq $null -and 
    $GuestVMAndHVInfo.HostNameBIOSInfo.SMBIOSBIOSVersion -notmatch "Hyper-V|VMWare|Xen|American Megatrends" -and 
    $GuestVMAndHVInfo.HostNameBIOSInfo.Manufacturer -notmatch "Hyper-V|VMWare|Xen|American Megatrends" -and 
    $GuestVMAndHVInfo.HostNameBIOSInfo.Name -notmatch "Hyper-V|VMWare|Xen|American Megatrends" -and 
    $GuestVMAndHVInfo.HostNameBIOSInfo.SerialNumber -notmatch "Hyper-V|VMWare|Xen|American Megatrends" -and 
    $GuestVMAndHVInfo.HostNameBIOSInfo.Version -notmatch "Hyper-V|VMWare|Xen|American Megatrends|VRTUAL"
    ) {
        $null = $StepsToAllow64BitNestedVMs.Add("Use Hyper-V or VMWare or Xen as the baremetal hypervisor.")
    }

    if ($GuestVMAndHVInfo.VirtualizationExtensionsExposed -eq $True) {
        $NestedVirtualizationPossible = $True
    }

    $FinalSteps = [pscustomobject]@{
        StepsThatAreDefinitelyNeeded = $($StepsToAllow64BitNestedVMs | Where-Object {$_ -notmatch "Might need"})
        StepsThatMightBeNeeded       = $($StepsToAllow64BitNestedVMs | Where-Object {$_ -match "Might need"})
    }

    ## END Mixed Analysis ##

    [pscustomobject]@{
        NestedVirtualizationPossible                = $NestedVirtualizationPossible
        PossibleOSArchitectureOfNestedVMs           = if ($NestedVirtualizationPossible) {$PossibleOSArchitectureOfNestedVMs} else {$null}
        HypervisorSoftwareThatWillWorkOnGuestVM     = if ($NestedVirtualizationPossible) {$HypervisorSoftwareThatWillWorkOnGuestVM} else {$null}
        StepsToAllow64BitNestedVMs                  = $FinalSteps
        NetworkingPossibilities                     = if ($NestedVirtualizationPossible) {[array]$NetworkingOptions} else {$null}
        GuestVMAndHVInfo                            = $GuestVMAndHVInfo
    }

    ##### END Main Body #####
}