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. Unlike general-purpose VM solutions, EFLOW is purpose-built for IoT Edge workloads and integrates natively with Windows networking and Hyper-V.
Prerequisites
Before you begin, you need:
-
Create an IoT Edge device in the Device Visibility section.
-
Go to the Device Overview page. Here, you can find the Serial Number and the other required parameters clicking on the Get Credentials button.
Network Requirements
To ensure proper communication between your IoT Edge device and Datalogic Connect services, make sure the following network endpoints are accessible:
| Endpoint | Port | Protocol | Notes |
|---|---|---|---|
*.azure-devices.net | 8883, 443 | MQTT TLS, HTTPS | Send diagnostic data, receive cloud commands (edgeHub upstream) |
global.azure-devices-provisioning.net | 443 | HTTPS | Device provisioning |
*.blob.core.windows.net | 443 | HTTPS | File upload, Docker image pull |
crsolinfraprodeuw.azurecr.io | 443 | HTTPS | Container registry |
crsolinfraprodeuw.westeurope.data.azurecr.io | 443 | HTTPS | Container registry (data endpoint) |
*.azureiotcentral.com | 443 | HTTPS | Device Smart Enrollment |
If your device is behind a corporate firewall, ensure all outbound endpoints above are whitelisted. The IoT Edge runtime will automatically select the best available upstream protocol (MQTT TLS on 8883 or HTTPS on 443).
Local Inbound Ports (Field Devices → Edge Device)
The following ports must be reachable from Datalogic field devices or AladdinSDS on the local network:
| Port | Protocol | Service | Used by |
|---|---|---|---|
1883 | MQTT (plain TCP) | Mosquitto | PCs, POS systems, HOST-type devices |
9883 | MQTT over TLS (mTLS) | Mosquitto | Datalogic scanner devices with client certificates |
8090 | HTTP | StorageModule | Field devices downloading firmware/config or uploading logs |
8098 | HTTP | ProvisioningModule | Field devices requesting X.509 certificates |
Windows Requirements
- Windows 10 (build 17763 / version 1809 or later, with all cumulative updates installed) or Windows 11 — Pro, Enterprise, or IoT Enterprise edition.
- Windows Server 2019 or 2022 is also supported.
- Hyper-V enabled — see the Microsoft Hyper-V installation guide. On Windows Server you must also create a default virtual switch manually.
- Minimum 1 GB free RAM and 10 GB free disk space (4 GB RAM and 16 GB disk recommended for production workloads).
- All PowerShell commands below must be run in an elevated (Administrator) PowerShell session.
Add an IoT Edge Device on Windows with EFLOW
1. Install EFLOW
Open an elevated PowerShell session and verify that the PowerShell execution policy allows the EFLOW module to run:
Get-ExecutionPolicy -List
If the LocalMachine scope is not set to AllSigned, set it now:
Set-ExecutionPolicy -ExecutionPolicy AllSigned -Force
Download the EFLOW MSI. Choose the command that matches your device architecture:
X64 / AMD64:
$msiPath = $([io.Path]::Combine($env:TEMP, 'AzureIoTEdge.msi'))
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest "https://aka.ms/AzEFLOWMSI_1_5_LTS_X64" -OutFile $msiPath
ARM64:
$msiPath = $([io.Path]::Combine($env:TEMP, 'AzureIoTEdge.msi'))
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest "https://aka.ms/AzEFLOWMSI_1_5_LTS_ARM64" -OutFile $msiPath
Install the MSI:
Start-Process -Wait msiexec -ArgumentList "/i","$([io.Path]::Combine($env:TEMP, 'AzureIoTEdge.msi'))","/qn"
By default, EFLOW installs to C:\Program Files\Azure IoT Edge and stores the VM disk under the same location. To use a different drive, append INSTALLDIR and VHDXDIR parameters. For example:
Start-Process -Wait msiexec -ArgumentList "/i","$([io.Path]::Combine($env:TEMP, 'AzureIoTEdge.msi'))","/qn","INSTALLDIR=D:\EFLOW","VHDXDIR=D:\EFLOW-VHDX"
After the installation completes, import the EFLOW PowerShell module:
Import-Module AzureEFLOW -WarningAction SilentlyContinue
The -WarningAction SilentlyContinue flag suppresses a cosmetic warning about unapproved PowerShell verb names that the EFLOW module triggers. It does not affect functionality.
2. Deploy the EFLOW Virtual Machine
Deploy using the Windows Default Switch. This is the universally supported option — it works on all Windows editions and with any network adapter, including Wi-Fi.
Deploy-Eflow `
-cpuCount 2 -memoryInMB 2048 -vmDataSize 16 `
-acceptEula Yes -acceptOptionalTelemetry No
The -acceptEula Yes -acceptOptionalTelemetry No flags skip the two interactive prompts so the command runs unattended.
Wait until the PowerShell window reports Deployment successful. The EFLOW VM is a dedicated, Microsoft-managed Linux environment with the IoT Edge runtime pre-installed.
The Default Switch is an ICS-type internal network. The EFLOW VM receives a dynamic IP that may change after every reboot. The startup script configured in Step 12 automatically detects the new IP, re-applies the port proxy rules, and restarts AladdinSDS on every boot.
Deploy-Eflow does not expose a cloudInitFilePath parameter — EFLOW does not support passing a cloud-init file at deployment time. All post-deployment configuration is performed via Invoke-EflowVmCommand and Copy-EflowVmFile from the PowerShell session, as shown in the following steps.
3. Get the EFLOW VM IP Address
Retrieve the current IP address assigned to the EFLOW VM by the Default Switch:
$output = Get-EflowVmAddr *>&1 | Out-String
$eflowIp = if ($output -match '(\d+\.\d+\.\d+\.\d+)') { $Matches[1] } else { $null }
Write-Host "EFLOW current IP: $eflowIp"
Keep this PowerShell session open throughout the tutorial. $eflowIp is used in Step 11 to configure the initial port proxy rules.
4. Create Required Directories
Connect to the EFLOW VM and create the directories required by the edge modules:
Connect-EflowVm
The EFLOW VM root filesystem (/) is read-only (dm-verity protected). Only the data partition mounted at /var is writable. Because the IoT Edge modules expect paths under /srv, create the directories under /var/srv and bind mount /var/srv to /srv. This bind mount does not persist across VM restarts — the Windows startup script configured in the Network Configuration section re-applies it automatically on every boot.
Once inside the EFLOW VM shell, run:
# Create directories in the writable data partition
sudo mkdir -p /var/srv/redis /var/srv/mosquitto/log /var/srv/mosquitto/config /var/srv/mosquitto/config/certs /var/srv/MqttTranslationModule/log /var/srv/MqttTranslationModule/certs /var/srv/DeviceHubModule/log /var/srv/DeviceHubModule/certs /var/srv/DeviceHubModule/config /var/srv/StorageModule/log /var/srv/StorageModule/config /var/srv/ProvisioningModule/log /var/srv/ProvisioningModule/config /tmp/edgeAgent /tmp/edgeHub /var/srv/shared/download /var/srv/shared/certs /var/srv/shared/certs/server /var/srv/otel
# Set ownership and permissions
sudo chown 1000 /var/srv/redis /var/srv/mosquitto/log /var/srv/mosquitto/config /var/srv/mosquitto/config/certs /var/srv/MqttTranslationModule/log /var/srv/MqttTranslationModule/certs /var/srv/DeviceHubModule/log /var/srv/DeviceHubModule/certs /var/srv/DeviceHubModule/config /var/srv/StorageModule/log /var/srv/StorageModule/config /var/srv/ProvisioningModule/log /var/srv/ProvisioningModule/config /var/srv/shared/download /var/srv/otel
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 now (for the initial setup session)
sudo mount --bind /var/srv /srv
# Verify the mount is active
mount | grep srv
5. Configure Mosquitto MQTT Broker
Still inside the EFLOW VM shell, create the Mosquitto configuration file:
sudo nano /srv/mosquitto/config/mosquitto.conf
Paste the following configuration:
per_listener_settings true
listener 1883
protocol mqtt
allow_anonymous true
require_certificate false
listener 9883
protocol mqtt
cafile /mosquitto/config/certs/bundle-ca.crt
certfile /mosquitto/config/certs/server/server.crt
keyfile /mosquitto/config/certs/server/server.key
require_certificate true
allow_anonymous true
This configuration sets up two MQTT listeners:
- Port 1883: Standard MQTT without TLS for local/development connections
- Port 9883: Secure MQTT with mutual TLS (mTLS) for production use
6. Configure Storage Module
Create the Storage Module configuration file, replacing {SAS_EDGE_STORAGE_KEY} with the value from the Sas Edge Storage Key field in the Datalogic Connect edge device configuration:
sudo nano /srv/StorageModule/config/config.yaml
app:
azure:
blob-storage-url: { SAS_EDGE_STORAGE_KEY }
7. Configure Provisioning Module
The Provisioning Module generates the X.509 certificates that field devices use to authenticate over mTLS (port 9883). Field devices connect through the Windows host via the port proxy rules described in the Network Configuration section — the certificate SAN must therefore contain the Windows host IP or hostname, not the EFLOW VM IP.
Retrieve the Windows host IP:
$windowsHostIp = (Get-NetIPAddress -AddressFamily IPv4 |
Where-Object { $_.InterfaceAlias -notmatch 'Loopback|vEthernet' } |
Select-Object -First 1).IPAddress
Write-Host "Windows host IP: $windowsHostIp"
Addresses in the 169.254.x.x range (APIPA) are link-local only and not reachable from other devices on the network. Use an address from your actual network subnet (for example, 10.x.x.x, 192.168.x.x, or 172.16.x.x). If the above command returns an APIPA address, your network adapter is not properly configured — check with your IT department or try a different adapter.
Create the configuration file:
sudo nano /srv/ProvisioningModule/config/certificates.yaml
At least one of common-name or server-ip-address must be provided. Remove any field that is not used.
certificates:
common-name: ${certificates_common_name:REPLACE_WITH_CERTIFICATES_COMMON_NAME}
server-ip-address: ${certificates_server_ip_address:REPLACE_WITH_WINDOWS_HOST_IP}
For example, if your Windows host IP is 192.168.1.50:
certificates:
server-ip-address: "192.168.1.50"
Or using the Windows hostname (preferred when the host has a stable DNS name on the local network):
certificates:
common-name: "my-windows-host"
Or both:
certificates:
common-name: "my-windows-host"
server-ip-address: "192.168.1.50"
8. Configure Open Telemetry Collector
Create the OpenTelemetry Collector configuration file:
sudo nano /srv/otel/config.yml
Paste the following configuration:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
exporters:
debug:
verbosity: detailed
azuremonitor:
spaneventsenabled: true
extensions:
zpages:
processors:
filter:
error_mode: ignore
logs:
log_record:
- "severity_number < SEVERITY_NUMBER_WARN"
service:
extensions: [zpages]
pipelines:
traces:
receivers: [otlp]
exporters: [debug, azuremonitor]
metrics:
receivers: [otlp]
exporters: [debug, azuremonitor]
logs:
receivers: [otlp]
processors: [filter]
exporters: [debug, azuremonitor]
9. Configure IoT Edge
Edit the IoT Edge configuration file:
sudo nano /etc/aziot/config.toml
In the provisioning section, set the following parameters, replacing <SCOPE_ID>, <SERIAL_NUMBER>, and <PRIMARY_KEY> with the values from your device credentials:
[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.15"
Apply the configuration:
sudo iotedge config apply
Once these steps are completed, the IoT Edge device should be successfully registered and connected to Datalogic Connect. You can verify the device's status by running sudo iotedge list inside the EFLOW VM shell.
10. Update Edge Configuration on Redis
Run the following command to populate the Redis configuration, then restart the IoT Edge services:
sudo docker exec -it redis /bin/sh -c 'redis-cli HMSET itmConfig scopeId "<SCOPE_ID>" tenantId "<ORGANIZATION_ID>" modulePrimaryKey "<SAS_DEVICES_KEY>" deviceHubApiKey "<SAS_SMART_ENROLLMENT_KEY>"'
sudo iotedge system restart
Replace the placeholders with your actual values from the device credentials:
<SCOPE_ID>: Your Scope ID<ORGANIZATION_ID>: Your Organization ID<SAS_DEVICES_KEY>: Your SAS Devices Key<SAS_SMART_ENROLLMENT_KEY>: Your SAS Smart Enrollment Key
Now the IoT Edge device is successfully connected to Datalogic Connect and ready to receive and process messages from leaf devices.
To exit the EFLOW VM shell and return to the Windows PowerShell session, type:
exit
Network Configuration
The following setup exposes the EFLOW edge modules to devices on your local network. This section is not part of the core edge device setup — it is infrastructure-specific and must be adapted to your network environment by the customer or network administrator.
Port proxy rules, firewall configuration, and the auto-start script depend on your network topology. The examples below use the Windows Default Switch described in the main tutorial above.
Expose Required Ports
The EFLOW VM is connected to the Windows host through the Default Switch (an internal NAT network). Field devices and mobile devices on the local network cannot reach the EFLOW VM directly — traffic must be forwarded from the Windows host to the VM using port proxy rules and Windows Firewall inbound rules.
Retrieve the current EFLOW VM IP (or reuse $eflowIp from Step 3 if your PowerShell session is still open):
$output = Get-EflowVmAddr *>&1 | Out-String
$eflowIp = if ($output -match '(\d+\.\d+\.\d+\.\d+)') { $Matches[1] } else { $null }
Write-Host "EFLOW current IP: $eflowIp"
Add port proxy forwarding rules:
foreach ($port in @(1883, 9883, 8090, 8098)) {
netsh interface portproxy add v4tov4 `
listenport=$port listenaddress=0.0.0.0 `
connectport=$port connectaddress=$eflowIp
}
Open the corresponding inbound rules in Windows Firewall (only needed once):
New-NetFirewallRule -DisplayName "EFLOW MQTT (1883)" -Direction Inbound -Protocol TCP -LocalPort 1883 -Action Allow
New-NetFirewallRule -DisplayName "EFLOW MQTT TLS (9883)" -Direction Inbound -Protocol TCP -LocalPort 9883 -Action Allow
New-NetFirewallRule -DisplayName "EFLOW StorageModule (8090)" -Direction Inbound -Protocol TCP -LocalPort 8090 -Action Allow
New-NetFirewallRule -DisplayName "EFLOW ProvisioningModule (8098)" -Direction Inbound -Protocol TCP -LocalPort 8098 -Action Allow
Verify the port proxy rules are active:
netsh interface portproxy show v4tov4
Then confirm Windows is listening on those ports:
netstat -ano | findstr "LISTENING" | findstr "1883"
The EFLOW VM IP may change after each Windows reboot because the Default Switch assigns addresses via DHCP. The auto-start startup script in the Configure Auto-Start section automatically re-applies the port proxy rules with the current VM IP on every boot.
netsh portproxy with listenaddress=0.0.0.0 does not intercept loopback traffic. Accessing http://localhost:8090 or http://127.0.0.1:8090 from the Windows host will not work. Use the EFLOW VM IP directly (e.g., http://172.23.x.x:8090) for local testing on the Windows host itself. External devices (mobile, field devices) must use the Windows host IP (e.g., 10.x.x.x) and are forwarded correctly.
Configure Auto-Start at Windows Startup
By default, the EFLOW VM does not start automatically when Windows boots. The startup script below starts the VM and then automatically re-applies the port proxy rules and updates the AladdinSDS configuration with the current VM IP.
Create the startup script (save it to a permanent location, for example C:\EFLOW\startup.ps1):
New-Item -ItemType Directory -Force -Path "C:\EFLOW" | Out-Null
$script = @'
Import-Module AzureEFLOW -WarningAction SilentlyContinue
Start-EflowVm
# Wait until the VM reports an IP address (confirms it has fully booted)
$timeout = 120
$elapsed = 0
do {
Start-Sleep -Seconds 5
$elapsed += 5
$output = Get-EflowVmAddr *>&1 | Out-String
$eflowIp = if ($output -match '(\d+\.\d+\.\d+\.\d+)') { $Matches[1] } else { $null }
} while ((-not $eflowIp) -and ($elapsed -lt $timeout))
if (-not $eflowIp) {
Write-EventLog -LogName Application -Source "EFLOW Startup" -EntryType Error -EventId 1 -Message "EFLOW VM did not respond within $timeout seconds."
exit 1
}
# Re-apply the /var/srv -> /srv bind mount (EFLOW root is read-only; mount does not persist across restarts)
Invoke-EflowVmCommand "sudo mount --bind /var/srv /srv"
# Restart IoT Edge so modules pick up the writable /srv
Invoke-EflowVmCommand "sudo iotedge system restart"
# Re-apply port proxy rules with the current VM IP
netsh interface portproxy reset
foreach ($port in @(1883, 9883, 8090, 8098)) {
netsh interface portproxy add v4tov4 `
listenport=$port listenaddress=0.0.0.0 `
connectport=$port connectaddress=$eflowIp
}
# Patch AladdinSDS config.toml [edge] host with the current EFLOW VM IP
$configPath = 'C:\ProgramData\Datalogic\AladdinSDS\config.toml'
if (Test-Path $configPath) {
(Get-Content $configPath -Raw) `
-replace '(?s)(\[edge\].*?host\s*=\s*)"[^"]*"', "`$1`"$eflowIp`"" |
Set-Content $configPath -NoNewline
Restart-Service -Name 'AladdinSDS' -ErrorAction SilentlyContinue
}
'@
Set-Content -Path "C:\EFLOW\startup.ps1" -Value $script
Register the Scheduled Task to run the script at system startup as the SYSTEM account:
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NonInteractive -ExecutionPolicy Bypass -File C:\EFLOW\startup.ps1"
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
Register-ScheduledTask `
-TaskName "EFLOW Auto-Start" `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Force
To test the startup script immediately without rebooting:
& "C:\EFLOW\startup.ps1"
Connect Field Devices to the Edge
All devices connect through the Windows host — port proxy rules forward traffic to the EFLOW VM regardless of its current IP.
| Device type | Address to configure | Reason |
|---|---|---|
| AladdinSDS (Windows host) | EFLOW VM current IP (auto-updated) | The startup script patches config.toml with the current EFLOW VM IP on every reboot |
| Mobile devices (local network) | Windows host IP | Port proxy rules on the Windows host forward traffic to the EFLOW VM |
AladdinSDS
Set the initial [edge] host in C:\ProgramData\Datalogic\AladdinSDS\config.toml using the $eflowIp variable from Step 3:
$configPath = 'C:\ProgramData\Datalogic\AladdinSDS\config.toml'
(Get-Content $configPath -Raw) `
-replace '(?s)(\[edge\].*?host\s*=\s*)"[^"]*"', "`$1`"$eflowIp`"" |
Set-Content $configPath -NoNewline
Restart-Service -Name 'AladdinSDS'
The resulting [edge] section will look like:
[edge]
host = "172.20.x.x" # current EFLOW VM IP — kept in sync automatically by startup script
[mqtt]
port = 1883
protocol = "tcp"
The auto-start startup script in the Network Configuration section automatically patches this value and restarts AladdinSDS on every Windows reboot. No manual update is needed after the initial setup.
For the full list of MQTT and edge gateway settings, see AladdinSDS Configuration — Edge and MQTT settings.
Mobile Devices
Mobile devices on the local network connect to the Windows host IP address — the port proxy rules forward that traffic to the EFLOW VM.
To find the Windows host IP address:
Get-NetIPAddress -AddressFamily IPv4 |
Where-Object { $_.InterfaceAlias -notmatch 'Loopback|vEthernet' } |
Select-Object InterfaceAlias, IPAddress
Addresses in the 169.254.x.x range (APIPA) are link-local only and not reachable from other devices on the network. Choose an IP address from your actual network subnet (for example, 10.x.x.x, 192.168.x.x, or 172.16.x.x). If all addresses show 169.254.x.x, your network adapter is not properly configured — check with your IT department.
When enrolling mobile devices, enter this Windows host IP in the edge device IP field. For step-by-step enrollment instructions, see Enroll a Mobile Device.
Troubleshooting
Verify EFLOW VM is running:
From an elevated PowerShell session:
Get-EflowVmName
Get-EflowVmTelemetry
Check IoT Edge module status from Windows:
Invoke-EflowVmCommand "sudo iotedge list"
Inspect IoT Edge logs for a specific module:
Invoke-EflowVmCommand "sudo iotedge logs <module-name>"
Restart the EFLOW VM:
Stop-EflowVm
Start-EflowVm
Re-apply the /srv bind mount after a manual Stop/Start:
The EFLOW VM root filesystem is read-only and the /var/srv → /srv bind mount does not persist across VM restarts. If you manually stop and start the VM (outside of the Windows startup script), you must re-apply the mount and restart IoT Edge:
Invoke-EflowVmCommand "sudo mount --bind /var/srv /srv"
Invoke-EflowVmCommand "sudo iotedge system restart"
The Windows startup script (configured in Configure Auto-Start) handles this automatically on every boot.
Uninstall EFLOW
Follow these steps to completely remove EFLOW and all associated resources from the Windows host.
This procedure permanently deletes the EFLOW virtual machine and all data stored inside it, including IoT Edge module configurations and logs. Make sure you have backed up any data you need before proceeding.
1. Remove the Scheduled Task and Startup Script
From an elevated PowerShell session:
Unregister-ScheduledTask -TaskName "EFLOW Auto-Start" -Confirm:$false
Remove-Item -Recurse -Force "C:\EFLOW"
2. Remove Windows Firewall Rules and Port Proxy Rules
Remove-NetFirewallRule -DisplayName "EFLOW MQTT (1883)"
Remove-NetFirewallRule -DisplayName "EFLOW MQTT TLS (9883)"
Remove-NetFirewallRule -DisplayName "EFLOW StorageModule (8090)"
Remove-NetFirewallRule -DisplayName "EFLOW ProvisioningModule (8098)"
netsh interface portproxy reset
3. Stop and Remove the EFLOW VM
Import-Module AzureEFLOW
Stop-EflowVm
Remove-EflowVm
4. Uninstall the EFLOW MSI
Open Settings → Apps (or Control Panel → Programs and Features) and uninstall Azure IoT Edge LTS. Alternatively, run the following command in an elevated PowerShell session:
Get-Package -Name "Azure IoT Edge LTS" | Uninstall-Package -Force
After the package is removed, you may need to reboot the machine to complete the uninstall.