eflowautodeploy/eflowAutoDeploy.ps1 (1,055 lines of code) (raw):

<# .DESCRIPTION This module contains the functions related to EFLOW setup on a PC #> param( [switch] $AutoDeploy ) New-Variable -Name eflowAutoDeployVersion -Value "1.0.220831.0800" -Option Constant -ErrorAction SilentlyContinue #Hashtable to store session information $eadSession = @{ "HostPC" = @{"FreeMem" = 0; "TotalMem" = 0; "FreeDisk" = 0; "TotalDisk" = 0; "TotalCPU" = 0; "Name" = $null} "HostOS" = @{"Name" = $null; "Version" = $null; "IsServerSKU" = $false; "IsVM" = $false; "IsAzureVM" = $false} "EFLOW" = @{"Product" = $null; "Version" = $null} "UserConfig" = $null "UserConfigFile" = $null } New-Variable -Option Constant -ErrorAction SilentlyContinue -Name eflowProducts -Value @{ "Azure IoT Edge LTS" = "https://aka.ms/AzEflowMSI" "Azure IoT Edge CR X64" = "https://aka.ms/AzEFLOWMSI-CR-X64" "Azure IoT Edge CR ARM64" = "https://aka.ms/AzEFLOWMSI-CR-ARM64" "Azure IoT Edge 1.4 LTS X64" = "https://aka.ms/AzEFLOWMSI_1_4_LTS_X64" "Azure IoT Edge 1.4 LTS ARM64" = "https://aka.ms/AzEFLOWMSI_1_4_LTS_ARM64" } New-Variable -Option Constant -ErrorAction SilentlyContinue -Name eflowProvisioningProperties -Value @{ "ManualConnectionString" = @("devConnString") "ManualX509" = @("iotHubHostname", "deviceId", "identityCertPath", "identityPrivKeyPath") "DpsTPM" = @("scopeId") "DpsX509" = @("scopeId", "identityCertPath", "identityPrivKeyPath") "DpsSymmetricKey" = @("scopeId", "symmKey", "registrationId") } function Test-AdminRole { If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(` [Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Host "Error: This module requires Administrator mode!" -ForegroundColor Red return $false } return $true } function Get-HostPcInfo { Write-Host "Running eflowAutoDeploy version $eflowAutoDeployVersion" $pOS = Get-CimInstance Win32_OperatingSystem $UBR = (Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name UBR) $eadSession.HostOS.Name = $pOS.Caption $eadSession.HostOS.Version = "$($pOS.Version).$UBR" Write-Host "HostOS`t: $($pOS.Caption)($($pOS.OperatingSystemSKU)) `nVersion`t: $($eadSession.HostOS.Version) `nLang`t: $($pOS.MUILanguages) `nName`t: $($pOS.CSName)" #ProductTypeDomainController -Value 2 , #ProductTypeServer -Value 3 $eadSession.HostPC.Name = $pOS.CSName $eadSession.HostOS.IsServerSKU = ($pOS.ProductType -eq 2 -or $pOS.ProductType -eq 3) $eadSession.HostPC.FreeMem = [Math]::Round($pOS.FreePhysicalMemory / 1MB) # convert kilo bytes to GB $pCS = Get-WmiObject -Class Win32_ComputerSystem $eadSession.HostPC.TotalMem = [Math]::Round($pCS.TotalPhysicalMemory / 1GB) $eadSession.HostPC.TotalCPU = $pCS.numberoflogicalprocessors Write-Host "Total CPUs`t`t: $($eadSession.HostPC.TotalCPU)" Write-Host "Free RAM / Total RAM`t: $($eadSession.HostPC.FreeMem) GB / $($eadSession.HostPC.TotalMem) GB" $pCDrive = (Get-WmiObject Win32_LogicalDisk ) | Where-Object { $_.DeviceID -eq 'C:' } #Get the C device size $eadSession.HostPC.FreeDisk = [Math]::Round($pCDrive.Freespace / 1GB) # convert bytes into GB $eadSession.HostPC.TotalDisk = [Math]::Round($pCDrive.Size / 1GB) # convert bytes into GB Write-Host "Free Disk / Total Disk`t: $($eadSession.HostPC.FreeDisk) GB / $($eadSession.HostPC.TotalDisk) GB" if ((Get-CimInstance Win32_BaseBoard).Product -eq 'Virtual Machine'){ $eadSession.HostOS.IsVM = $true Write-Host "Running as a virtual machine " -NoNewline if (Get-Service WindowsAzureGuestAgent -ErrorAction SilentlyContinue){ $eadSession.HostOS.IsAzureVM = $true Write-Host "in Azure environment " -NoNewline } if ($pCS.HypervisorPresent) { Write-Host "with Nested Hyper-V enabled" #(Get-VMProcessor -VM $vm).ExposeVirtualizationExtensions } else { Write-Host "without Nested Hyper-V" -ForegroundColor Red } } Get-EadEflowInstalledVersion | Out-Null } function Get-EadUserConfig { if ($null -eq $eadSession.UserConfig) { Write-Host "Error: EFLOW UserConfig is not set." -ForegroundColor Red } return $eadSession.UserConfig } function Read-EadUserConfig { if ($eadSession.UserConfigFile) { $eadSession.UserConfig = Get-Content "$($eadSession.UserConfigFile)" | ConvertFrom-Json # Remove unncesary empty network parameters $eadSession.UserConfig.network.PSObject.Properties | ForEach { if ($_.TypeNameOfValue -is [System.String] -and [string]::IsNullOrEmpty($_.Value)) { $eadSession.UserConfig.network.PSObject.Properties.Remove($_.Name) } } } else { Write-Host "Error: EFLOWUserConfigFile not configured" -ForegroundColor Red } } function Set-EadUserConfig { <# .DESCRIPTION Check if there is a configuration file, and loads the JSON configuration into a variable #> Param ( [Parameter(Position = 0, Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$eflowjson ) if (!(Test-Path -Path "$eflowjson" -PathType Leaf)) { Write-Host "Incorrect json file: $eflowjson" return } Write-Host "Loading $eflowjson.." $eadSession.UserConfigFile = "$eflowjson" Read-EadUserConfig } function Test-EadUserConfigNetwork { <# .DESCRIPTION Checks the EFLOW user configuration needed for EFLOW Network setup #> $errCnt = 0 $eflowConfig = Get-EadUserConfig # 0) Check Hyper-V status Test-HyperVStatus | Out-Null # 1) Check the virtual switch name $nwCfg = $eflowConfig.network if ($null -eq $nwCfg) { if ($eadSession.HostOS.IsServerSKU) { Write-Host "Error: Server SKU, requires Network configuration, see https://aka.ms/AzEFLOW-vSwitch" -ForegroundColor Red return $false } else { Write-Host "* Client SKU : No Network configuration specified. Default Switch will be used." -ForegroundColor Green return $true } } if ([string]::IsNullOrEmpty($nwCfg.vswitchName)) { if ($eadSession.HostOS.IsServerSKU) { Write-Host "Error: Server SKU, a virutal switch is needed - For more information about EFLOW virutal switch creation, see https://aka.ms/AzEFLOW-vSwitch" -ForegroundColor Red $errCnt += 1 } else { Write-Host "* Client SKU : No virtual switch specified - Default Switch will be used." -ForegroundColor Green } } else { Write-Host "* Using virtual switch $($nwCfg.vswitchName)" -ForegroundColor Green } # Check if the virtual switch type and associated properties switch ($nwCfg.vSwitchType) { "Internal" { if (-not $eadSession.HostOS.IsServerSKU ) { Write-Host "Error: vSwitchType is incorrect. Supported types : External (Client and Server) and Internal (Server)" -ForegroundColor Red $errCnt += 1 } else { Write-Host "* Using vSwitchType Internal" -ForegroundColor Green } } "External" { if ([string]::IsNullOrEmpty($nwCfg.adapterName)) { Write-Host "Error: adapterName required for External switch" -ForegroundColor Red $errCnt += 1 } else { $nwadapters = (Get-NetAdapter -Physical) | Where-Object { $_.Status -eq "Up" } if ($nwadapters.Name -notcontains ($nwCfg.adapterName)) { Write-Host "Error: $($nwCfg.adapterName) not found. External switch creation will fail." -ForegroundColor Red $errCnt += 1 } else { Write-Host "* Using vSwitchType External" -ForegroundColor Green } } } default { if ($eadSession.HostOS.IsServerSKU ) { Write-Host "Error: vSwitchType is incorrect. Supported types : External (Client and Server) and Internal (Server)" -ForegroundColor Red $errCnt += 1 } else { Write-Host "* Using vSwitchType Default" -ForegroundColor Green } } } # 3) Check the virtual switch IP address allocation Write-Host "--- Verifying virtual switch IP address allocation..." if ([string]::IsNullOrEmpty($nwCfg.ip4Address) -and [string]::IsNullOrEmpty($nwCfg.ip4GatewayAddress) -and [string]::IsNullOrEmpty($nwCfg.ip4PrefixLength)) { Write-Host "* No static IP address provided - DHCP allocation or auto-static ip(internal switch) will be used" -ForegroundColor Green } elseif ([string]::IsNullOrEmpty($nwCfg.ip4Address) -or [string]::IsNullOrEmpty($nwCfg.ip4GatewayAddress) -or [string]::IsNullOrEmpty($nwCfg.ip4PrefixLength)) { Write-Host "Error: IP4Address, IP4GatewayAddress and IP4PrefixLength parameters are needed" -ForegroundColor Red $errCnt += 1 } else { $ipconfigstatus = $true if (-not ($nwCfg.ip4Address -as [IPAddress] -as [Bool])) { Write-Host "Error: Invalid IP4Address $($nwCfg.ip4Address)" -ForegroundColor Red $errCnt += 1 $ipconfigstatus = $false } else { #Ping IP to ensure it is free $status = Test-Connection $nwCfg.ip4Address -Count 1 -Quiet if ($status) { Write-Host "Error: ip4Address $($nwCfg.ip4Address) in use" -ForegroundColor Red $errCnt += 1 $ipconfigstatus = $false } } if (-not ($nwCfg.ip4GatewayAddress -as [IPAddress] -as [Bool])) { Write-Host "Error: Invalid IP4GatewayAddress $($nwCfg.ip4GatewayAddress)" -ForegroundColor Red $errCnt += 1 $ipconfigstatus = $false } else { $status = Test-Connection $nwCfg.ip4GatewayAddress -Count 1 -Quiet if ((-not $status) -and ($nwCfg.vSwitchType -ieq "External")) { Write-Host "Error: ip4GatewayAddress $($nwCfg.ip4GatewayAddress) is not reachable. Required for external switch" -ForegroundColor Red $errCnt += 1 $ipconfigstatus = $false } } [IPAddress]$mask = "255.255.255.0" if ( (([IPAddress]$nwCfg.ip4GatewayAddress).Address -band $mask.Address ) -ne (([IPAddress]$nwCfg.ip4Address).Address -band $mask.Address)) { Write-Host "Error: ip4GatewayAddress and ip4Address are not in the same subnet" -ForegroundColor Red $errCnt += 1 $ipconfigstatus = $false } if ($nwCfg.ip4PrefixLength -as [int] -lt 0 -and $nwCfg.ip4PrefixLength -as [int] -ge 32) { Write-Host "Error: Invalid IP4PrefixLength $($nwCfg.ip4PrefixLength). Should be an integer between 0 and 32" -ForegroundColor Red $errCnt += 1 $ipconfigstatus = $false } if ($ipconfigstatus) { Write-Host "* Using virtual switch with IP4Address: $($nwCfg.ip4Address)" -ForegroundColor Green Write-Host " ip4GatewayAddress: $($nwCfg.ip4GatewayAddress)" -ForegroundColor Green Write-Host " PrefixLength: $($nwCfg.ip4PrefixLength)" -ForegroundColor Green } } #TODO : Ping dnsServers for reachability. No Tests for http proxies Write-Host "--- Checking proxy settings..." if ($nwCfg.useHostProxy) { Write-Host "useHostProxy is set, ignoring httpProxy, httpsProxy, ftpProxy settings" $hostSettings = Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' | Select-Object ProxyServer, ProxyEnable if ($hostSettings.ProxyEnable) { Write-Host "Using $($hostSettings.ProxyServer) as proxy settings" } else { Write-Host "Warning: useHostProxy is set, but no proxy setting found in host." -ForegroundColor Yellow } } else { if (![string]::IsNullOrEmpty($nwCfg.httpProxy) -and $nwCfg.httpProxy -NotLike "http://*") { Write-Host "Error: The httpProxy address $($nwCfg.httpProxy) is not valid." -ForegroundColor Red $errCnt += 1 } if (![string]::IsNullOrEmpty($nwCfg.httpsProxy) -and $nwCfg.httpsProxy -NotLike "https://*") { Write-Host "Error: The httpsProxy address $($nwCfg.httpsProxy) is not valid." -ForegroundColor Red $errCnt += 1 } if (![string]::IsNullOrEmpty($nwCfg.ftpProxy) -and $nwCfg.ftpProxy -NotLike "ftp://*") { Write-Host "Error: The ftpProxy address $($nwCfg.ftpProxy) is not valid." -ForegroundColor Red $errCnt += 1 } } $retval = $true if ($errCnt) { Write-Host "$errCnt errors found in the Network Configuration. Fix errors before deployment" -ForegroundColor Red $retval = $false } else { Write-Host "*** No errors found in the Network Configuration." -ForegroundColor Green } return $retval } function Test-EadUserConfigInstall { $errCnt = 0 $eflowConfig = Get-EadUserConfig Write-Host "`n--- Verifying EFLOW Install Configuration..." # 1) Check the product requested is valid if ($Script:eflowProducts.ContainsKey($eflowConfig.eflowProduct)) { if ($eadSession.EFLOW.Product) { #if already installed, check if they match if ($eadSession.EFLOW.Product -ne $eflowConfig.eflowProduct) { Write-Host "Error: Installed product $($eadSession.EFLOW.Product) does not match requested product $($eflowConfig.eflowProduct)." -ForegroundColor Red $errCnt += 1 } else { Write-Host "* $($eflowConfig.eflowProduct) is installed" -ForegroundColor Green } } else { Write-Host "* $($eflowConfig.eflowProduct) to be installed" -ForegroundColor Green } } else { Write-Host "Error: Incorrect eflowProduct." -ForegroundColor Red Write-Host "Supported products: [$($Script:eflowProducts.Keys -join ',' )]" $errCnt += 1 } # 2) Check if ProductUrl is valid if specified if (-not [string]::IsNullOrEmpty($eflowConfig.eflowProductUrl) -and (-not ([system.uri]::IsWellFormedUriString($eflowConfig.eflowProductUrl, [System.UriKind]::Absolute)))) { Write-Host "Error: eflowProductUrl is incorrect. $($eflowConfig.eflowProductUrl)." -ForegroundColor Red $errCnt += 1 } # 3) Check if the install options are proper $installOptions = $eflowConfig.installOptions if ($installOptions) { $installOptItems = @("installPath", "vhdxPath") foreach ($item in $installOptItems) { $path = $installOptions[$item] if (-not [string]::IsNullOrEmpty($path) -and (-not (Test-Path -Path $path -IsValid))) { Write-Host "Error: Incorrect item. : $path" -ForegroundColor Red $errCnt += 1 } } } $retval = $true if ($errCnt) { Write-Host "$errCnt errors found in the Install Configuration. Fix errors before Install" -ForegroundColor Red $retval = $false } else { Write-Host "*** No errors found in the Install Configuration." -ForegroundColor Green } return $retval } function Test-EadUserConfigDeploy { <# .DESCRIPTION Checks the EFLOW user configuration needed for EFLOW VM deployment Return $true if no blocking errors are found, and $false otherwise #> $errCnt = 0 $eflowConfig = Get-EadUserConfig $euCfg = $eflowConfig.enduser Write-Host "`n--- Verifying EFLOW VM Deployment Configuration..." # 1) Check Mandatory configuration EULA Write-Host "--- Verifying EULA and telemetry..." if (($euCfg.acceptEula) -and ($euCfg.acceptEula -eq "Yes")) { Write-Host "* EULA accepted." -ForegroundColor Green } else { Write-Host "Error: Missing/incorrect mandatory EULA acceptance. Set acceptEula Yes" -ForegroundColor Red $errCnt += 1 } if (($euCfg.acceptOptionalTelemetry) -and ($euCfg.acceptOptionalTelemetry -eq "Yes")) { Write-Host "* Optional telemetry accepted." -ForegroundColor Green } else { Write-Host "- Optional telemetry not accepted. Basic telemetry will be sent." -ForegroundColor Yellow } # 2) Check the virtual switch specified Write-Host "--- Verifying virtual switch..." if (-not (Test-EadEflowVmSwitch)) { $errCnt += 1 } # 3) Check the virtual switch memory, cpu, and storage $vmCfg = $eflowConfig.vmConfig Write-Host "--- Verifying virtual machine resources..." if ($vmCfg.cpuCount -gt 0) { Write-Host "* Virtual machine will be created with $($vmCfg.cpuCount) vCPUs." } else { Write-Host "* No custom vCPUs used - Using default configuration, virtual machine will be created with 1 vCPUs." if ($vmCfg) { $vmCfg.PSObject.properties.remove('cpuCount') } } if ($vmCfg.memoryInMB -gt 0) { Write-Host "* Virtual machine will be created with $($vmCfg.memoryInMB) MB of memory." } else { Write-Host "* No custom memory used - Using default configuration, virtual machine will be created with 1024 MB of memory." if ($vmCfg) { $vmCfg.PSObject.properties.remove('memoryInMB') } } if ($vmCfg.vmDiskSize) { $lowerLimit = 21 if ($eflowConfig.eflowProduct -ieq "Azure IoT Edge LTS") { $lowerLimit = 8 } if (($vmCfg.vmDiskSize -ge $lowerLimit) -and ($vmCfg.vmDiskSize -le 2000)) { #Between $lowerLimit GB and 2 TB Write-Host "* Virtual machine VHDX will be created with $($vmCfg.vmDiskSize) GB disk size." } else { Write-Host "Error: vmDiskSize should be between $lowerLimit GB and 2000 GB(2TB)" -ForegroundColor Red $errCnt += 1 } } else { Write-Host "* No custom disk size used - Using default configuration, virtual machine VHDX will be created with 29 GB of disk size." if ($vmCfg) { $vmCfg.PSObject.properties.remove('vmDiskSize') } } if ($eflowConfig.eflowProduct -ieq "Azure IoT Edge LTS") { if ($vmCfg.vmDataSize -gt 0) { Write-Host "Error: vmDataSize is not supported in Azure IoT Edge LTS" -ForegroundColor Red $errCnt += 1 } } else { if ($vmCfg.vmDataSize) { if (($vmCfg.vmDataSize -ge 2) -and ($vmCfg.vmDataSize -le 2000)) { #Between 2 GB and 2 TB Write-Host "* Virtual machine VHDX will be created with $($vmCfg.vmDataSize) GB of data size." } else { Write-Host "Error: vmDataSize should be between 2 GB and 2000 GB(2TB)" -ForegroundColor Red $errCnt += 1 } } else { Write-Host "* No custom data size used - Using default configuration, virtual machine VHDX will be created with 10 GB of data size." if ($vmCfg) { $vmCfg.PSObject.properties.remove('vmDataSize') } } } if ($vmCfg.vmDataSize -and $vmCfg.vmDiskSize) { Write-Host "Error: Both vmDataSize and vmDiskSize specified. Specify one." -ForegroundColor Red $errCnt += 1 } # 4) Check GPU passthrough configuration Write-Host "--- Verifying GPU passthrough configuration..." if ([string]::IsNullOrEmpty($vmCfg.gpuPassthroughType)) { Write-Host "* No GPU passthrough being used - CPU only allocation" if ($vmCfg) { $vmCfg.PSObject.properties.remove('gpuPassthroughType') $vmCfg.PSObject.properties.remove('gpuName') $vmCfg.PSObject.properties.remove('gpuCount') } } else { if ($vmCfg.gpuPassthroughType -ne 'DirectDeviceAssignment' -and $vmCfg.gpuPassthroughType -ne 'ParaVirtualization') { Write-Host "Error: GpuPassthrough type is invalid - Supported types: DirectDeviceAssignment or ParaVirtualization" -ForegroundColor Red $errCnt += 1 } else { Write-Host "* $($vmCfg.gpuPassthroughType) specified" -ForegroundColor Green } if ([string]::IsNullOrEmpty($vmCfg.gpuName)) { Write-Host "Error: GpuName must be provided" -ForegroundColor Red $errCnt += 1 } } if ($vmCfg -and $null -eq $vmCfg.PSObject.properties.Name) { $eflowConfig.PSObject.properties.remove('vmConfig') } $retval = $true if ($errCnt) { Write-Host "$errCnt errors found in the Deployment Configuration. Fix errors before deployment" -ForegroundColor Red $retval = $false } else { Write-Host "*** No errors found in the Deployment Configuration." -ForegroundColor Green } return $retval } function Test-EadUserConfigProvision { <# .DESCRIPTION Checks the EFLOW user configuration needed for EFLOW provisioning #> $errCnt = 0 $eflowConfig = Get-EadUserConfig $provCfg = $eflowConfig.eflowProvisioning if ($null -eq $provCfg) { Write-Host "- Provisioning Configuration not specified." -ForegroundColor Yellow return $false } Write-Host "`n--- Verifying EFLOW VM Provisioning Configuration..." # 1) Check provisioning type Write-Host "--- Verifying provisioning type..." if (-not $Script:eflowProvisioningProperties.ContainsKey($provCfg.provisioningType)) { Write-Host "Error: provisioningType is incorrect or not specified.`nSupported provisioningType: [$($Script:eflowProvisioningProperties.Keys)]" -ForegroundColor Red $errCnt += 1 } else { Write-Host "* $($provCfg.provisioningType)" -ForegroundColor Green } # 2) Check parameters required for the provisioning type Write-Host "--- Verifying provisioning parameters.." $reqSettings = $Script:eflowProvisioningProperties[$provCfg.provisioningType] if ($reqSettings) { #if we have valid parameter array foreach ($setting in $reqSettings) { #verify if the parameters are specified $value = $provCfg.$setting if ($value) { # Not writing out the value as it may have credentials that we dont want in logs Write-Host "* $setting Ok" -ForegroundColor Green #if parameter contains path, ensure that the file is present if ($setting.Contains("Path")) { if (-not (Test-Path -Path $value -PathType Leaf)) { Write-Host "Error: File not found :$value" -ForegroundColor Red $errCnt += 1 } } } else { Write-Host "Error: provisioningType $($provCfg.provisioningType) requires $setting value." -ForegroundColor Red $errCnt += 1 } } } $retval = $true if ($errCnt) { Write-Host "$errCnt errors found in the Provisioning Configuration. Fix errors before deployment" -ForegroundColor Red $retval = $false } else { Write-Host "No errors found in the Provisioning Configuration." -ForegroundColor Green } return $retval } function Test-EadUserConfig { $installResult = Test-EadUserConfigInstall $deployResult = Test-EadUserConfigDeploy $provResult = Test-EadUserConfigProvision return ($installResult -and $deployResult -and $provResult) } function Test-EadEflowInstall { Param ( [Switch] $Install ) $eflowVersion = Get-EadEflowInstalledVersion if ($null -eq $eflowVersion) { if (!$Install) { return $false } if (-not (Invoke-EadEflowInstall)) { return $false } } $mod = Get-Module -Name AzureEFLOW #check if module is loaded if (!$mod) { Write-Host "Importing AzureEFLOW.." Import-Module -Name AzureEFLOW -Force } $version = (Get-Module -Name AzureEFLOW).Version.ToString() Write-Host "AzureEFLOW Module:$version" return $true } function Test-HyperVStatus { Param ( [Switch] $Enable ) $retval = $true #Enable HyperV $feature = Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V if ($feature.State -ne "Enabled") { $retval = $false Write-Host "Hyper-V is disabled" -ForegroundColor Red if ($Enable) { Write-Host "Enabling Hyper-V" if ($eadSession.HostOS.IsServerSKU) { Install-WindowsFeature -Name Hyper-V -IncludeAllSubFeature -IncludeManagementTools } else { Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All -NoRestart } Write-Host "Rebooting machine for enabling Hyper-V" -ForegroundColor Yellow Restart-Computer -Force -ErrorAction SilentlyContinue } } else { Write-Host "Hyper-V is enabled" -ForegroundColor Green } return $retval } function Test-EadEflowVmProvision { <# .DESCRIPTION Checks if the EFLOW VM is provisioned #> $retval = $true if (Test-EadEflowVmRun) { $command = "if [[ `$(sudo sha256sum /etc/iotedge/config.yaml | cut -f1 -d' ') == `$(sudo sha256sum /var/.eflow/config/config.yaml | cut -f1 -d' ') ]]; then echo clean; fi" $ret = Invoke-EflowVmCommand -command $command if ($ret -like "clean") { $retval = $false } else { Write-Host "config.yaml is not default. EFLOW VM maybe provisioned earlier." -ForegroundColor Yellow } } else { Write-Host "Error: Eflow VM is not running. Cannot determine provisioning status." -ForegroundColor Red } return $retval } function Test-EadEflowVmRun { <# .DESCRIPTION Checks if the EFLOW VM is running #> $retval = $false if ($eadSession.HostOS.IsServerSKU) { $vm = Get-VM | Where-Object { $_.Name -like '*EFLOW'} if ($vm -and ($vm.State -ieq 'Running')) { $retval = $true } } else { $found = (hcsdiag list) | Select-String -Pattern 'wssdagent' <# hcsdiag list -raw supported only in later windows versions $pVMList = (hcsdiag list -raw) | ConvertTo-Json -ErrorAction SilentlyContinue $found = $pVMList | Where-Object { $_.Owner -like 'wssdagent'} #> if ($found) { $retval = $true } } return $retval } function Test-EadEflowVmDeploy { <# .DESCRIPTION Checks if the EFLOW VM is deployed (checking vhdx is present) #> $vhdxPath = "C:\\Program Files\\Azure IoT Edge" $eflowConfig = Get-EadUserConfig if ($eflowConfig.installOptions.vhdxPath) { $vhdxPath = $eflowConfig.installOptions.vhdxPath } $retval = Test-Path -Path $vhdxPath\*EFLOW.vhdx return $retval } function Invoke-EadEflowInstall { <# .DESCRIPTION Checks if EFLOW MSI is installed, and installs it if not #> #TODO : Add Force flag to uninstall and install req product if ($eadSession.EFLOW.Version) { Write-Host "$($eadSession.EFLOW.Product)-$($eadSession.EFLOW.Version) is already installed" return $true } $eflowConfig = Get-EadUserConfig if ($null -eq $eflowConfig) { return $retval } if (-not (Test-EadUserConfigInstall)) { return $false } # bail if the validation failed $reqProduct = $eflowConfig.eflowProduct $url = $Script:eflowProducts[$reqProduct] if ($eflowConfig.eflowProductUrl) { $url = $eflowConfig.eflowProductUrl } Write-Host "Installing $reqProduct from $url" $ProgressPreference = 'SilentlyContinue' Invoke-WebRequest $url -OutFile .\AzureIoTEdge.msi $argList = '/I AzureIoTEdge.msi /qn ' if ($eflowConfig.installOptions) { $installPath = $eflowConfig.installOptions.installPath if ($installPath) { $argList = $argList + "INSTALLDIR=""$($installPath)"" " } $vhdxPath = $eflowConfig.installOptions.vhdxPath if ($vhdxPath) { $argList = $argList + "VHDXDIR=""$($vhdxPath)"" " } } Write-Host $argList Start-Process msiexec.exe -Wait -ArgumentList $argList Remove-Item .\AzureIoTEdge.msi $ProgressPreference = 'Continue' Write-Host "$reqProduct successfully installed" return $true } function Remove-EadEflowInstall { <# .DESCRIPTION Checks if EFLOW MSI is installed, and removes it if installed #> $eflowInfo = Get-ChildItem -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' | Get-ItemProperty | Where-Object { $_.DisplayName -match 'Azure IoT Edge *' } if ($null -eq $eflowInfo) { Write-Host "Azure IoT Edge is not installed." } else { Write-Host "$($eflowInfo.DisplayName) version $($eflowInfo.DisplayVersion) is installed. Removing..." Start-Process msiexec.exe -Wait -ArgumentList "/x $($eflowInfo.PSChildName) /quiet /noreboot" # Remove the module from Powershell session as well Remove-Module -Name AzureEFLOW -Force $eadSession.EFLOW.Product = $null $eadSession.EFLOW.Version = $null Write-Host "$($eflowInfo.DisplayName) successfully removed." } } function Get-EadEflowInstalledVersion { <# .DESCRIPTION Gets EFLOW version if installed #> $eflowInfo = Get-ChildItem -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' | Get-ItemProperty | Where-Object { $_.DisplayName -match 'Azure IoT Edge *' } $retval = $null if ($null -eq $eflowInfo) { Write-Host "Azure IoT Edge is not installed." } else { $retval = "$($eflowInfo.DisplayName),$($eflowInfo.DisplayVersion)" $eadSession.EFLOW.Version = $eflowInfo.DisplayVersion $eadSession.EFLOW.Product = $eflowInfo.DisplayName Write-Host "$retval is installed." } return $retval } function Invoke-EadEflowDeploy { <# .DESCRIPTION Loads the configuration and tries to deploy the EFLOW VM #> if (Test-EadEflowVmDeploy) { Write-Host "Error: Eflow VM already deployed" -Foreground red return $false } if (-not (Test-EadUserConfigDeploy)) { return $false } $eflowDeployParams = @{} $eflowConfig = Get-EadUserConfig #Properties are validated. So just add here $eflowDeployParams.Add("acceptEula", "Yes") if ($eflowConfig.enduser.acceptOptionalTelemetry -eq "Yes") { $eflowDeployParams.Add("acceptOptionalTelemetry", "Yes") } elseif ($eflowConfig.enduser.acceptOptionalTelemetry -eq "No") { $eflowDeployParams.Add("acceptOptionalTelemetry", "No") } if ($eflowConfig.network) { #network params are optional for Client using default switch $reqProperties = @("vswitchName", "vswitchType", "ip4Address", "ip4GatewayAddress", "ip4PrefixLength") foreach ($property in $($eflowConfig.network).PSObject.Properties) { if ($reqProperties -contains $property.Name) { $eflowDeployParams.Add($property.Name, $property.Value) } } } if ($eflowConfig.vmConfig) { #vmConfig params are optional foreach ($property in $($eflowConfig.vmConfig).PSObject.Properties) { $eflowDeployParams.Add($property.Name, $property.Value) } } Write-Verbose "EFLOW VM deployment parameters..." Write-Verbose ($eflowDeployParams | Out-String) #TODO - Check if eflow is deployed previously before attempting to deploy again. Write-Host "Starting EFLOW VM deployment..." $retval = Deploy-Eflow @eflowDeployParams if ($retval -ieq "OK") { Write-Host "* EFLOW VM deployment successfull." -ForegroundColor Green } else { Write-Host "Error: EFLOW VM deployment failed with the below error message." -ForegroundColor Red Write-Host "Error message : $retval." -ForegroundColor Red return $false } #Set-EflowVmDnsServers if ($eflowConfig.network.dnsServers) { Write-Host "Setting DNS Servers to $($eflowConfig.network.dnsServers)" Set-EflowVmDNSServers -dnsServers $eflowConfig.network.dnsServers } # If using Static IP and no DnsServer is provided, use main interface DNS servers elseif ($eflowConfig.network.vswitchType -eq "Internal") { # Get DNS servers including gateway Write-Host "Looking for available DNS Servers..." [String[]] $dnsservers = $(((Get-NetIPConfiguration | ForEach-Object IPv4DefaultGateway).NextHop)) $dnsservers += $(Get-DnsClientServerAddress | Where-Object { $_.AddressFamily -eq 2 } | ForEach-Object { $_.ServerAddresses }) $dnsservers = $dnsservers | Where-Object { -Not [string]::IsNullOrEmpty($_) } | Select-Object -uniq Write-Host "Setting DNS Servers to $dnsservers" Set-EflowVmDNSServers -dnsServers $dnsservers } #Set-EflowVmFeature if ($eflowConfig.vmFeature.DpsTpm) { Write-Host "Enabling vmFeature: DpsTpm" Set-EflowVmFeature -feature DpsTpm -enable } if ($eflowConfig.vmFeature.Defender) { Write-Host "Enabling vmFeature: Defender" Set-EflowVmFeature -feature Defender -enable } return $true } function Invoke-EadEflowProvision { <# .DESCRIPTION Loads the configuration and tries to provision the EFLOW VM #> if (-not (Test-EadEflowVmRun)) { Write-Host "Error: Eflow VM is not found" -ForegroundColor Red return $false } $retval = Test-EadUserConfigProvision if (-not $retval) { return $false } $eflowConfig = Get-EadUserConfig $provCfg = $eflowConfig.eflowProvisioning $eflowProvisionParams = @{ "provisioningType" = $provCfg.provisioningType } $reqSettings = $Script:eflowProvisioningProperties[$provCfg.provisioningType] if ($reqSettings) { #if we have valid parameter array foreach ($setting in $reqSettings) { #get values for each required setting. No validation here as its done earlier $eflowProvisionParams.Add($setting, $provCfg.$setting) } } if ($provCfg.globalEndpoint) { $eflowProvisionParams.Add("globalEndpoint", $provCfg.globalEndpoint) } Write-Verbose "--- EFLOW VM provisioning parameters..." #Making this Verbose as we possibly show secure key/connection string in logs Write-Verbose ($eflowProvisionParams | Out-String) Write-Host "Starting EFLOW VM provisioning..." $retval = Provision-EflowVm @eflowProvisionParams -headless if ($retval -ieq "OK") { Write-Host "* EFLOW provisioning successfull." -ForegroundColor Green Start-Sleep 60 #wait a minute to allow iotedge initialize } else { Write-Host "Error: EFLOW provisioning failed with the below error message." -ForegroundColor Red Write-Host "Error message : $retval." -ForegroundColor Red return $false } return $true } function Invoke-EadEflowProxyConfiguration { <# .DESCRIPTION Loads the proxy configuration and tries to set up proxy on the VM #> if (-not (Test-EadEflowVmRun)) { Write-Host "Error: Eflow VM is not found" -ForegroundColor Red return $false } $proxyParams = @{} if ($eflowConfig.network.useHostProxy) { $hostSettings = Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' | Select-Object ProxyServer, ProxyEnable if ($hostSettings.ProxyEnable) { $proxyParams.Add("httpsProxy", "$($hostSettings.ProxyServer)") $proxyParams.Add("httpProxy", "$($hostSettings.ProxyServer)") } } else { if ($eflowConfig.network.httpsProxy) { $proxyParams.Add("httpsProxy", "$($eflowConfig.network.httpsProxy)") } if ($eflowConfig.network.httpProxy) { $proxyParams.Add("httpProxy", "$($eflowConfig.network.httpProxy)") } if ($eflowConfig.network.ftpProxy) { $proxyParams.Add("ftpProxy", "$($eflowConfig.network.ftpProxy)") } } if ($proxyParams.Count) { Write-Host "Starting EFLOW VM Proxy configuration..." -ForegroundColor Green Set-EflowVmProxyServers @proxyParams } else { Write-Host "No proxy configuration found. Skipping EFLOW VM proxy configuration" -ForegroundColor Green } return $true } function Test-EadEflowProxyConfiguration { $proxyEnv = Invoke-EflowVmCommand 'echo $http_proxy $https_proxy' if ([string]::IsNullOrEmpty($proxyEnv)) { Write-Host "No EFLOW VM proxy configuration already applied" -ForegroundColor Green $retval = $false } else { Write-Host "Error: Eflow VM proxy already configured - Try manual configuration" - -ForegroundColor Red $retval = $true } return $retval } function Set-EflowVmProxyServers { # Configuration based on the following documents # https://learn.microsoft.com/en-us/azure/iot-edge/how-to-configure-proxy-support?view=iotedge-1.4 param ( [string] $httpProxy, [string] $httpsProxy, [string] $ftpProxy ) if ([string]::IsNullOrEmpty($httpProxy) -and [string]::IsNullOrEmpty($httpsProxy) -and [string]::IsNullOrEmpty($ftpProxy)) { Write-Host "Either -httpProxy or -httpsProxy or -ftpProxy must be provided." -ForegroundColor Red return } $restartIotEdge = $false if (![string]::IsNullOrEmpty($httpProxy) -or ![string]::IsNullOrEmpty($httpsProxy)) { # Create the ovverride conf files Invoke-EflowVmCommand "sudo mkdir -p /etc/systemd/system/aziot-edged.service.d/ && sudo touch /etc/systemd/system/aziot-edged.service.d/override.conf && printf '[Service]' | sudo tee /etc/systemd/system/aziot-edged.service.d/override.conf > /dev/null" Invoke-EflowVmCommand "sudo mkdir -p /etc/systemd/system/aziot-identityd.service.d/ && sudo touch /etc/systemd/system/aziot-identityd.service.d/override.conf && printf '[Service]' | sudo tee /etc/systemd/system/aziot-identityd.service.d/override.conf > /dev/null" Invoke-EflowVmCommand "sudo mkdir -p /etc/systemd/system/docker.service.d/ && sudo touch /etc/systemd/system/docker.service.d/http-proxy.conf && printf '[Service]' | sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf > /dev/null" Invoke-EflowVmCommand "sudo echo -e '\n[agent]\nname = \`"edgeAgent\`"\ntype = \`"docker\`"\n\n[agent.config]\nimage = \`"mcr.microsoft.com/azureiotedge-agent:latest\`"\ncreateOptions = { HostConfig = { Binds = [\`"/iotedge/storage:/iotedge/storage\`"] } }\n\n[agent.env]' | sudo tee -a /etc/aziot/config.toml > /dev/null" $restartIotEdge = $true } if (![string]::IsNullOrEmpty($httpProxy)) { Write-Host "Setting httpProxy configuration : $httpProxy" # Define the http_proxy environment variable Invoke-EflowVmCommand "sudo sed -i '/^export http_proxy/s.^.#.' /etc/bash.bashrc && echo export http_proxy=$httpProxy | sudo tee -a /etc/bash.bashrc > /dev/null && export http_proxy=$httpProxy" # Create overrride conf for the different aziot-edged service Invoke-EflowVmCommand "sudo printf '\nEnvironment=http_proxy=$httpProxy' | sudo tee -a /etc/systemd/system/aziot-edged.service.d/override.conf > /dev/null" Invoke-EflowVmCommand "sudo printf '\nEnvironment=http_proxy=$httpProxy' | sudo tee -a /etc/systemd/system/aziot-identityd.service.d/override.conf > /dev/null" Invoke-EflowVmCommand "sudo printf '\nEnvironment=http_proxy=$httpProxy' | sudo tee -a /etc/systemd/system/docker.service.d/http-proxy.conf > /dev/null" Invoke-EflowVmCommand "sudo echo -e '\`"http_proxy\`" = \`"$httpProxy\`"' | sudo tee -a /etc/aziot/config.toml > /dev/null" } if (![string]::IsNullOrEmpty($httpsProxy)) { Write-Host "Setting httpsProxy configuration : $httpsProxy" # Define the http_proxy environment variable Invoke-EflowVmCommand "sudo sed -i '/^export https_proxy/s.^.#.' /etc/bash.bashrc && echo export https_proxy=$httpsProxy | sudo tee -a /etc/bash.bashrc > /dev/null && export https_proxy=$httpsProxy" # Create overrride conf for the different aziot-edged service Invoke-EflowVmCommand "sudo printf '\nEnvironment=https_proxy=$httpsProxy' | sudo tee -a /etc/systemd/system/aziot-edged.service.d/override.conf > /dev/null" Invoke-EflowVmCommand "sudo printf '\nEnvironment=https_proxy=$httpsProxy' | sudo tee -a /etc/systemd/system/aziot-identityd.service.d/override.conf > /dev/null" Invoke-EflowVmCommand "sudo printf '\nEnvironment=https_proxy=$httpsProxy' | sudo tee -a /etc/systemd/system/docker.service.d/http-proxy.conf > /dev/null" Invoke-EflowVmCommand "sudo echo -e '\`"https_proxy\`" = \`"$httpsProxy\`"' | sudo tee -a /etc/aziot/config.toml > /dev/null" } if (![string]::IsNullOrEmpty($ftpProxy)) { Write-Host "Setting ftpProxy configuration : $ftpProxy" # Define the http_proxy environment variable Invoke-EflowVmCommand "sudo sed -i '/^export ftp_proxy/s.^.#.' /etc/bash.bashrc && echo export ftp_proxy=$ftpProxy | sudo tee -a /etc/bash.bashrc > /dev/null && export ftp_proxy=$ftpProxy" } if ($restartIotEdge) { Write-Host "Applying IoT Edge configuration and recreating EdgeAgent" Invoke-EflowVmCommand "sudo iotedge config apply && sudo docker rm -f edgeAgent" Write-Host "Restarting IoT Edge services" Invoke-EflowVmCommand "sudo systemctl daemon-reload && sudo iotedge system restart" } } function Test-EadEflowVmSwitch { Param ( [Switch] $Create ) $usrCfg = Get-EadUserConfig $nwCfg = $usrCfg.network if (! (Test-EadUserConfigNetwork)) { return $false } if ([string]::IsNullOrEmpty($nwCfg.vSwitchName)) { if (-not $eadSession.HostOS.IsServerSKU) { Write-Host "vSwitchName not specified. Checking Default switch." $defaultSwitch = Get-VMSwitch -Name 'Default Switch' -ErrorAction SilentlyContinue if ($defaultSwitch) { Write-Host "* Default switch found" -ForegroundColor Green $retval = $true } else { Write-Host "Error: Default switch not found" - -ForegroundColor Red $retval = $false } return $retval } Write-Host "Error: vSwitchName not specified. " -ForegroundColor Red return $false } # we have a name to check further.. $eflowSwitch = Get-VMSwitch -Name $nwCfg.vSwitchName -ErrorAction SilentlyContinue if ($eflowSwitch) { #Switch is found. If it is internal, grab the IP details to pass if ($nwCfg.vSwitchType -ieq "Internal") { # check if nat is present as well $nat = Get-NetNat -Name "$($nwCfg.vSwitchName)-NAT" if ($nat) { # Nat already exists, grab the ip info $eflowSwitchAdapter = Get-NetAdapter | Where-Object { $_.Name -eq "vEthernet ($($nwCfg.vSwitchName))" } $ifIndex = $eflowSwitchAdapter.ifIndex $switchIpAddress = (Get-NetIPAddress -AddressFamily IPv4 -InterfaceIndex $ifIndex).IPAddress $ipPrefix = ($switchIpAddress.Split('.')[0..2]) -join '.' Write-Host "* Internal Switch found with the ipPrefix $ipPrefix" if ($null -eq $nwCfg.ip4Address) { # No ip address specified. So update those fields with the generated ones. $nwCfg | Add-Member -MemberType NoteProperty -Name ip4GatewayAddress -Value $switchIpAddress $nwCfg | Add-Member -MemberType NoteProperty -Name ip4Address -Value "$ipPrefix.2" $nwCfg | Add-Member -MemberType NoteProperty -Name ip4PrefixLength -Value 24 Write-Host "Using EFLOW static IP4Address: $($nwCfg.ip4Address)" -ForegroundColor Green Write-Host " Gateway IP4Address: $($nwCfg.ip4GatewayAddress)" -ForegroundColor Green Write-Host " PrefixLength: $($nwCfg.ip4PrefixLength)" -ForegroundColor Green } } else { # No Nat. Not in a good state, so either we auto delete and recreate or report error and bail # bailing out for now Write-Host "Error: Internal switch found. NAT not found. Delete switch and recreate." -ForegroundColor Red return $false } } Write-Host "* Name:$($eflowSwitch.Name) - Type:$($eflowSwitch.SwitchType)" -ForegroundColor Green } else { # no switch found. Create if requested if ($Create) { return New-EadEflowVmSwitch } Write-Host "Error: VMSwitch $($nwCfg.vSwitchName) not found." -ForegroundColor Red return $false } return $true } function New-EadEflowVmSwitch { $usrCfg = Get-EadUserConfig $nwCfg = $usrCfg.network $eflowSwitch = Get-VMSwitch -Name $nwCfg.vSwitchName -ErrorAction SilentlyContinue if ($eflowSwitch) { Write-Host "Error: Name:$($eflowSwitch.Name) - Type:$($eflowSwitch.SwitchType) already exists" -ForegroundColor Red return $false } # no switch found. Create if requested Write-Host "Creating VMSwitch $($nwCfg.vSwitchName) - $($nwCfg.vSwitchType)..." if ($nwCfg.vSwitchType -ieq "Internal") { $eflowSwitch = New-VMSwitch -SwitchType $nwCfg.vSwitchType -Name $nwCfg.vSwitchName -ErrorAction SilentlyContinue # give some time for the switch creation to succeed Start-Sleep 10 $eflowSwitchAdapter = Get-NetAdapter -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq "vEthernet ($($nwCfg.vSwitchName))" } if ($null -eq $eflowSwitchAdapter) { Write-Host "Error: [vEthernet ($($nwCfg.vSwitchName))] is not found. $($nwCfg.vSwitchName) switch creation failed. Please try switch creation again." return $false } if ($null -eq $nwCfg.ip4Address) { # No ip address specified. So update those fields with the generated ones. $ifIndex = $eflowSwitchAdapter.ifIndex $switchIpAddress = (Get-NetIPAddress -AddressFamily IPv4 -InterfaceIndex $ifIndex -ErrorAction SilentlyContinue).IPAddress if (!$switchIpAddress) { Write-Host "Error: [vEthernet ($($nwCfg.vSwitchName))] IP address not found. IP assignment of the virtual switch $($nwCfg.vSwitchName) failed. Please try switch creation again." return $false } $ipPrefix = ($switchIpAddress.Split('.')[0..2]) -join '.' $nwCfg | Add-Member -MemberType NoteProperty -Name ip4GatewayAddress -Value "$ipPrefix.1" $nwCfg | Add-Member -MemberType NoteProperty -Name ip4Address -Value "$ipPrefix.2" $nwCfg | Add-Member -MemberType NoteProperty -Name ip4PrefixLength -Value 24 } $ipPrefix = ($nwCfg.ip4GatewayAddress.Split('.')[0..2]) -join '.' $natPrefix = "$ipPrefix.0/$($nwCfg.ip4PrefixLength)" New-NetIPAddress -IPAddress $nwCfg.ip4GatewayAddress -PrefixLength $nwCfg.ip4PrefixLength -InterfaceAlias "vEthernet ($($nwCfg.vSwitchName))" | Out-Null Write-Host "Using EFLOW static IP4Address: $($nwCfg.ip4Address)" -ForegroundColor Green Write-Host " Gateway IP4Address: $($nwCfg.ip4GatewayAddress)" -ForegroundColor Green Write-Host " PrefixLength: $($nwCfg.ip4PrefixLength)" -ForegroundColor Green New-NetNat -Name "$($nwCfg.vSwitchName)-NAT" -InternalIPInterfaceAddressPrefix $natPrefix | Out-Null } else { $nwadapters = (Get-NetAdapter -Physical -ErrorAction SilentlyContinue) | Where-Object { $_.Status -eq "Up" } if ($nwadapters.Name -notcontains ($nwCfg.adapterName)) { Write-Host "Error: $($nwCfg.adapterName) not found. External switch not created." -ForegroundColor Red return $false } $eflowSwitch = New-VMSwitch -NetAdapterName $nwCfg.adapterName -Name $nwCfg.vSwitchName -ErrorAction SilentlyContinue # give some time for the switch creation to succeed Start-Sleep 10 $eflowSwitchAdapter = Get-NetAdapter | Where-Object { $_.Name -eq "vEthernet ($($nwCfg.vSwitchName))" } if ($null -eq $eflowSwitchAdapter) { Write-Host "Error: [vEthernet ($($nwCfg.vSwitchName))] is not found. $($nwCfg.vSwitchName) switch creation failed. Please try switch creation again." return $false } } return $true } function Remove-EadEflowVmSwitch { $usrCfg = Get-EadUserConfig $switchName = $($usrCfg.network.vswitchName) $eflowSwitch = Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue if ($eflowSwitch) { Write-Host "Removing $switchName" Remove-VMSwitch -Name $switchName if ($eflowSwitch.SwitchType -ieq "Internal") { $eflowNat = Get-NetNat -Name "$switchName-NAT" if ($eflowNat) { Write-Host "Removing $switchName-NAT" Remove-NetNat -Name "$switchName-NAT" } } } } # Main function for full functional path function Start-EadWorkflow { Param ( [String]$eflowjson ) # Validate input parameter. Use default json in the same path if not specified if ([string]::IsNullOrEmpty($eflowjson)) { $eflowjson = "$PSScriptRoot\eflow-userconfig.json" } if (!(Test-Path -Path "$eflowjson" -PathType Leaf)) { Write-Host "Error: $eflowjson not found" -ForegroundColor Red return $false } $eflowjson = (Resolve-Path -Path $eflowjson).Path Set-EadUserConfig $eflowjson # validate later after creating the switch # Check admin role if (!(Test-AdminRole)) { return $false } # Check PC prequisites (Hyper-V, EFLOW and CLI) if (!(Test-HyperVStatus -Enable)) { return $false } # todo resume after reboot. Intune will retry. Arc to be checked if (!(Test-EadEflowInstall -Install)) { return $false } # Check if EFLOW is deployed already and bail out if (Test-EadEflowVmDeploy) { Write-Host "EFLOW VM is already deployed." -ForegroundColor Yellow } else { if (!(Test-EadEflowVmSwitch -Create)) { return $false } #create switch if specified # We are here.. all is good so far. Validate and deploy eflow if (!(Invoke-EadEflowDeploy)) { return $false } } if (! (Test-EadEflowVmProvision)) { # Validate and provision eflow if (!(Invoke-EadEflowProvision)) { return $false} } if (! (Test-EadEflowProxyConfiguration)) { # Validate and provision eflow if (!(Invoke-EadEflowProxyConfiguration)) { return $false} } if (Verify-EflowVm) { $eflowVM = Get-EflowVM Write-Host "$($eflowVM.VmConfiguration.name) $($eflowVM.VmPowerState)" Write-Host "IoTEdge Info:" $eflowVM.EdgeRuntimeVersion | Out-String Write-Host "EFLOW VM Info:" $eflowVM.SystemStatistics | Out-String } return $true } ### MAIN ### # Get Host PC information on loading of this script Get-HostPcInfo # If autodeploy switch is specified, start eflow deployment with the default json file path (.\eflow-userconfig.json) if ($AutoDeploy) { if (Start-EadWorkflow) { Write-Host "Deployment Successful" } else { Write-Error -Message "Deployment failed" -Category OperationStopped } } else { $eflowjson = "$PSScriptRoot\eflow-userconfig.json" if (Test-Path -Path "$eflowjson" -PathType Leaf) { Set-EadUserConfig $eflowjson } }