Skip to main content

Add an IoT Edge Device on Windows with EFLOW

Azure IoT Edge for Linux on Windows (EFLOW) runs a lightweight, Microsoft-maintained Linux virtual machine directly on Windows. The IoT Edge runtime and the Datalogic Connect modules run inside that VM, unlike the Ubuntu setup where they run natively on the host.

The installation script install-iot-edge.ps1 prepares the Windows host and the EFLOW VM: it creates the Hyper-V external switch, installs EFLOW, deploys the VM with a static IP address, provisions IoT Edge and registers the auto-start task. No manual module configuration is required. Broker, storage, certificate and telemetry settings are delivered to the device by the Connect deployment manifest when the device is enrolled.

Prerequisites

Before you begin

  1. Create an Edge Device in Device Visibility. The Serial Number you enter here becomes the Edge Device ID. The Device IP must be the static IP address you will assign to the EFLOW VM, because the ProvisioningModule issues the server certificate for that address.

  2. Check the hardware requirements before deploying EFLOW. The CPU, RAM and disk you assign to the VM are reserved for it and are no longer available to Windows, so make sure the host has enough capacity for the VM and its other workloads.

  3. Use Windows PowerShell as Administrator. The installation script requires PowerShell 5.1.

  4. Log on with the local account that will remain on the machine. The AzureEFLOW module only works from the account that deployed the VM, and the EFLOW Auto-Start self-repair task is registered under that account. See Configure auto-start and self-repair for the reason.

  5. Optional pre-check: verify that Hyper-V and hardware virtualization are available on the host. If Hyper-V is missing, the script can enable it and request a reboot.

  6. Open the device's Overview page in Connect. From here you download the installation script and, if you need it, the credentials file.

If this is your first time using Connect

Start with Authentication.

Provisioning values

The installer needs only the following three values. When you download the script from the device's Overview page, Connect embeds all three, so you normally do not have to supply them yourself.

ValueWhere it comes fromWhere it is used
Scope IDscope in the Get Credentials JSON exportDPS id_scope in /etc/aziot/config.toml
Primary KeyprimaryKey in the Get Credentials JSON exportDPS symmetric_key in /etc/aziot/config.toml
Edge Device IDThe Serial Number you entered when creating the device. It is not part of the export.DPS registration_id in /etc/aziot/config.toml

Supported OS

OSStatus
Windows 11✅ Verified
Windows 10 (version 1809, build 17763 or later)⚠️ Compatible, not tested
Windows Server 2025✅ Verified
Windows Server 2022⚠️ Compatible, not tested
OS compatibility

On Windows desktop editions, Hyper-V is not available in the Home edition. Use the Pro (recommended) or Enterprise edition. Windows Server supports Hyper-V through the Hyper-V server role.

ARM

ARM64 support is experimental in this EFLOW setup and has not been validated in our test matrix. Use it only for evaluation and verify compatibility in your own environment before production use.

Network requirements

The EFLOW VM must be able to reach the following outbound endpoints:

EndpointPortProtocolPurpose
*.azure-devices.net8883, 443, 5671MQTT TLS, AMQPS, or HTTPSSend diagnostic data and receive cloud commands (edgeHub upstream)
global.azure-devices-provisioning.net443HTTPSDevice provisioning
*.blob.core.windows.net443HTTPSFile upload and Docker image pull
crsolinfraprodeuw.azurecr.io443HTTPSContainer registry
crsolinfraprodeuw.westeurope.data.azurecr.io443HTTPSContainer registry (data endpoint)
*.azureiotcentral.com443HTTPSDevice Smart Enrollment
Firewall configuration

If the host is behind a corporate firewall, allow all the outbound endpoints above. The IoT Edge runtime selects the best available upstream protocol, MQTT TLS on port 8883 or HTTPS on port 443. Field devices also need to reach the VM on the local network: see Configure inbound firewall rules for secure mobile enrollment and the full list in Network Requirements.


Quick install

Use this path for the automated setup with a static IP address.

1. Download the installation archive

Open the device's Overview page in Connect, click Install script and choose Windows. The archive contains install-iot-edge.ps1, its SHA-256 checksum and a README.

Extract the archive into a folder on the Windows host and verify the checksum:

(Get-FileHash .\install-iot-edge.ps1 -Algorithm SHA256).Hash.ToLower()
Get-Content .\install-iot-edge.ps1.sha256

The two hashes must match.

2. Open Windows PowerShell as Administrator

Open Windows PowerShell as Administrator with the account described in Before you begin, then change to the folder that contains the script. The script will automatically set Windows PowerShell 5.1 version if it is not already in use.

3. Set the execution policy

The following setting applies to the current session only:

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force

4. Run the installer

The script downloaded from Connect already contains the Scope ID, the Edge Device ID and the Primary Key of the device. Pass the network settings and the VM size:

.\install-iot-edge.ps1 `
-EflowIp "192.168.1.50" `
-PrefixLength 24 `
-GatewayIp "192.168.1.1" `
-DnsServer @("192.168.1.1") `
-AdapterName "Ethernet" `
-CpuCount 2 `
-MemoryInMB 4096 `
-VmDataSize 50

The values above are examples. -EflowIp must be the same address you entered as Device IP when creating the device. Every network parameter you omit is detected from the host adapter or asked interactively. See the parameter table for the full list.

The script runs five phases in sequence: create the Hyper-V external switch, install EFLOW, deploy the VM, configure IoT Edge inside the VM and register the auto-start task. If Hyper-V is enabled during the run, the script exits and asks for a reboot. After the reboot, run the same command again: the script is resumable and skips the phases already completed.

Cleanup inside the VM

The configuration phase stops IoT Edge, removes the module containers and clears /var/srv inside the EFLOW VM before recreating the volume directories. It does not ask for confirmation. Do not run the script on a VM that holds data you need under that path.

Supplying the values yourself

If the script does not contain the provisioning values, for example because you obtained it from another source, you can supply them in two ways.

  • Credentials file next to the script. Download the Get Credentials JSON export from the device's Overview page, save it as edge-credentials.json in the same folder as the script and pass the serial number with -DeviceId. The script detects the file and reads the Scope ID and the Primary Key from it.

  • All three values on the command line. Pass -ScopeId, -DeviceId and -PrimaryKey together with the network parameters.

Any value not supplied is asked interactively. Command-line values take precedence over the credentials file, which takes precedence over the configuration saved by a previous run in C:\EFLOW\install-edge.env.

Parameter table

The installation script accepts the following parameters. Run Get-Help .\install-iot-edge.ps1 -Full to print the same list.

ParameterDescriptionNotes
-PhasePhase to run: All, CreateSwitch, InstallEflow, DeployVm, ConfigureVm or SetupAutoStartDefault All. Use a single phase to repeat one step, for example -Phase SetupAutoStart.
-CredentialsFilePath to the Get Credentials JSON export. Supplies the Scope ID and the Primary Key.If omitted, edge-credentials.json next to the script is used.
-ScopeIdDPS ID Scope.Overrides the value read from the credentials file.
-DeviceIdEdge Device ID, that is the Serial Number of the device.Not part of the credentials export. -SerialNumber and -EdgeDeviceId are aliases.
-PrimaryKeyDPS symmetric primary key.Overrides the value read from the credentials file.
-AdapterNameName of the physical network adapter bound to the external switch.If omitted, the script lists the active adapters and asks you to choose.
-EflowIpStatic IPv4 address of the EFLOW VM.Must be in the same subnet as the gateway, unused, and equal to the Device IP set in Connect.
-GatewayIpDefault gateway IPv4 address.Detected from the host if omitted. -Ip4GatewayAddress is an alias.
-PrefixLengthIPv4 subnet prefix length.Range 1 to 32. Detected from the host if omitted.
-DnsServerOne or two DNS server IPv4 addresses.Example: @("DNS_1", "DNS_2"). Uses the host DNS servers if omitted, then the gateway.
-CpuCountNumber of CPU cores assigned to the VM.Range 1 to 8. Default 2.
-MemoryInMBRAM assigned to the VM, in MB.Range 2048 to 32768. Default 4096.
-VmDataSizeSize of the EFLOW VM data disk, in GB.Range 16 to 200. Default 50. Can be expanded later.
-ConfigFilePath to the configuration saved by a previous run.Default C:\EFLOW\install-edge.env. Contains the network settings, the VM size and the provisioning values.
EFLOW disk size

-VmDataSize defines the EFLOW data disk quota, not the total VHDX capacity shown by Hyper-V. In Hyper-V, Get-VHD may report a larger Size, for example around 68 GB for -VmDataSize 50, because EFLOW adds its own system partitions. FileSize is the space currently consumed on the host disk, which is often lower than Size because the VHDX is dynamic.

5. Add the inbound firewall rules

The script does not change the Windows or Hyper-V firewall rules. Add the inbound firewall rules for secure mobile enrollment before you enroll mobile devices with an enrollment token.

6. Enroll the device

When the script finishes, wait a few minutes before enrolling the edge device in the Connect portal. IoT Edge must complete the DPS provisioning and start edgeAgent before it can receive the deployment manifest. Enrolling too early can leave the modules unconfigured.

Check that edgeAgent is running before you enroll. Open a shell inside the VM:

Connect-EflowVm

Then, inside the VM:

sudo iotedge list

After the enrollment, follow Verify the installation.


Configure inbound firewall rules for secure mobile enrollment

The EFLOW setup uses an external Hyper-V switch and gives the VM its own static IP address. Traffic from mobile devices reaches the VM through its Hyper-V port, not through the Windows host address, so the rules must be Hyper-V firewall rules.

PortServicePurpose
8098ProvisioningModuleDevice certificate provisioning
9883MQTT over TLSAuthenticated mobile connection
Required for token-based enrollment

Without these rules the mobile device cannot reach the ProvisioningModule. Enrollment with an enrollment token fails, while enrollment without a token still succeeds on port 1883.

Run the following commands in Windows PowerShell as Administrator:

New-NetFirewallHyperVRule -Name 'DatalogicConnect-EFLOW-Provisioning-8098' -DisplayName 'Datalogic Connect EFLOW Provisioning (8098)' -Direction Inbound -Protocol TCP -LocalPorts 8098
New-NetFirewallHyperVRule -Name 'DatalogicConnect-EFLOW-MqttTls-9883' -DisplayName 'Datalogic Connect EFLOW MQTT TLS (9883)' -Direction Inbound -Protocol TCP -LocalPorts 9883

Manual procedure

Use this path when you want full control over each step. It produces the same networking model as the installation script: an external switch and a static IP address. The last step still uses the script, because the auto-start task must not be written by hand.

Commands marked on the host run in Windows PowerShell 5.1 as Administrator. Commands marked inside the VM run in the shell opened by Connect-EflowVm.

1. Install Hyper-V

On the host, open Turn Windows features on or off, select the whole Hyper-V feature with its sub-components and confirm. A reboot is required. On Windows Server, install the Hyper-V role instead.

2. Create the external virtual switch

On the host, set the execution policy for the current session:

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force

List the physical adapters, then create the switch on the one connected to your network:

Get-NetAdapter -Physical | Select-Object Name, InterfaceDescription, Status
$adapterName = "Ethernet" # Replace with the adapter name from the output above
New-VMSwitch -Name "vEflowSwitch" -NetAdapterName $adapterName -AllowManagementOS $true
Get-VMSwitch -Name "vEflowSwitch"

3. Install EFLOW

On the host, download and install the EFLOW 1.5 LTS package for your CPU architecture, then import the module:

$msiPath = [io.Path]::Combine($env:TEMP, 'AzureIoTEdge.msi')
$ProgressPreference = 'SilentlyContinue'
$msiUrl = "https://aka.ms/AzEFLOWMSI_1_5_LTS_X64" # Use AzEFLOWMSI_1_5_LTS_ARM64 on ARM64 hosts
Invoke-WebRequest $msiUrl -OutFile $msiPath
Start-Process -Wait msiexec -ArgumentList "/i", "$msiPath", "/qn"
Import-Module AzureEFLOW -WarningAction SilentlyContinue

4. Plan the static network configuration

On the host, read the current network configuration and choose an unused IPv4 address in the same subnet for the VM. The address must be the Device IP you entered in Connect.

Get-NetIPConfiguration | Where-Object { $_.IPv4DefaultGateway -ne $null -and $_.NetAdapter.Status -eq 'Up' }

5. Deploy the EFLOW VM

On the host, deploy the VM on the external switch with the static address. Replace the placeholders with your values and size the VM according to the hardware requirements.

Deploy-Eflow `
-vSwitchName "vEflowSwitch" `
-vSwitchType "External" `
-ip4Address "<EFLOW_IP>" `
-ip4GatewayAddress "<GATEWAY_IP>" `
-ip4PrefixLength 24 `
-cpuCount 2 `
-memoryInMB 4096 `
-vmDataSize 50 `
-acceptEula Yes `
-acceptOptionalTelemetry No

Then set the DNS servers of the VM:

Set-EflowVmDnsServers -dnsServers @("<DNS_IP>")

6. Create the volume directories

Inside the VM, /var/srv is the only persistent path. The deployment manifest binds the module volumes under /srv, which is a bind mount of /var/srv. Open a shell in the VM:

Connect-EflowVm

Inside the VM, create the directories and set the ownership expected by the user account each container runs as:

sudo mkdir -p \
/var/srv/redis \
/var/srv/MqttTranslationModule/log /var/srv/MqttTranslationModule/certs \
/var/srv/DeviceHubModule/log /var/srv/DeviceHubModule/certs \
/var/srv/StorageModule/log \
/var/srv/ProvisioningModule/log \
/var/srv/shared/download /var/srv/shared/certs /var/srv/shared/certs/server \
/var/srv/edgeAgent /var/srv/edgeHub

sudo chown 1000 \
/var/srv/redis \
/var/srv/MqttTranslationModule/log /var/srv/MqttTranslationModule/certs \
/var/srv/DeviceHubModule/log /var/srv/DeviceHubModule/certs \
/var/srv/StorageModule/log \
/var/srv/ProvisioningModule/log \
/var/srv/shared/download \
/var/srv/edgeAgent /var/srv/edgeHub

sudo chown 1001 /var/srv/shared/certs /var/srv/shared/certs/server
sudo chmod 755 /var/srv/shared/certs /var/srv/shared/certs/server

Apply the bind mount for the current boot:

sudo mkdir -p /srv
sudo mount --bind /var/srv /srv
UIDDirectoriesUsed by
1000redis, MqttTranslationModule/*, DeviceHubModule/*, StorageModule/log, ProvisioningModule/log, shared/download, edgeAgent, edgeHub, all under /var/srvGateway modules, Redis, edgeAgent and edgeHub storage
1001/var/srv/shared/certs, /var/srv/shared/certs/serverShared certificates, written by ProvisioningModule and read by nanomq
Manifest coverage

This procedure and the installation script create the directories of the base edge gateway manifest. The Smart Vision Suite for Retail manifest (ROGER), which also needs the local blob storage directories, is not covered on EFLOW.

7. Configure the Docker log policy

Inside the VM, write the Docker daemon configuration with the local logging driver and an explicit log rolling policy, then restart Docker:

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
"log-driver": "local",
"log-opts": { "max-size": "10m", "max-file": "5" }
}
EOF
sudo systemctl restart docker
sudo docker info --format '{{.LoggingDriver}}'

The last command must print local.

8. Configure IoT Edge provisioning

Inside the VM, write the IoT Edge configuration file. Replace <SCOPE_ID>, <SERIAL_NUMBER> and <PRIMARY_KEY> with your provisioning values.

sudo tee /etc/aziot/config.toml > /dev/null <<'EOF'
[provisioning]
source = "dps"
global_endpoint = "https://global.azure-devices-provisioning.net"
id_scope = "<SCOPE_ID>"

[provisioning.attestation]
method = "symmetric_key"
registration_id = "<SERIAL_NUMBER>"
symmetric_key = { value = "<PRIMARY_KEY>" }

[image_garbage_collection]
enabled = true
cleanup_recurrence = "1d"
image_age_cleanup_threshold = "7d"
cleanup_time = "00:00"

[agent]
name = "edgeAgent"
type = "docker"

[agent.config]
image = "crsolinfraprodeuw.azurecr.io/azureiotedge-agent:1.5.40"
EOF
sudo chmod 600 /etc/aziot/config.toml
sudo iotedge config apply

Wait until sudo iotedge list reports edgeAgent as running, then type exit to return to the host.

9. Configure auto-start and self-repair

The bind mount of /var/srv on /srv is lost at every VM boot. When the modules start on an empty /srv, they all exit with code 255.

Persistence is handled from the Windows host by the EFLOW Auto-Start scheduled task. The task re-applies the bind mount and restarts IoT Edge only when edgeAgent is not running. It runs at Windows startup and every 10 minutes, so it also covers Windows Fast Startup, which does not fire startup triggers, and restarts of the VM alone.

Do not create the task manually. On the host, run the dedicated phase of the installation script from the same account you used to deploy EFLOW. The phase installs C:\EFLOW\startup.ps1, registers the task under your account and runs it once as a smoke test:

.\install-iot-edge.ps1 -Phase SetupAutoStart

The output must end with Smoke test passed (LastTaskResult = 0). Any other result means the self-repair will not work. Inspect C:\EFLOW\logs\startup.log before continuing.

The task must run under your account, not SYSTEM

The AzureEFLOW PowerShell module resolves the VM through the context of the user who deployed it. Under SYSTEM every call fails with Virtual machine name could not be retrieved, and the task exits before reaching the repair. The phase registers the task with the S4U logon type, which runs it whether or not you are logged on, without storing a password.

Why persistence is not configured inside the VM

The EFLOW VM is an image-based Azure Linux with a read-only verity root and a Trident /etc overlay. The fstab generator and the unit loader run before that overlay is assembled, so an fstab entry, a .mount unit or a docker.service drop-in written to /etc are not seen at boot. They appear only after systemctl daemon-reload. Files that services read at runtime, such as /etc/aziot/config.toml and /etc/docker/daemon.json, work normally. Persistence therefore has to come from outside the VM.

10. Verify the host setup

On the host:

Test-NetConnection -ComputerName <EFLOW_IP> -Port 1883
Get-ScheduledTaskInfo -TaskName 'EFLOW Auto-Start' | Select-Object LastRunTime, LastTaskResult

LastTaskResult must be 0 and LastRunTime must be recent. Then add the inbound firewall rules, enroll the edge device in the Connect portal and follow Verify the installation.

Finally, reboot Windows once and confirm, without any manual intervention, that every module is running again within a few minutes.


Verify the installation

After the enrollment, Connect applies the deployment manifest and the modules are pulled. Wait at least two minutes, then open a shell in the VM:

Connect-EflowVm

Inside the VM, list the IoT Edge modules:

sudo iotedge list

All modules should reach the running state:

NAME                   STATUS      DESCRIPTION      CONFIG
DeviceHubModule running Up 2 hours crsolinfraprodeuw.azurecr.io/device-hub-module:2.0.11-amd64.debug
MqttTranslationModule running Up 2 hours crsolinfraprodeuw.azurecr.io/mqtt-translation-module:1.0.0-amd64.debug
ProvisioningModule running Up 2 hours crsolinfraprodeuw.azurecr.io/provisioning-module:1.0.1-amd64.debug
StorageModule running Up 2 hours crsolinfraprodeuw.azurecr.io/storage-module:1.1.1-amd64.debug
edgeAgent running Up 2 hours crsolinfraprodeuw.azurecr.io/azureiotedge-agent:1.5.40
edgeHub running Up 2 hours crsolinfraprodeuw.azurecr.io/azureiotedge-hub:1.5.40
nanomq running Up 2 hours crsolinfraprodeuw.azurecr.io/nanomq:0.24.14-slim
otelcollector running Up 2 hours crsolinfraprodeuw.azurecr.io/opentelemetry-collector-contrib:0.155.0
redis running Up 2 hours crsolinfraprodeuw.azurecr.io/redis:8.8.0-alpine
Module set

The module set and the image tags come from the manifest selected as Active manifest when the device was created. The listing above belongs to the base edge gateway manifest.

If a module stays in failed or keeps restarting, inspect it:

sudo iotedge logs <module-name>

The installation log is available on the host at C:\EFLOW\logs\setup.log.


Troubleshooting

Mobile enrollment fails with a token but succeeds without one

Enrollment without a token uses plain MQTT on port 1883. Enrollment with a token also needs port 8098 for the ProvisioningModule, which issues the certificate, and then port 9883 for MQTT over mutual TLS. From a machine on the mobile device's network, test the three ports:

1883,8098,9883 | ForEach-Object {
"{0}`t{1}" -f $_, (Test-NetConnection <EFLOW_IP> -Port $_ -WarningAction SilentlyContinue).TcpTestSucceeded
}

If 1883 succeeds and 8098 or 9883 fail, check the following in order:

  1. The inbound firewall rules are present on the Windows host.
  2. The modules are running. Run Connect-EflowVm, then sudo iotedge list inside the VM. If sudo docker ps -a shows every container as Exited, see the next section.
  3. The Device IP of the edge device in Connect matches the static IP address of the EFLOW VM. A different value makes the ProvisioningModule issue a server certificate for the wrong address, and the mobile device fails with SSLPeerUnverifiedException.

All module containers are in Exited state after a reboot

Inside the VM, sudo iotedge system status reports the host services as running, but sudo docker ps -a shows every module exited with code 255. The modules started before /srv was bind-mounted, so each one found its configuration directory empty.

The EFLOW Auto-Start task should repair this within a few minutes of boot. If it did not, check the task first: the task is the mechanism, the missing mount is only the symptom. On the host:

Get-ScheduledTaskInfo -TaskName 'EFLOW Auto-Start' | Select-Object LastRunTime, LastTaskResult
Get-Content C:\EFLOW\logs\startup.log -Tail 30
What you seeMeaningFix
Task not found, or LastRunTime older than the last bootTask missing or not firingRun .\install-iot-edge.ps1 -Phase SetupAutoStart
LastTaskResult is not 0 and the log stops at Virtual machine name could not be retrievedTask registered as SYSTEM by an installer earlier than 2.2.0Run .\install-iot-edge.ps1 -Phase SetupAutoStart from the account that deployed EFLOW
LastTaskResult is not 0 and the log says the VM did not answer commandsVM not reachable from the task's accountConnect-EflowVm must work from that account. Check the EFLOW module and the VM state with Get-VM
LastTaskResult is 0 but the log has no line for the last runLog not writableLook in %TEMP%\eflow-startup-fallback.log of the task's account

While you fix the task, recover the modules immediately. Inside the VM:

sudo mount --bind /var/srv /srv
sudo iotedge system restart

For the reason why persistence cannot be configured inside the VM, see Configure auto-start and self-repair.


Resize the EFLOW VM disk

If you need more disk space after the initial deployment, for logs, container images or data, you can expand the VM disk.

Disk size constraints

Only expansion is supported. The disk cannot be shrunk. The VM must be stopped during the resize, so expect about 2 to 3 minutes of downtime.

  1. On the host, stop the EFLOW VM:

    Stop-EflowVm
  2. On the host, find the VHDX and resize it. Replace <NEW_SIZE> with the target size, for example 100GB:

    $vm = Get-VM -Name 'EFLOW*'
    $vhdx = ($vm.HardDrives | Where-Object { $_.Path -like '*EFLOW*.vhdx' }).Path
    Resize-VHD -Path $vhdx -SizeBytes <NEW_SIZE>
  3. On the host, start the VM and open a shell in it:

    Start-EflowVm
    Connect-EflowVm
  4. Inside the VM, expand the partition and the file system, then check the result:

    sudo growpart /dev/sda 2
    sudo resize2fs /dev/sda2
    df -h /
    exit

Uninstall EFLOW

The procedure below returns the host to the state it had before the installation script ran. Run all the commands on the host, in Windows PowerShell 5.1 as Administrator, in the order shown. Everything written inside the VM (IoT Edge configuration, module containers, /var/srv data) disappears together with the VM. The Hyper-V feature is left enabled.

1. Stop and remove the scheduled tasks

Remove the tasks first. EFLOW Auto-Start runs every 10 minutes and would otherwise restart the VM while you are deleting it.

foreach ($taskName in @('EFLOW Auto-Start', 'EFLOW-Rotate-Startup-Log')) {
$task = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if ($task) {
if ($task.State -eq 'Running') { Stop-ScheduledTask -TaskName $taskName }
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
}
}

EFLOW-Rotate-Startup-Log is the companion task that rotates C:\EFLOW\logs\startup.log. Both tasks are registered by the SetupAutoStart phase.

2. Remove the EFLOW VM

Stop the VM through the AzureEFLOW module when it is available, then delete it from Hyper-V together with its disks:

Import-Module AzureEFLOW -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
if (Get-Command Stop-EflowVm -ErrorAction SilentlyContinue) { Stop-EflowVm }

foreach ($vm in @(Get-VM -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '(?i)EFLOW' })) {
$disks = @(Get-VMHardDiskDrive -VMName $vm.Name -ErrorAction SilentlyContinue | ForEach-Object { $_.Path })
if ($vm.State -ne 'Off') { Stop-VM -Name $vm.Name -Force -TurnOff -ErrorAction SilentlyContinue }
Remove-VM -Name $vm.Name -Force
foreach ($disk in $disks) { Remove-Item -LiteralPath $disk -Force -ErrorAction SilentlyContinue }
}
There is no Remove-EflowVm cmdlet

The AzureEFLOW module cannot delete the VM, so the removal goes through Hyper-V. EFLOW names the VM <hostname>-EFLOW, which is why the filter matches on EFLOW anywhere in the name. Remove-VM deletes only the VM configuration, not its disks: the VHDX paths are collected before the VM is removed and deleted afterwards. A leftover VHDX makes the next Deploy-Eflow fail.

3. Uninstall the Azure IoT Edge package

Locate the EFLOW MSI through the Windows Installer registry keys and remove it with msiexec. This is more reliable than Get-Package on Windows PowerShell 5.1:

$roots = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
)
$products = Get-ChildItem $roots -ErrorAction SilentlyContinue |
Where-Object { $_.GetValue('DisplayName') -match 'Azure IoT Edge' -and $_.PSChildName -match '^\{[0-9A-Fa-f-]{36}\}$' }

foreach ($product in $products) {
$proc = Start-Process msiexec.exe -ArgumentList "/x $($product.PSChildName) /qn /norestart" -Wait -PassThru
"$($product.GetValue('DisplayName')): exit code $($proc.ExitCode)"
}
Exit codeMeaning
0Uninstalled
1605The product was already uninstalled
3010Uninstalled, a reboot is required to complete the removal
OtherUninstall failed. Retry from Apps & Features or after a reboot

If no product is listed, fall back to PackageManagement:

Get-Package -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'Azure IoT Edge' } | Uninstall-Package -Force

Then delete the installation folder if it survived the uninstall. Skip this when the exit code was 3010: Windows Installer removes the folder after the reboot.

Remove-Item -Recurse -Force "C:\Program Files\Azure IoT Edge" -ErrorAction SilentlyContinue
note

The installation script treats a present LICENSE folder under C:\Program Files\Azure IoT Edge as a sign of a healthy install. A stale folder would confuse the next run.

4. Remove the working folder

Remove-Item -Recurse -Force (Join-Path $env:SystemDrive 'EFLOW') -ErrorAction SilentlyContinue

The folder holds install-edge.env, startup.ps1, rotate-startup-log.ps1 and the startup logs.

5. Remove the Event Log source and the temporary files

The auto-start task writes to the Application log under the EFLOW Startup source. The installer leaves the downloaded MSI and a fallback log in TEMP:

if (Test-Path 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\EFLOW Startup') {
Remove-EventLog -Source 'EFLOW Startup'
}
Remove-Item (Join-Path $env:TEMP 'AzureIoTEdge.msi') -Force -ErrorAction SilentlyContinue
Remove-Item (Join-Path $env:TEMP 'eflow-startup-fallback.log') -Force -ErrorAction SilentlyContinue

6. Remove the Hyper-V switch

Skip this step if you plan to reinstall EFLOW on the same host, or if other VMs use the switch.

if (Get-VMSwitch -Name 'vEflowSwitch' -ErrorAction SilentlyContinue) {
Remove-VMSwitch -Name 'vEflowSwitch' -Force
}
Brief network interruption

Removing the external switch moves the IP configuration back to the physical adapter. Expect the host to lose connectivity for a few seconds. Do not run this step over a remote session that depends on that adapter unless you can recover the host locally.

If step 3 returned 3010, reboot the host before running the installation script again.