Skip to content

Instantly share code, notes, and snippets.

@veyalla
Last active March 6, 2020 19:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save veyalla/4cd77593a209e6622c44c85bdfdd5543 to your computer and use it in GitHub Desktop.
Save veyalla/4cd77593a209e6622c44c85bdfdd5543 to your computer and use it in GitHub Desktop.
New-Module -Name IoTEdge -ScriptBlock {
<#
# Installs the IoT Edge Security Daemon on Windows.
#>
#requires -Version 5
#requires -RunAsAdministrator
Set-Variable Windows1607 -Value 14393 -Option Constant
Set-Variable Windows1809 -Value 17763 -Option Constant
Set-Variable MinBuildForLinuxContainers -Value $Windows1607
# When using Windows containers, the host OS version must match the container OS version.
# Since our containers are built with 10.0.17763 base images, we require the same for the host OS.
#
# If this needs to be changed, also update the host OS version check in the `iotedge check` tool (edgelet/iotedge/src/check/mod.rs)
Set-Variable SupportedBuildsForWindowsContainers -Value @($Windows1809)
Set-Variable DockerServiceName -Value 'com.docker.service' -Option Constant
Set-Variable EdgePackage -Value 'microsoft-azure-iotedge' -Option Constant
# If the user is running a 32-bit PS host on a 64-bit OS, then `$env:ProgramFiles` points to `C:\Program Files (x86)`
# So use `$env:ProgramW6432` instead.
#
# However, an actual 32-bit OS like IoT Core ARM32 does not define `$env:ProgramW6432`, so fall back to `$env:ProgramFiles` in that case.
Set-Variable ProgramFilesDirectory -Value $(
if (Test-Path Env:\ProgramW6432) {
$env:ProgramW6432
}
else {
$env:ProgramFiles
}
) -Option Constant
Set-Variable EdgeInstallDirectory -Value "$ProgramFilesDirectory\iotedge" -Option Constant
Set-Variable EdgeDataDirectory -Value "$env:ProgramData\iotedge" -Option Constant
Set-Variable EdgeServiceName -Value 'iotedge' -Option Constant
Set-Variable ContainersFeaturePackageName -Value 'Microsoft-IoT-Containers-Server-Package' -Option Constant
Set-Variable ContainersFeatureLangPackageName -Value 'Microsoft-IoT-Containers-Server-Package_*' -Option Constant
Set-Variable MobyDataRootDirectory -Value "$env:ProgramData\iotedge-moby" -Option Constant
Set-Variable MobyInstallDirectory -Value "$ProgramFilesDirectory\iotedge-moby" -Option Constant
Set-Variable MobyLinuxNamedPipeUrl -Value 'npipe://./pipe/docker_engine' -Option Constant
Set-Variable MobyNamedPipeUrl -Value 'npipe://./pipe/iotedge_moby_engine' -Option Constant
Set-Variable MobyServiceName -Value 'iotedge-moby' -Option Constant
Set-Variable LegacyEdgeInstallDirectory -Value 'C:\ProgramData\iotedge' -Option Constant
Set-Variable LegacyEdgeEventLogName -Value 'iotedged' -Option Constant
Set-Variable LegacyEdgeEventLogInstallDirectory -Value 'C:\ProgramData\iotedge-eventlog' -Option Constant
Set-Variable LegacyEventLogApplicationRegPath -Value 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application' -Option Constant
Set-Variable LegacyMobyDataRootDirectory -Value "$env:ProgramData\iotedge-moby-data" -Option Constant
Set-Variable LegacyMobyStaticDataRootDirectory -Value 'C:\ProgramData\iotedge-moby-data' -Option Constant
Set-Variable LegacyMobyInstallDirectory -Value "$env:ProgramData\iotedge-moby" -Option Constant
Set-Variable LegacyMobyStaticInstallDirectory -Value 'C:\ProgramData\iotedge-moby' -Option Constant
Set-Variable ReinstallMessage -Value 'To reinstall, first remove the existing installation using "Uninstall-IoTEdge".' -Option Constant
Set-Variable InstallMessage -Value 'To install, run "Deploy-IoTEdge" first.' -Option Constant
enum ContainerOs {
Linux
Windows
}
function New-Sockets([string] $EdgeDataDirectory) {
foreach ($name in 'mgmt', 'workload') {
# We can't bind socket files directly in Windows, so create a folder
# and bind to that. The folder needs to give Modify rights to a
# well-known group that will exist in any container so that
# non-privileged modules can access it.
$path = "$EdgeDataDirectory\$name"
New-Item $Path -ItemType Directory -Force | Out-Null
$sid = New-Object System.Security.Principal.SecurityIdentifier 'S-1-5-11' # NT AUTHORITY\Authenticated Users
$rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule(`
$sid, 'Modify', 'ObjectInherit', 'InheritOnly', 'Allow')
$acl = Get-Acl -Path $path
$acl.AddAccessRule($rule)
Set-Acl -Path $path -AclObject $acl
}
}
<#
.SYNOPSIS
Initializes the IoT Edge Security Daemon and its dependencies.
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
PS> Initialize-IoTEdge -Manual -DeviceConnectionString $deviceConnectionString -ContainerOs Windows
.EXAMPLE
PS> Initialize-IoTEdge -Manual -DeviceConnectionString $deviceConnectionString -ContainerOs Windows -DeviceCACertificate $deviceCACertificate -DeviceCAPrivateKey $deviceCAPrivateKey -DeviceTrustbundle $deviceTrustbundle
.EXAMPLE
PS> Initialize-IoTEdge -Dps -ScopeId $scopeId -RegistrationId $registrationId -ContainerOs Windows
.EXAMPLE
PS> Initialize-IoTEdge -Dps -ScopeId $scopeId -RegistrationId $registrationId -ContainerOs Windows -DeviceCACertificate $deviceCACertificate -DeviceCAPrivateKey $deviceCAPrivateKey -DeviceTrustbundle $deviceTrustbundle
.EXAMPLE
PS> Initialize-IoTEdge -Dps -ScopeId $scopeId -RegistrationId $registrationId -ContainerOs Windows -SymmetricKey $symmetricKey
.EXAMPLE
PS> Initialize-IoTEdge -Dps -ScopeId $scopeId -RegistrationId $registrationId -ContainerOs Windows -SymmetricKey $symmetricKey -DeviceCACertificate $deviceCACertificate -DeviceCAPrivateKey $deviceCAPrivateKey -DeviceTrustbundle $deviceTrustbundle
.EXAMPLE
PS> Initialize-IoTEdge -Dps -ScopeId $scopeId -ContainerOs Windows -X509IdentityCertificate $x509IdentityCertificate
-X509IdentityPrivateKey $x509IdentityPrivateKey
.EXAMPLE
PS> Initialize-IoTEdge -Dps -ScopeId $scopeId -ContainerOs Windows -X509IdentityCertificate $x509IdentityCertificate
-X509IdentityPrivateKey $x509IdentityPrivateKey -DeviceCACertificate $deviceCACertificate -DeviceCAPrivateKey $deviceCAPrivateKey -DeviceTrustbundle $deviceTrustbundle
.EXAMPLE
PS> Initialize-IoTEdge -External -ExternalProvisioningEndpoint $externalProvisioningEndpoint -ContainerOs Windows -DeviceCACertificate $deviceCACertificate -DeviceCAPrivateKey $deviceCAPrivateKey -DeviceTrustbundle $deviceTrustbundle
#>
function Initialize-IoTEdge {
[CmdletBinding(DefaultParameterSetName = 'Manual')]
param (
# Specified the daemon will be configured manually, using a device connection string.
[Parameter(ParameterSetName = 'Manual')]
[Switch] $Manual,
# Specified the daemon will be configured using DPS, using a scope ID and registration ID.
[Parameter(ParameterSetName = 'DPS')]
[Switch] $Dps,
# Specified the daemon will be configured using an external provisioning endpoint.
[Parameter(ParameterSetName = 'External')]
[Switch] $External,
# The device connection string.
[Parameter(Mandatory = $true, ParameterSetName = 'Manual')]
[String] $DeviceConnectionString,
# The DPS scope ID.
[Parameter(Mandatory = $true, ParameterSetName = 'DPS')]
[String] $ScopeId,
# The DPS registration ID.
[Parameter(ParameterSetName = 'DPS')]
[ValidateNotNullOrEmpty()]
[String] $RegistrationId,
# The DPS symmetric key to provision the Edge device identity
[Parameter(ParameterSetName = 'DPS')]
[ValidateNotNullOrEmpty()]
[String] $SymmetricKey,
# The Edge device identity certificate
[Parameter(ParameterSetName = 'DPS')]
[ValidateNotNullOrEmpty()]
[String] $X509IdentityCertificate,
# The Edge device identity private key
[Parameter(ParameterSetName = 'DPS')]
[ValidateNotNullOrEmpty()]
[String] $X509IdentityPrivateKey,
# The Edge device CA certificate
[ValidateNotNullOrEmpty()]
[String] $DeviceCACertificate,
# The Edge device CA private key
[ValidateNotNullOrEmpty()]
[String] $DeviceCAPrivateKey,
# The Edge device trustbundle
[ValidateNotNullOrEmpty()]
[String] $DeviceTrustbundle,
# The external provisioning environment endpoint for the External provisioning mode.
[Parameter(Mandatory = $true, ParameterSetName = 'External')]
[ValidateNotNullOrEmpty()]
[String] $ExternalProvisioningEndpoint,
# The base OS of all the containers that will be run on this device via the security daemon.
#
# If set to Linux, a separate installation of Docker for Windows is expected.
#
# If set to Windows, the Moby Engine will be installed automatically. This will not affect any existing installation of Docker for Windows.
[ContainerOs] $ContainerOs = 'Windows',
# IoT Edge Agent image to pull for the initial configuration.
[String] $AgentImage,
# Username used to access the container registry and pull the IoT Edge Agent image.
[String] $Username,
# Password used to access the container registry and pull the IoT Edge Agent image.
[SecureString] $Password
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version 5
if (-not (Test-EdgeAlreadyInstalled)) {
Write-HostRed
Write-HostRed ('IoT Edge is not yet installed. ' + $InstallMessage)
throw
}
if (-not (Test-VcRuntimePresent)) {
Write-HostRed 'VC Runtime must be installed before IoT Edge can be initialized.'
throw
}
if ((Test-MobyNeedsToBeMoved) -or (Test-LegacyInstaller)) {
Write-HostRed
Write-HostRed ('IoT Edge or the IoT Edge Moby Engine is installed in an invalid location. There may be an old preview install present. Please run Uninstall-IoTEdge first or reimage the device. ' + $ReinstallMessage)
throw
}
if (-not (Test-MobyAlreadyInstalled)) {
Write-HostRed
Write-HostRed ('IoT Edge Moby Engine is not yet installed. ' + $ReinstallMessage)
throw
}
if (-not (Test-AgentRegistryArgs)) {
throw
}
Setup-Environment -ContainerOs $ContainerOs -SkipBatteryCheck
$configPath = Join-Path -Path $EdgeDataDirectory -ChildPath 'config.yaml'
if (Test-Path $configPath) {
Write-HostRed
Write-HostRed "$configPath already exists."
if (Test-IotCore) {
Write-HostRed ('You must reflash the device and then ' +
're-run "Deploy-IoTEdge" and "Initialize-IoTEdge"')
} else {
Write-HostRed ('Delete it using "Uninstall-IoTEdge -Force" and then ' +
're-run "Deploy-IoTEdge" and "Initialize-IoTEdge"')
}
throw
}
New-Sockets $EdgeDataDirectory
Set-SystemPath
# config.yaml
Write-Host 'Generating config.yaml...'
if (-not (Test-Path $EdgeDataDirectory)) {
New-Item -Path $EdgeDataDirectory -ItemType 'Directory'
}
Copy-Item -Path (Join-Path -Path $EdgeInstallDirectory -ChildPath 'config.yaml') -Destination $configPath
Set-ProvisioningMode
Set-Certificates
if (![string]::IsNullOrEmpty($X509IdentityCertificate)) {
Import-IntermediateCertChain $X509IdentityCertificate
}
Set-AgentImage
Set-Hostname
if ($ContainerOs -eq 'Linux') {
Set-GatewayAddress
}
else {
Set-CorrectProgramData
}
Set-MobyEngineParameters
# Start services
Start-IoTEdgeService
if ($ContainerOs -eq 'Linux') {
Add-FirewallExceptions
}
Write-LogInformation
}
<#
.SYNOPSIS
Updates the IoT Edge Security Daemon and its dependencies.
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
PS> Update-IoTEdge
.EXAMPLE
PS> Update-IoTEdge -OfflineInstallationPath c:\data
#>
function Update-IoTEdge {
[CmdletBinding()]
param (
# The base OS of all the containers that will be run on this device via the security daemon.
#
# If set to Linux, a separate installation of Docker for Windows is expected.
#
# If set to Windows, the Moby Engine will be installed automatically. This will not affect any existing installation of Docker for Windows.
[ContainerOs] $ContainerOs = 'Windows',
# Proxy URI used for all Invoke-WebRequest calls. To specify other proxy-related options like -ProxyCredential, see -InvokeWebRequestParameters
[Uri] $Proxy,
# If set to a directory path, the installer uses the IoTEdge CAB and VC Runtime MSI files from inside this directory
# instead of downloading them from the internet. Thus placing both files in this directory can be used to have a completely offline install.
[String] $OfflineInstallationPath,
# Splatted into every Invoke-WebRequest invocation. Can be used to set extra options.
#
# If -Proxy is also specified, it overrides the "-Proxy" key set in this hashtable, if any.
#
# Example:
#
# Update-IoTEdge -InvokeWebRequestParameters @{ '-Proxy' = 'http://localhost:8888'; '-ProxyCredential' = (Get-Credential).GetNetworkCredential() }
[HashTable] $InvokeWebRequestParameters,
# Restart if needed without prompting.
[Switch] $RestartIfNeeded
)
Install-Packages `
-ContainerOs $ContainerOs `
-Proxy $Proxy `
-OfflineInstallationPath $OfflineInstallationPath `
-InvokeWebRequestParameters $InvokeWebRequestParameters `
-RestartIfNeeded:$RestartIfNeeded `
-Update `
-SkipBatteryCheck
}
<#
.SYNOPSIS
Deploys the IoT Edge Security Daemon and its dependencies.
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
PS> Deploy-IoTEdge
.EXAMPLE
PS> Deploy-IoTEdge -OfflineInstallationPath c:\data
#>
function Deploy-IoTEdge {
[CmdletBinding()]
param (
# The base OS of all the containers that will be run on this device via the security daemon.
#
# If set to Linux, a separate installation of Docker for Windows is expected.
#
# If set to Windows, the Moby Engine will be installed automatically. This will not affect any existing installation of Docker for Windows.
[ContainerOs] $ContainerOs = 'Windows',
# Proxy URI used for all Invoke-WebRequest calls. To specify other proxy-related options like -ProxyCredential, see -InvokeWebRequestParameters
[Uri] $Proxy,
# If set to a directory path, the installer uses the IoTEdge CAB and VC Runtime MSI files from inside this directory
# instead of downloading them from the internet. Thus placing both files in this directory can be used to have a completely offline install.
[String] $OfflineInstallationPath,
# Splatted into every Invoke-WebRequest invocation. Can be used to set extra options.
#
# If -Proxy is also specified, it overrides the "-Proxy" key set in this hashtable, if any.
#
# Example:
#
# Update-IoTEdge -InvokeWebRequestParameters @{ '-Proxy' = 'http://localhost:8888'; '-ProxyCredential' = (Get-Credential).GetNetworkCredential() }
[HashTable] $InvokeWebRequestParameters,
# Restart if needed without prompting.
[Switch] $RestartIfNeeded,
# Skip battery check.
[Switch] $SkipBatteryCheck
)
Set-StrictMode -Version 5
Install-Packages `
-ContainerOs $ContainerOs `
-Proxy $Proxy `
-OfflineInstallationPath $OfflineInstallationPath `
-InvokeWebRequestParameters $InvokeWebRequestParameters `
-RestartIfNeeded:$RestartIfNeeded `
-SkipBatteryCheck:$SkipBatteryCheck
Set-SystemPath
}
<#
.SYNOPSIS
Installs the IoT Edge Security Daemon and its dependencies. Wrapper for Deploy-IoTEdge and Initialize-IoTEdge.
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
PS> Install-IoTEdge -Manual -DeviceConnectionString $deviceConnectionString -ContainerOs Windows
.EXAMPLE
PS> Install-IoTEdge -Manual -DeviceConnectionString $deviceConnectionString -ContainerOs Windows -DeviceCACertificate $deviceCACertificate -DeviceCAPrivateKey $deviceCAPrivateKey -DeviceTrustbundle $deviceTrustbundle
.EXAMPLE
PS> Install-IoTEdge -Dps -ScopeId $scopeId -RegistrationId $registrationId -ContainerOs Windows
.EXAMPLE
PS> Install-IoTEdge -Dps -ScopeId $scopeId -RegistrationId $registrationId -ContainerOs Windows -DeviceCACertificate $deviceCACertificate -DeviceCAPrivateKey $deviceCAPrivateKey -DeviceTrustbundle $deviceTrustbundle
.EXAMPLE
PS> Install-IoTEdge -Dps -ScopeId $scopeId -RegistrationId $registrationId -ContainerOs Windows -SymmetricKey $symmetricKey
.EXAMPLE
PS> Install-IoTEdge -Dps -ScopeId $scopeId -RegistrationId $registrationId -ContainerOs Windows -SymmetricKey $symmetricKey -DeviceCACertificate $deviceCACertificate -DeviceCAPrivateKey $deviceCAPrivateKey -DeviceTrustbundle $deviceTrustbundle
.EXAMPLE
PS> Install-IoTEdge -Dps -ScopeId $scopeId -ContainerOs Windows -X509IdentityCertificate $x509IdentityCertificate -X509IdentityPrivateKey $x509IdentityPrivateKey
.EXAMPLE
PS> Install-IoTEdge -Dps -ScopeId $scopeId -ContainerOs Windows -X509IdentityCertificate $x509IdentityCertificate -X509IdentityPrivateKey $x509IdentityPrivateKey -DeviceCACertificate $deviceCACertificate -DeviceCAPrivateKey $deviceCAPrivateKey -DeviceTrustbundle $deviceTrustbundle
.EXAMPLE
PS> Install-IoTEdge -External -ExternalProvisioningEndpoint $externalProvisioningEndpoint -ContainerOs Windows -DeviceCACertificate $deviceCACertificate -DeviceCAPrivateKey $deviceCAPrivateKey -DeviceTrustbundle $deviceTrustbundle
#>
function Install-IoTEdge {
[CmdletBinding(DefaultParameterSetName = 'Manual')]
param (
# Specified the daemon will be configured manually, using a device connection string.
[Parameter(ParameterSetName = 'Manual')]
[Switch] $Manual,
# Specified the daemon will be configured using DPS, using a scope ID and registration ID.
[Parameter(ParameterSetName = 'DPS')]
[Switch] $Dps,
# Specified the daemon will be configured using an external provisioning endpoint.
[Parameter(ParameterSetName = 'External')]
[Switch] $External,
# The device connection string.
[Parameter(Mandatory = $true, ParameterSetName = 'Manual')]
[String] $DeviceConnectionString,
# The DPS scope ID.
[Parameter(Mandatory = $true, ParameterSetName = 'DPS')]
[String] $ScopeId,
# The DPS registration ID.
[Parameter(ParameterSetName = 'DPS')]
[ValidateNotNullOrEmpty()]
[String] $RegistrationId,
# The DPS symmetric key to provision the Edge device identity
[Parameter(ParameterSetName = 'DPS')]
[ValidateNotNullOrEmpty()]
[String] $SymmetricKey,
# The Edge device identity certificate
[Parameter(ParameterSetName = 'DPS')]
[ValidateNotNullOrEmpty()]
[String] $X509IdentityCertificate,
# The Edge device identity private key
[Parameter(ParameterSetName = 'DPS')]
[ValidateNotNullOrEmpty()]
[String] $X509IdentityPrivateKey,
# The Edge device CA certificate
[ValidateNotNullOrEmpty()]
[String] $DeviceCACertificate,
# The Edge device CA private key
[ValidateNotNullOrEmpty()]
[String] $DeviceCAPrivateKey,
# The Edge device trustbundle
[ValidateNotNullOrEmpty()]
[String] $DeviceTrustbundle,
# The external provisioning environment endpoint for the External provisioning mode.
[Parameter(Mandatory = $true, ParameterSetName = 'External')]
[ValidateNotNullOrEmpty()]
[String] $ExternalProvisioningEndpoint,
# The base OS of all the containers that will be run on this device via the security daemon.
#
# If set to Linux, a separate installation of Docker for Windows is expected.
#
# If set to Windows, the Moby Engine will be installed automatically. This will not affect any existing installation of Docker for Windows.
[ContainerOs] $ContainerOs = 'Windows',
# Proxy URI used for all Invoke-WebRequest calls. To specify other proxy-related options like -ProxyCredential, see -InvokeWebRequestParameters
[Uri] $Proxy,
# If set to a directory path, the installer uses the IoTEdge CAB and VC Runtime MSI files from inside this directory
# instead of downloading them from the internet. Thus placing both files in this directory can be used to have a completely offline install.
[String] $OfflineInstallationPath,
# IoT Edge Agent image to pull for the initial configuration.
[String] $AgentImage,
# Username used to access the container registry and pull the IoT Edge Agent image.
[String] $Username,
# Password used to access the container registry and pull the IoT Edge Agent image.
[SecureString] $Password,
# Splatted into every Invoke-WebRequest invocation. Can be used to set extra options.
#
# If -Proxy is also specified, it overrides the "-Proxy" key set in this hashtable, if any.
#
# Example:
#
# Update-IoTEdge -InvokeWebRequestParameters @{ '-Proxy' = 'http://localhost:8888'; '-ProxyCredential' = (Get-Credential).GetNetworkCredential() }
[HashTable] $InvokeWebRequestParameters,
# Restart if needed without prompting.
[Switch] $RestartIfNeeded,
# Skip battery check.
[Switch] $SkipBatteryCheck
)
Set-StrictMode -Version 5
# Set by Deploy-IoTEdge if it succeeded, so we can abort early in case of failure.
#
# We use a script-scope var instead of having Deploy-IoTEdge return a boolean or take a [ref] parameter
# because users can also run Deploy-IoTEdge themselves, so it can't be part of the public API.
$script:installPackagesCompleted = $false
# Used to suppress some messages from Deploy-IoTEdge since we are automatically running Initialize-IoTEdge
$calledFromInstall = $true
Deploy-IoTEdge `
-ContainerOs $ContainerOs `
-Proxy $Proxy `
-OfflineInstallationPath $OfflineInstallationPath `
-InvokeWebRequestParameters $InvokeWebRequestParameters `
-RestartIfNeeded:$RestartIfNeeded `
-SkipBatteryCheck:$SkipBatteryCheck
if (-not $script:installPackagesCompleted) {
return
}
$Params = @{
'-ContainerOs' = $ContainerOs
}
if ($Manual) { $Params["-Manual"] = $true }
if ($Dps) { $Params["-Dps"] = $true }
if ($External) { $Params["-External"] = $true }
if ($DeviceConnectionString) { $Params["-DeviceConnectionString"] = $DeviceConnectionString }
if ($ScopeId) { $Params["-ScopeId"] = $ScopeId }
if ($RegistrationId) { $Params["-RegistrationId"] = $RegistrationId }
if ($SymmetricKey) { $Params["-SymmetricKey"] = $SymmetricKey }
if ($X509IdentityCertificate) { $Params["-X509IdentityCertificate"] = $X509IdentityCertificate }
if ($X509IdentityPrivateKey) { $Params["-X509IdentityPrivateKey"] = $X509IdentityPrivateKey }
if ($DeviceCACertificate) { $Params["-DeviceCACertificate"] = $DeviceCACertificate }
if ($DeviceCAPrivateKey) { $Params["-DeviceCAPrivateKey"] = $DeviceCAPrivateKey }
if ($DeviceTrustbundle) { $Params["-DeviceTrustbundle"] = $DeviceTrustbundle }
if ($ExternalProvisioningEndpoint) { $Params["-ExternalProvisioningEndpoint"] = $ExternalProvisioningEndpoint }
if ($AgentImage) { $Params["-AgentImage"] = $AgentImage }
if ($Username) { $Params["-Username"] = $Username }
if ($Password) { $Params["-Password"] = $Password }
# Used to suppress some messages from Initialize-IoTEdge that have already been emitted by Deploy-IoTEdge
$initializeCalledFromInstall = $true
Initialize-IoTEdge @Params
}
<#
.SYNOPSIS
Uninstalls the IoT Edge Security Daemon and its dependencies.
.DESCRIPTION
This cmdlet will delete the config.yaml and the Moby Engine data root (for -ContainerOs 'Windows' installs).
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
PS> Uninstall-IoTEdge
.EXAMPLE
PS> Uninstall-IoTEdge -Force
#>
function Uninstall-IoTEdge {
[CmdletBinding()]
param (
# Forces the uninstallation in case the previous install was only partially successful.
[Switch] $Force,
# Restart if needed without prompting.
[Switch] $RestartIfNeeded
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version 5
$legacyInstaller = Test-LegacyInstaller
if ((Test-IoTCore) -and (-not $legacyInstaller)) {
Write-HostRed
Write-HostRed ('Uninstall-IoTEdge is only supported on IoTCore to uninstall legacy installation. ' +
'For new installations, please use "Update-IoTEdge" directly to update.')
throw
}
if (-not $Force -and -not ((Test-EdgeAlreadyInstalled) -or (Test-MobyAlreadyInstalled))) {
Write-HostRed
Write-HostRed 'IoT Edge is not installed. Use "-Force" to uninstall anyway.'
throw
}
Write-Host 'Uninstalling...'
$ContainerOs = Get-ContainerOs
$restartNeeded = $false
Uninstall-Services -RestartNeeded ([ref] $restartNeeded) -LegacyInstaller $legacyInstaller
$success = Remove-IoTEdgeResources -LegacyInstaller $legacyInstaller
Reset-SystemPath
Remove-MachineEnvironmentVariable 'IOTEDGE_HOST'
Remove-Item Env:\IOTEDGE_HOST -ErrorAction SilentlyContinue
Remove-FirewallExceptions
if ($success) {
Write-HostGreen 'Successfully uninstalled IoT Edge.'
}
if ($restartNeeded) {
Write-HostRed 'Reboot required.'
Write-Host 'You might need to rerun "Uninstall-IoTEdge" after the reboot to finish the cleanup.'
Restart-Computer -Confirm:(-not $RestartIfNeeded) -Force:$RestartIfNeeded
}
}
function Get-IoTEdgeLog {
[CmdletBinding()]
param (
# What time to start the log from.
[DateTime] $StartTime = [datetime]::Now.AddMinutes(-5)
)
Set-StrictMode -Version 5
Get-WinEvent -ea SilentlyContinue -FilterHashtable @{ProviderName='iotedged';LogName='application';StartTime=$StartTime} |
Select TimeCreated, Message |
Sort-Object @{Expression='TimeCreated';Descending=$false} |
Format-Table -AutoSize -Wrap
}
function Install-Packages(
[ContainerOs] $ContainerOs,
[Uri] $Proxy,
[String] $OfflineInstallationPath,
[HashTable] $InvokeWebRequestParameters,
[Switch] $RestartIfNeeded,
[Switch] $Update,
[Switch] $SkipBatteryCheck
)
{
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version 5
if ($InvokeWebRequestParameters -eq $null) {
$InvokeWebRequestParameters = @{}
}
if ($Proxy -ne $null) {
$InvokeWebRequestParameters['-Proxy'] = $Proxy
}
if ($Update) {
if (-not (Test-EdgeAlreadyInstalled)) {
Write-HostRed
Write-HostRed ('IoT Edge is not yet installed. ' + $InstallMessage)
throw
}
if ((Test-MobyNeedsToBeMoved) -or (Test-LegacyInstaller)) {
Write-HostRed
Write-HostRed ('IoT Edge or the IoT Edge Moby Engine is installed in an invalid location. There may be an old preview install present. Please run Uninstall-IoTEdge first or reimage the device. ' + $ReinstallMessage)
throw
}
if (-not (Test-MobyAlreadyInstalled)) {
Write-HostRed
Write-HostRed ('IoT Edge Moby Engine is not yet installed. ' + $ReinstallMessage)
throw
}
}
else {
if (Test-EdgeAlreadyInstalled) {
if ((Test-MobyNeedsToBeMoved) -or (Test-LegacyInstaller)) {
Write-HostRed
Write-HostRed ('IoT Edge or the IoT Edge Moby Engine is installed in an invalid location. There may be an old preview install present. Please run Uninstall-IoTEdge first or reimage the device. ' + $ReinstallMessage)
}
else {
Write-HostRed
Write-HostRed ('IoT Edge is already installed. To update, run "Update-IoTEdge". ' +
'Alternatively, if you want to finalize the installation, run "Initialize-IoTEdge".')
}
throw
}
if (Test-MobyAlreadyInstalled) {
if ((Test-MobyNeedsToBeMoved) -or (Test-LegacyInstaller)) {
Write-HostRed
Write-HostRed ('IoT Edge or the IoT Edge Moby Engine is installed in an invalid location. There may be an old preview install present. Please run Uninstall-IoTEdge first or reimage the device. ' + $ReinstallMessage)
}
else {
Write-HostRed
Write-HostRed ('IoT Edge Moby Engine is already installed, but IoT Edge is not. ' +
$ReinstallMessage)
}
throw
}
}
Setup-Environment -ContainerOs $ContainerOs -SkipBatteryCheck:$SkipBatteryCheck
$restartNeeded = $false
if (-not $Update) {
if (-not (Test-IotCore)) {
$result = Get-WindowsOptionalFeature -Online -FeatureName 'Containers'
if ($result -and ($result.State -ne 'Enabled')) {
$result = Enable-WindowsOptionalFeature -FeatureName 'Containers' -Online -NoRestart
if ($result.RestartNeeded) {
$restartNeeded = $true
}
}
}
}
Get-VcRuntime # does nothing if vcruntime already installed
# Download
Get-IoTEdge -RestartNeeded ([ref] $restartNeeded) -Update $Update
if ((-not $restartNeeded) -and $Update) {
try {
Start-Service $EdgeServiceName
}
catch {
Write-HostRed
Write-HostRed 'Failed to start Security Daemon, make sure to initialize config file by running "Initialize-IoTEdge".'
throw
}
}
if ($Update) {
Write-LogInformation
}
elseif (-not (Test-Path Variable:\calledFromInstall)) {
Write-Host 'To complete the installation, run "Initialize-IoTEdge".'
}
if ($restartNeeded) {
Write-HostRed 'Reboot required. To complete the installation after the reboot, run "Initialize-IoTEdge".'
Restart-Computer -Confirm:(-not $RestartIfNeeded) -Force:$RestartIfNeeded
}
else {
$script:installPackagesCompleted = $true
}
}
function Setup-Environment {
[CmdletBinding()]
param ([string] $ContainerOs, [switch] $SkipBatteryCheck)
$currentWindowsBuild = Get-WindowsBuild
$preRequisitesMet = switch ($ContainerOs) {
'Linux' {
if ($currentWindowsBuild -lt $MinBuildForLinuxContainers) {
Write-HostRed "The container host is on unsupported build version $currentWindowsBuild."
Write-HostRed "Please use a container host running build $MinBuildForLinuxContainers or newer for running Linux containers."
$false
}
elseif (-not (Test-IsDockerRunning)) {
$false
}
else {
if (-not (Test-Path Variable:\initializeCalledFromInstall)) {
Write-Warning ('Linux containers on Windows can be used for development and testing, ' +
'but are not supported in production IoT Edge deployments. See https://aka.ms/iotedge-platsup for more details.')
}
$true
}
}
'Windows' {
if ($SupportedBuildsForWindowsContainers -notcontains $currentWindowsBuild) {
Write-HostRed "The container host is on unsupported build version $currentWindowsBuild."
Write-HostRed 'Please use a container host running one of the following build versions for running Windows containers:'
foreach ($version in $SupportedBuildsForWindowsContainers) {
Write-HostRed "$version"
}
$false
}
else {
$true
}
}
}
if (Test-IoTCore) {
if (-not (Get-Service vmcompute -ErrorAction SilentlyContinue) -or (-not [bool] (Get-Package $ContainersFeaturePackageName)) -or (-not [bool] (Get-Package $ContainersFeatureLangPackageName))) {
Write-HostRed "The container host does not have 'Containers Feature' enabled. Please build an Iot Core image with 'Containers Feature' enabled."
$preRequisitesMet = $false
}
}
if ($preRequisitesMet) {
Write-HostGreen "The container host is on supported build version $currentWindowsBuild."
Set-ContainerOs
}
else {
Write-HostRed
Write-HostRed ('The prerequisites for installation of the IoT Edge Security daemon are not met. ' +
'Please fix all known issues before rerunning this script.')
throw
}
if (-not (Test-IotCore)) {
# "Invoke-WebRequest" may not use TLS 1.2 by default, depending on the specific release of Windows 10.
# This will be a problem if the release is downloaded from github.com since it only provides TLS 1.2.
# So enable TLS 1.2 in "[System.Net.ServicePointManager]::SecurityProtocol", which enables it (in the current PS session)
# for "Invoke-WebRequest" and everything else that uses "System.Net.HttpWebRequest"
#
# This is not needed on IoT Core since its "Invoke-WebRequest" supports TLS 1.2 by default. It *can't* be done
# for IoT Core anyway because the "System.Net.ServicePointManager" type doesn't exist in its version of dotnet.
[System.Net.ServicePointManager]::SecurityProtocol =
[System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12
}
if (($ContainerOs -eq 'Windows') -and (-not $SkipBatteryCheck)) {
if ((Get-Command 'Get-WmiObject' -ErrorAction Ignore) -ne $null) {
[psobject[]] $batteries = Get-WmiObject Win32_Battery -ErrorAction Ignore
if (($batteries -ne $null) -and ($batteries.Length -gt 0)) {
Write-Warning (
"One or more batteries were detected on this device.`n`n" +
'A known Windows operating system issue prevents transition to sleep and hibernate power states when IoT Edge modules ' +
"(process-isolated Windows Nano Server containers) are running. This issue impacts battery life on the device.`n`n" +
'As a workaround, use the command "Stop-Service iotedge, iotedge-moby -Force" to stop any running IoT Edge modules ' +
'before using these power states.')
if (-not $PSCmdlet.ShouldContinue('Do you want to continue with installation?', '')) {
Write-HostRed
Write-HostRed 'Aborting installation.'
throw
}
}
}
}
}
function Write-LogInformation {
Write-HostGreen
Write-HostGreen 'This device is now provisioned with the IoT Edge runtime.'
Write-HostGreen 'Check the status of the IoT Edge service with "Get-Service iotedge"'
Write-HostGreen 'List running modules with "iotedge list"'
Write-HostGreen 'Display logs from the last five minutes in chronological order with "Get-IoTEdgeLog"'
}
function Test-IsDockerRunning {
switch ($ContainerOs) {
'Linux' {
$service = Get-Service $DockerServiceName -ErrorAction SilentlyContinue
if (($service -eq $null) -or ($service.Status -ne 'Running')) {
Write-HostRed 'Docker is not running.'
if (Test-IotCore) {
Write-HostRed ('Please visit https://docs.microsoft.com/en-us/azure/iot-edge/how-to-install-iot-core ' +
'for assistance with installing Docker on IoT Core.')
}
else {
Write-HostRed ('You can use Docker for Windows for development and testing. ' +
'Please visit https://www.docker.com/docker-windows for additional information.')
}
return $false
}
Write-HostGreen 'Docker is running.'
return $true
}
'Windows' {
$service = Get-Service $MobyServiceName -ErrorAction SilentlyContinue
if (($service -eq $null) -or ($service.Status -ne 'Running')) {
return $false
}
return $true
}
}
}
function Set-ContainerOs {
switch ($ContainerOs) {
'Linux' {
if ((Get-ExternalDockerServerOs) -ne 'Linux') {
Write-Host 'Switching Docker to use Linux containers...'
$dockerCliExe = "$ProgramFilesDirectory\Docker\Docker\DockerCli.exe"
if (-not (Test-Path -Path $dockerCliExe)) {
Write-HostRed
Write-HostRed "Unable to switch to Linux containers: could not find $dockerCliExe"
throw
}
Invoke-Native """$dockerCliExe"" -SwitchDaemon"
$newExternalDockerServerOs = Get-ExternalDockerServerOs
if ($newExternalDockerServerOs -ne 'Linux') {
Write-HostRed
Write-HostRed "Unable to switch to Linux containers: Docker is still set to use $newExternalDockerServerOs containers"
throw
}
Write-HostGreen 'Switched Docker to use Linux containers.'
}
}
'Windows' {
# No need to test for Linux/switch to Windows containers because our
# moby installation doesn't support Linux containers
}
}
}
function Get-WindowsBuild {
return (Get-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion').'CurrentBuild'
}
function Test-EdgeAlreadyInstalled {
return (Get-Service $EdgeServiceName -ErrorAction SilentlyContinue) -or
(Test-Path -Path "$EdgeInstallDirectory\iotedged.exe") -or
(Test-path -Path "$LegacyEdgeInstallDirectory\iotedged.exe")
}
function Test-MobyAlreadyInstalled {
return (Get-Service $MobyServiceName -ErrorAction SilentlyContinue) -or
(Test-Path -Path $MobyInstallDirectory)
}
function Test-MobyNeedsToBeMoved {
if ($LegacyMobyStaticInstallDirectory -ne $LegacyMobyInstallDirectory) {
return $(Test-Path -Path $LegacyMobyStaticInstallDirectory)
}
else {
return $false
}
}
function Test-LegacyInstaller {
$legacyMobyData = (Test-Path -Path $LegacyMobyDataRootDirectory) -or
(Test-Path -Path $LegacyMobyStaticDataRootDirectory)
if ($legacyMobyData) {
return $true
}
$newPackage = Get-Package $EdgePackage
return -not ([bool] $newPackage)
}
function Test-AgentRegistryArgs {
$noImageNoCreds = (-not ($AgentImage -or $Username -or $Password))
$imageNoCreds = ($AgentImage -and -not ($Username -or $Password))
$imageFullCreds = ($AgentImage -and $Username -and $Password)
$valid = ($noImageNoCreds -or $imageNoCreds -or $imageFullCreds)
if (-not $valid) {
$message =
if (-not $AgentImage) {
'Parameter ''AgentImage'' is required when parameters ''Username'' and ''Password'' are specified.'
}
else {
'Parameters ''Username'' and ''Password'' must be used together. Please specify both (or neither for anonymous access).'
}
Write-HostRed $message
}
return $valid
}
function Get-ContainerOs {
$yamlPath = (Join-Path -Path $EdgeDataDirectory -ChildPath 'config.yaml')
if (-not (Test-Path $yamlPath)) {
return 'Windows'
}
$configurationYaml = Get-Content $yamlPath -Raw
if (-not ($configurationYaml -match 'moby_runtime:\s*uri:\s*''([^'']+)''')) {
return 'Windows'
}
if ($Matches[1] -eq $MobyLinuxNamedPipeUrl) {
return 'Linux'
}
return 'Windows'
}
function Get-ExternalDockerServerOs {
$dockerExe = Get-DockerCommandPrefix
if ((Invoke-Native "$dockerExe version --format ""{{.Server.Os}}""" -Passthru) -match '\s*windows\s*$') {
return 'Windows'
}
else {
return 'Linux'
}
}
function Install-Package([string] $Path, [ref] $RestartNeeded) {
if (Test-IotCore) {
Invoke-Native "ApplyUpdate -stage $Path"
}
else {
$result = Add-WindowsPackage -Online -PackagePath $Path -NoRestart
if ($result.RestartNeeded) {
$RestartNeeded.Value = $true
}
}
}
function Uninstall-Package([string] $Name, [ref] $RestartNeeded) {
if (Test-IotCore) {
return
}
Get-WindowsPackage -Online |
Where-Object { $_.PackageName -like "$Name~*"} |
Remove-WindowsPackage -Online -NoRestart |
ForEach-Object {
if ($_.RestartNeeded) {
$RestartNeeded.Value = $true
}
}
}
function Get-Package([string] $Name) {
if (Test-IotCore) {
return Invoke-Native 'ApplyUpdate -getinstalledpackages' -Passthru |
Where-Object { $_ -like "*INFO: $Name,*"}
}
else {
return Get-WindowsPackage -Online |
Where-Object { $_.PackageName -like "$Name~*"}
}
}
function Try-StopService([string] $Name) {
if (Get-Service $Name -ErrorAction SilentlyContinue) {
Stop-Service -NoWait -ErrorAction SilentlyContinue -ErrorVariable cmdErr $Name
if ($?) {
Start-Sleep -Seconds 7
}
}
}
function Get-IoTEdge([ref] $RestartNeeded, [bool] $Update) {
try {
# If we create the archive ourselves, then delete it when we're done
$deleteEdgeArchive = $false
if (Test-IotCore) {
Invoke-Native 'ApplyUpdate -clear'
}
$edgeCabUrl = switch ($env:PROCESSOR_ARCHITECTURE) {
'AMD64' {
'https://aka.ms/iotedged-windows-latest-cab'
}
'ARM' {
'https://aka.ms/iotedged-windows-arm32v7-latest-cab'
}
}
$edgeArchivePath = Download-File `
-Description 'IoT Edge' `
-Url $edgeCabUrl `
-DownloadFilename 'microsoft-azure-iotedge.cab' `
-LocalCacheGlob 'microsoft-azure-iotedge.cab' `
-Delete ([ref] $deleteEdgeArchive)
Try-StopService $MobyServiceName
Try-StopService $EdgeServiceName
Install-Package -Path $edgeArchivePath -RestartNeeded $RestartNeeded
if (-not $Update) {
Stop-Service $EdgeServiceName -ErrorAction SilentlyContinue
}
}
finally {
if ($deleteEdgeArchive) {
Remove-Item $edgeArchivePath -Recurse -Force -ErrorAction SilentlyContinue
}
}
if (Test-IotCore) {
Write-Host ('Committing changes, this will cause a reboot on success. ' +
'If this is the first time installation, run "Initialize-IoTEdge" after the reboot completes.')
$output = Invoke-Native 'ApplyUpdate -commit' -DoNotThrow -Passthru
# On success, this should reboot, we currently cannot block that
if ($LASTEXITCODE -ne 0) {
Write-HostRed
Write-HostRed "Failed to deploy, consider rebooting. Please refer to the following for more information:"
Write-HostRed "$output"
throw
}
Start-Sleep -Seconds 120
$output = Invoke-Native 'ApplyUpdate -status' -DoNotThrow -Passthru
Write-HostRed
Write-HostRed "Failed to deploy. Please refer to the following for more information:"
Write-HostRed "$output"
throw
}
}
Function Remove-SecurityDaemonDirectory([string] $Path)
{
Write-Host "Deleting data directory '$Path'..."
Remove-Item -Recurse $Path -ErrorAction SilentlyContinue -ErrorVariable cmdErr
if ($?) {
Write-Verbose "Deleted data directory '$Path'"
}
elseif ($cmdErr.FullyQualifiedErrorId -ne 'PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand') {
Write-Verbose "$cmdErr"
Write-HostRed ("Could not delete directory '$Path'. Please reboot " +
'your device and run "Uninstall-IoTEdge" again with "-Force".')
}
else {
Write-Verbose "$cmdErr"
}
}
function Delete-Directory([string] $Path) {
if (-not (Test-Path $Path)) {
return
}
# Removing "$MobyDataRootDirectory" is tricky. Windows base images contain files owned by TrustedInstaller, etc
# It can also silently succeed but actually delete only some of the files.
#
# So try up to three times to ensure the directory really does get deleted.
for ($i = 0; $i -lt 3; $i++) {
# Deleting is a three-step process:
#
# 1. Take ownership of all files
Invoke-Native "takeown /r /skipsl /d y /f ""$Path""" -DoNotThrow
# 2. Reset their ACLs so that they inherit from their container
Invoke-Native "icacls ""$Path"" /reset /t /l /q /c" -DoNotThrow
# 3. Use cmd's "rd" rather than "Remove-Item" since the latter gets tripped up by reparse points, etc.
# Prepend the path with "\\?\" since the layer directories have long names, so the paths usually exceed 260 characters,
# and IoT Core's filesystem doesn't seem to automatically use (or even have) short names
Invoke-Native "rd /s /q ""\\?\$Path""" -DoNotThrow
if (-not (Test-Path $Path)) {
return
}
}
throw
}
function Remove-IoTEdgeResources([bool] $LegacyInstaller) {
$success = $true
if ($LegacyEdgeInstallDirectory -ne $EdgeDataDirectory) {
Remove-SecurityDaemonDirectory $LegacyEdgeInstallDirectory
}
Remove-SecurityDaemonDirectory $EdgeDataDirectory
if (Test-Path $LegacyEdgeEventLogInstallDirectory) {
Remove-Item -Recurse $LegacyEdgeEventLogInstallDirectory -ErrorAction SilentlyContinue -ErrorVariable cmdErr
if ($?) {
Write-Verbose "Deleted install directory '$LegacyEdgeEventLogInstallDirectory'"
}
elseif ($cmdErr.FullyQualifiedErrorId -ne 'PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand') {
Write-Verbose "$cmdErr"
Write-Warning "Could not delete '$LegacyEdgeEventLogInstallDirectory'."
Write-Warning 'If you are reinstalling or updating IoT Edge, then this is safe to ignore.'
Write-Warning ('Otherwise, please close Event Viewer, or any PowerShell windows where you ran Get-WinEvent, ' +
'then run "Uninstall-IoTEdge" again with "-Force".')
}
else {
Write-Verbose "$cmdErr"
}
}
if ($LegacyInstaller) {
# Check whether we need to clean up after an errant installation into the OS partition on IoT Core
if ($env:ProgramData -ne 'C:\ProgramData') {
Write-Verbose 'Multiple ProgramData directories found'
$existingMobyDataRoots = $LegacyMobyDataRootDirectory, $LegacyMobyStaticDataRootDirectory
$existingMobyInstallations = $LegacyMobyInstallDirectory, $LegacyMobyStaticInstallDirectory
}
else {
$existingMobyDataRoots = $LegacyMobyDataRootDirectory
$existingMobyInstallations = $LegacyMobyInstallDirectory
}
}
else {
$existingMobyDataRoots = $MobyDataRootDirectory
$existingMobyInstallations = @()
}
foreach ($root in $existingMobyDataRoots | ?{ Test-Path $_ }) {
try {
Write-Host "Deleting Moby data root directory '$root'..."
Delete-Directory $root
Write-Verbose "Deleted Moby data root directory '$root'"
}
catch {
Write-Verbose "$_"
Write-HostRed ("Could not delete Moby data root directory '$root'. Please reboot " +
'your device and run "Uninstall-IoTEdge" again with "-Force".')
$success = $false
}
}
foreach ($install in $existingMobyInstallations | ?{ Test-Path $_ }) {
try {
Write-Host "Deleting directory '$install'..."
Delete-Directory $install
Write-Verbose "Deleted directory '$install'"
}
catch {
Write-Verbose $_
Write-HostRed ("Could not delete directory '$install'. Please reboot " +
'your device and run "Uninstall-IoTEdge" again with "-Force".')
$success = $false
}
}
return $success
}
function Get-MachineEnvironmentVariable([string] $Name) {
# Equivalent to "[System.Environment]::GetEnvironmentVariable($Name, [System.EnvironmentVariableTarget]::Machine)"
# but IoT Core doesn't have this overload
return (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment').$Name
}
function Set-MachineEnvironmentVariable([string] $Name, [string] $Value) {
# Equivalent to "[System.Environment]::SetEnvironmentVariable($Name, $Value, [System.EnvironmentVariableTarget]::Machine)"
# but IoT Core doesn't have this overload; however, the direct registry route requires a reboot to make it available everywhere
if (Test-IotCore) {
Set-ItemProperty `
-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' `
-Name $Name `
-Value $Value
}
else {
[System.Environment]::SetEnvironmentVariable($Name, $Value, [System.EnvironmentVariableTarget]::Machine)
}
}
function Remove-MachineEnvironmentVariable([string] $Name) {
# Equivalent to "[System.Environment]::SetEnvironmentVariable($Name, $null, [System.EnvironmentVariableTarget]::Machine)"
# but IoT Core doesn't have this overload
Remove-ItemProperty `
-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' `
-Name $Name `
-ErrorAction SilentlyContinue
}
function Get-SystemPath {
return ((Get-MachineEnvironmentVariable 'Path') -split ';' | Where-Object { $_.Length -gt 0 })
}
function Set-SystemPath {
$systemPath = Get-SystemPath
$needsModification = ($systemPath -notcontains $EdgeInstallDirectory) -or
(($ContainerOs -eq 'Windows') -and ($systemPath -notcontains $MobyInstallDirectory))
if (-not $needsModification) {
Write-HostGreen 'System PATH does not require an update.'
return
}
if ($systemPath -notcontains $EdgeInstallDirectory) {
$systemPath += $EdgeInstallDirectory
}
if (($ContainerOs -eq 'Windows') -and ($systemPath -notcontains $MobyInstallDirectory)) {
$systemPath += $MobyInstallDirectory
}
Set-MachineEnvironmentVariable 'PATH' ($systemPath -join ';')
$env:PATH += ";$EdgeInstallDirectory;$MobyInstallDirectory"
Write-HostGreen 'Updated system PATH.'
}
function Reset-SystemPath {
$systemPath = Get-SystemPath
$needsModification =
($systemPath -contains $EdgeInstallDirectory) -or
($systemPath -contains $MobyInstallDirectory) -or
($systemPath -contains $LegacyMobyStaticInstallDirectory) -or
($systemPath -contains $LegacyEdgeInstallDirectory) -or
($systemPath -contains $LegacyMobyInstallDirectory)
if (-not $needsModification) {
return
}
$systemPath = $systemPath | Where-Object {
return ($_ -ne $EdgeInstallDirectory) -and
($_ -ne $MobyInstallDirectory) -and
($_ -ne $LegacyMobyStaticInstallDirectory) -and
($_ -ne $LegacyEdgeInstallDirectory) -and
($_ -ne $LegacyMobyInstallDirectory)
}
Set-MachineEnvironmentVariable 'PATH' ($systemPath -join ';')
Write-Verbose 'Removed IoT Edge directories from system PATH'
}
function Test-VcRuntimePresent {
return Test-Path 'C:\Windows\System32\vcruntime140.dll'
}
function Get-VcRuntime {
if (Test-IotCore) {
Write-HostGreen 'Skipping VC Runtime installation on IoT Core.'
return
}
if (Test-VcRuntimePresent) {
Write-HostGreen 'Skipping VC Runtime installation because it is already installed.'
return
}
$deleteVcRuntimeArchive = $false
$vcRuntimeArchivePath = Download-File `
-Description 'VC Runtime installer' `
-Url 'https://download.microsoft.com/download/0/6/4/064F84EA-D1DB-4EAA-9A5C-CC2F0FF6A638/vc_redist.x64.exe' `
-DownloadFilename 'vc_redist.x64.exe' `
-LocalCacheGlob '*vc_redist*.exe' `
-Delete ([ref] $deleteVcRuntimeArchive)
try {
Invoke-Native """$vcRuntimeArchivePath"" /quiet /norestart"
Write-HostGreen 'Installed VC Runtime.'
}
catch {
if ((Test-Path Variable:\LASTEXITCODE) -and ($LASTEXITCODE -eq 1638)) {
Write-HostGreen 'Skipping VC Runtime installation because a newer version is already installed.'
}
else {
throw $_
}
}
finally {
if ($deleteVcRuntimeArchive) {
Remove-Item $vcRuntimeArchivePath -Recurse -Force -ErrorAction SilentlyContinue
}
}
}
function Start-IoTEdgeService([bool] $RestartNeeded = $false) {
if (-not $RestartNeeded) {
Start-Service $EdgeServiceName
}
Write-HostGreen 'Initialized the IoT Edge service.'
}
function Uninstall-Services([ref] $RestartNeeded, [bool] $LegacyInstaller) {
if (Get-Service $EdgeServiceName -ErrorAction SilentlyContinue) {
Set-Service -StartupType Disabled $EdgeServiceName -ErrorAction SilentlyContinue
Stop-Service -NoWait -ErrorAction SilentlyContinue -ErrorVariable cmdErr $EdgeServiceName
if ($?) {
Start-Sleep -Seconds 7
Write-Verbose "Stopped the IoT Edge service $EdgeServiceName"
}
else {
Write-Verbose "Stopping IoT Edge service $EdgeServiceName failed. Error: $cmdErr"
}
}
Remove-IotEdgeContainers
if (Get-Service $MobyServiceName -ErrorAction SilentlyContinue) {
Stop-Service -NoWait -ErrorAction SilentlyContinue -ErrorVariable cmdErr $MobyServiceName
if ($?) {
Start-Sleep -Seconds 7
Write-Verbose "Stopped the IoT Edge Moby Engine service $MobyServiceName"
}
else {
Write-Verbose "Stopping IoT Edge Moby Engine service $MobyServiceName failed. Error: $cmdErr"
}
}
if ($LegacyInstaller) {
if (Get-Service $EdgeServiceName -ErrorAction SilentlyContinue) {
if (Invoke-Native "sc.exe delete ""$EdgeServiceName""" -ErrorAction SilentlyContinue) {
Write-Verbose 'Removed IoT Edge service subkey from the registry'
}
}
if (Get-Service $MobyServiceName -ErrorAction SilentlyContinue) {
if (Invoke-Native "sc.exe delete ""$MobyServiceName""" -ErrorAction SilentlyContinue) {
Write-Verbose 'Removed IoT Edge Moby Engine service subkey from the registry'
}
}
}
else {
Write-Verbose 'Uninstalling IoT Edge package.'
Uninstall-Package -Name $EdgePackage -RestartNeeded $RestartNeeded
}
}
function Add-FirewallExceptions {
New-NetFirewallRule `
-DisplayName 'iotedged allow inbound 15580,15581' `
-Direction 'Inbound' `
-Action 'Allow' `
-Protocol 'TCP' `
-LocalPort '15580-15581' `
-Program "$EdgeInstallDirectory\iotedged.exe" `
-InterfaceType 'Any' | Out-Null
Write-HostGreen 'Added firewall exceptions for ports used by the IoT Edge service.'
}
function Remove-FirewallExceptions {
Remove-NetFirewallRule -DisplayName 'iotedged allow inbound 15580,15581' -ErrorAction SilentlyContinue -ErrorVariable cmdErr
Write-Verbose "$(if ($?) { 'Removed firewall exceptions' } else { $cmdErr })"
}
function Update-ConfigYaml([ScriptBlock] $UpdateFunc)
{
$yamlPath = (Join-Path -Path $EdgeDataDirectory -ChildPath 'config.yaml')
$configurationYaml = Get-Content $yamlPath -Raw
$configurationYaml = $UpdateFunc.Invoke($configurationYaml)
$configurationYaml | Set-Content $yamlPath -Force
}
function Validate-GatewaySettings {
$certFilesProvided = $false
if ($DeviceCACertificate -or $DeviceCAPrivateKey -or $DeviceTrustbundle) {
if (-Not (Test-Path -Path $DeviceCACertificate)) {
Write-HostRed
Write-HostRed "Device CA certificate file $DeviceCACertificate not found. When configuring device certificates, a certificate file is required."
throw
}
if (-Not (Test-Path -Path $DeviceCAPrivateKey)) {
Write-HostRed
Write-HostRed "Device CA private key file $DeviceCAPrivateKey not found. When configuring device certificates, a private key file is required."
throw
}
if (-Not (Test-Path -Path $DeviceTrustbundle)) {
Write-HostRed
Write-HostRed "Device trustbundle file $DeviceTrustbundle not found. When configuring device certificates, a trust bundle file is required."
throw
}
$certFilesProvided = $true
}
return $certFilesProvided
}
function Get-DpsProvisioningSettings {
$idCertFilesProvided = $false
$attestationMethod = 'tpm' # default
if ($SymmetricKey) {
$attestationMethod = 'symmetric_key'
}
elseif ($X509IdentityCertificate -or $X509IdentityPrivateKey) {
$attestationMethod = 'x509'
$idCertFilesProvided = $true
}
if ($idCertFilesProvided) {
if ($RegistrationId) {
Write-HostYellow 'NOTE: RegistrationId is strictly not required for this DPS provisioning mode as it can be obtained from the identity certificate'
}
}
else {
if (-not $RegistrationId) {
Write-HostRed
Write-HostRed "RegistrationId is required for this DPS provisioning mode."
throw
}
}
if ($attestationMethod -eq 'x509') {
if (-Not (Test-Path -Path $X509IdentityCertificate)) {
Write-HostRed
Write-HostRed "Identity certificate file $X509IdentityCertificate not found."
throw
}
if (-Not (Test-Path -Path $X509IdentityPrivateKey)) {
Write-HostRed
Write-HostRed "Identity private file $X509IdentityPrivateKey not found."
throw
}
}
return $attestationMethod
}
function Set-ProvisioningMode {
Update-ConfigYaml({
param($configurationYaml)
if ($Manual -or $DeviceConnectionString) {
$selectionRegex = '(?:[^\S\n]*#[^\S\n]*)?provisioning:\s*#?\s*source:\s*".*"\s*#?\s*device_connection_string:\s*".*"'
$replacementContent = @(
'provisioning:',
' source: ''manual''',
" device_connection_string: '$DeviceConnectionString'")
$configurationYaml = ($configurationYaml -replace $selectionRegex, ($replacementContent -join "`n"))
Write-HostGreen 'Configured device for manual provisioning.'
return $configurationYaml
}
elseif ($External -or $ExternalProvisioningEndpoint){
$selectionRegex = '(?:[^\S\n]*#[^\S\n]*)?provisioning:\s*#?\s*source:\s*".*"\s*#?\s*endpoint:\s*".*"'
$replacementContent = @(
'provisioning:',
' source: ''external''',
" endpoint: '$ExternalProvisioningEndpoint'")
$configurationYaml = ($configurationYaml -replace $selectionRegex, ($replacementContent -join "`n"))
Write-HostGreen 'Configured device for external provisioning.'
return $configurationYaml
}
else {
$attestationMethod = Get-DpsProvisioningSettings
$selectionRegex = '(?:[^\S\n]*#[^\S\n]*)?provisioning:\s*#?\s*source:\s*".*"\s*#?\s*global_endpoint:\s*".*"\s*#?\s*scope_id:\s*".*"\s*#?\s*attestation:\s*#?\s*method:\s*"' + $attestationMethod + '"\s*#?\s*registration_id:\s*".*"'
if ($attestationMethod -eq 'symmetric_key') {
$selectionRegex += '\s*#?\s*symmetric_key:\s".*"'
} elseif ($attestationMethod -eq 'x509') {
$selectionRegex += '\s*#?\s*identity_cert:\s".*"\s*#?\s*identity_pk:\s".*"'
}
$replacementContent = @(
'provisioning:',
' source: ''dps''',
' global_endpoint: ''https://global.azure-devices-provisioning.net''',
" scope_id: '$ScopeId'",
" attestation:",
" method: '$attestationMethod'")
if ($RegistrationId) {
$replacementContent += " registration_id: '$RegistrationId'"
}
if ($SymmetricKey) {
$replacementContent += " symmetric_key: '$SymmetricKey'"
}
if ($X509IdentityCertificate) {
$uri = ([System.Uri][System.IO.Path]::GetFullPath($X509IdentityCertificate)).AbsoluteUri
$replacementContent += " identity_cert: '$uri'"
}
if ($X509IdentityPrivateKey) {
$uri = ([System.Uri][System.IO.Path]::GetFullPath($X509IdentityPrivateKey)).AbsoluteUri
$replacementContent += " identity_pk: '$uri'"
}
$configurationYaml = $configurationYaml -replace $selectionRegex, ($replacementContent -join "`n")
$selectionRegex = '(?:[^\S\n]*#[^\S\n]*)?provisioning:\s*#?\s*source:\s*".*"\s*#?\s*device_connection_string:\s*".*"'
$replacementContent = ''
$configurationYaml = ($configurationYaml -replace $selectionRegex, ($replacementContent -join "`n"))
Write-HostGreen 'Configured device for DPS provisioning.'
return $configurationYaml
}
})
}
function Set-Certificates {
if (Validate-GatewaySettings) {
Update-ConfigYaml({
param($configurationYaml)
$selectionRegex = '(?:[^\S\n]*#[^\S\n]*)?certificates:\s*#?\s*device_ca_cert:\s*".*"\s*#?\s*device_ca_pk:\s*".*"\s*#?\s*trusted_ca_certs:\s*".*"'
$certURI = ([System.Uri][System.IO.Path]::GetFullPath($DeviceCACertificate)).AbsoluteUri
$keyURI = ([System.Uri][System.IO.Path]::GetFullPath($DeviceCAPrivateKey)).AbsoluteUri
$tbURI = ([System.Uri][System.IO.Path]::GetFullPath($DeviceTrustbundle)).AbsoluteUri
$replacementContent = @(
"certificates:",
" device_ca_cert: '$certURI'",
" device_ca_pk: '$keyURI'",
" trusted_ca_certs: '$tbURI'")
$configurationYaml = ($configurationYaml -replace $selectionRegex, ($replacementContent -join "`n"))
Write-HostGreen 'Configured device for manual provisioning.'
return $configurationYaml
})
}
}
function Set-AgentImage {
if ($AgentImage) {
Update-ConfigYaml({
param($configurationYaml)
$selectionRegex = 'image:\s*".*"'
$replacementContent = "image: '$AgentImage'"
$configurationYaml = $configurationYaml -replace $selectionRegex, ($replacementContent -join "`n")
if ($Username -and $Password) {
$selectionRegex = '\n auth:\s*\{\s*\}'
$agentRegistry = Get-AgentRegistry
$cred = New-Object System.Management.Automation.PSCredential ($Username, $Password)
$replacementContent = @(
'',
' auth:',
" serveraddress: '$agentRegistry'",
" username: '$Username'",
" password: '$($cred.GetNetworkCredential().Password)'")
$configurationYaml = ($configurationYaml -replace $selectionRegex, ($replacementContent -join "`n"))
}
Write-HostGreen "Configured device with agent image '$AgentImage'."
return $configurationYaml
})
}
}
function Set-Hostname {
Update-ConfigYaml({
param($configurationYaml)
$hostname = [System.Net.Dns]::GetHostName()
$selectionRegex = 'hostname:\s*".*"'
$replacementContent = "hostname: '$hostname'"
$configurationYaml = ($configurationYaml -replace $selectionRegex, ($replacementContent -join "`n"))
Write-HostGreen "Configured device with hostname '$hostname'."
return $configurationYaml
})
}
function Set-ConfigUri([string] $Section, [string] $ManagementUri, [string] $WorkloadUri) {
Update-ConfigYaml({
param($configurationYaml)
$selectionRegex = ('{0}:\s*management_uri:\s*".*"\s*workload_uri:\s*".*"' -f $Section)
$replacementContent = @(
('{0}:' -f $Section),
" management_uri: '$ManagementUri'",
" workload_uri: '$WorkloadUri'")
$configurationYaml = $configurationYaml -replace $selectionRegex, ($replacementContent -join "`n")
return $configurationYaml
})
}
function Set-ListenConnectUri([string] $ManagementUri, [string] $WorkloadUri) {
Set-ConfigUri -Section 'connect' -ManagementUri $ManagementUri -WorkloadUri $WorkloadUri
Set-ConfigUri -Section 'listen' -ManagementUri $ManagementUri -WorkloadUri $WorkloadUri
Set-MachineEnvironmentVariable 'IOTEDGE_HOST' $ManagementUri
$env:IOTEDGE_HOST = $ManagementUri
}
function Set-GatewayAddress {
$gatewayAddress = (Get-NetIpAddress |
Where-Object {$_.InterfaceAlias -like '*vEthernet (DockerNAT)*' -and $_.AddressFamily -eq 'IPv4'}).IPAddress
Set-ListenConnectUri `
-ManagementUri "http://${gatewayAddress}:15580" `
-WorkloadUri "http://${gatewayAddress}:15581"
Write-HostGreen "Configured device with gateway address '$gatewayAddress'."
}
function Set-CorrectProgramData {
$forwardProgramData = $env:ProgramData -replace '\\', '/'
Set-ListenConnectUri `
-ManagementUri ('unix:///{0}/iotedge/mgmt/sock' -f $forwardProgramData) `
-WorkloadUri ('unix:///{0}/iotedge/workload/sock' -f $forwardProgramData)
Update-ConfigYaml({
param($configurationYaml)
$selectionRegex = 'homedir:\s".*"'
$replacementContent = ('homedir: "{0}"' -f ($EdgeDataDirectory -replace '\\', '\\'))
$configurationYaml = $configurationYaml -replace $selectionRegex, $replacementContent
Write-HostGreen 'Configured ProgramData directory.'
return $configurationYaml
})
}
function Set-MobyEngineParameters {
Update-ConfigYaml({
param($configurationYaml)
$selectionRegex = 'moby_runtime:\s*uri:\s*".*"\s*#?\s*network:\s*".*"'
$mobyUrl = switch ($ContainerOs) {
'Linux' {
$MobyLinuxNamedPipeUrl
}
'Windows' {
$MobyNamedPipeUrl
}
}
$mobyNetwork = switch ($ContainerOs) {
'Linux' {
'azure-iot-edge'
}
'Windows' {
'nat'
}
}
$replacementContent = @(
'moby_runtime:',
" uri: '$mobyUrl'",
" network: '$mobyNetwork'")
$configurationYaml = ($configurationYaml -replace $selectionRegex, ($replacementContent -join "`n"))
Write-HostGreen "Configured device with Moby Engine URL '$mobyUrl' and network '$mobyNetwork'."
return $configurationYaml
})
}
function Get-AgentRegistry {
$parts = $AgentImage -split '/'
if (($parts.Length -gt 1) -and ($parts[0] -match '\.')) {
return $parts[0]
}
return 'index.docker.io'
}
function Stop-EdgeContainer([string] $Name = '') {
$dockerExe = Get-DockerCommandPrefix
$allContainersString = Invoke-Native "$dockerExe ps --all --format ""{{.ID}}""" -Passthru
[string[]] $allContainers = $allContainersString -split {$_ -eq "`r" -or $_ -eq "`n"} | where {$_.Length -gt 0}
foreach ($containerId in $allContainers) {
$inspectString = Invoke-Native "$dockerExe inspect ""$containerId""" -Passthru
$inspectResult = ($inspectString | ConvertFrom-Json)[0]
if ($Name -and ($inspectResult.Name -ne $Name)) {
continue
}
$label = $inspectResult.Config.Labels | Get-Member -MemberType NoteProperty -Name 'net.azure-devices.edge.owner' | %{ $inspectResult.Config.Labels | Select-Object -ExpandProperty $_.Name }
if (($label -eq 'Microsoft.Azure.Devices.Edge.Agent')) {
if (($inspectResult.Name -eq '/edgeAgent') -or ($inspectResult.Name -eq '/edgeHub')) {
Invoke-Native "$dockerExe rm --force ""$containerId""" -Backoff
Write-Verbose "Stopped and deleted container $($inspectResult.Name)"
# ".Config.Image" contains the user-provided name, but this can be a tag like "foo:latest"
# that doesn't necessarily point to the image that has that tag right now.
#
# So delete the image using its unique identifier, as given by ".Image", like "sha256:1234abcd..."
Invoke-Native "$dockerExe image rm --force ""$($inspectResult.Image)""" -Backoff
Write-Verbose "Deleted image of container $($inspectResult.Name) ($($inspectResult.Config.Image))"
}
else {
Invoke-Native "$dockerExe stop ""$containerId"""
Write-Verbose "Stopped container $($inspectResult.Name)"
}
}
}
}
function Remove-IotEdgeContainers {
if (-not (Test-IsDockerRunning 6> $null)) {
return
}
# Need to stop Agent first so it does not restart other containers
Stop-EdgeContainer -Name '/edgeAgent'
Stop-EdgeContainer
if ($ContainerOs -eq "Windows") {
try {
$dockerExe = Get-DockerCommandPrefix
Invoke-Native "$dockerExe container prune -f"
Invoke-Native "$dockerExe image prune -f -a"
Invoke-Native "$dockerExe system prune -f -a"
}
catch {
# Best effort
}
}
}
function Get-DockerCommandPrefix {
switch ($ContainerOs) {
'Linux' {
return '"docker"'
}
'Windows' {
# docker needs two more slashes after the scheme
$namedPipeUrl = $MobyNamedPipeUrl -replace 'npipe://\./pipe/', 'npipe:////./pipe/'
$prefix = ""
# in case the installation has not been completed
if (-not (Get-Command "docker.exe" -ErrorAction SilentlyContinue)) {
$prefix = "$MobyInstallDirectory\"
}
return ('"{0}docker" -H "{1}"' -f $prefix, $namedPipeUrl)
}
}
}
function Invoke-Native {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[String] $Command,
[Switch] $Passthru,
[Switch] $DoNotThrow,
[Switch] $Backoff
)
process {
Write-Verbose "Executing native Windows command '$Command'..."
$sleep = 1
for ($i = 0; $i -lt 5; $i++) {
if ($i -ne 0) {
Start-Sleep -Seconds $sleep
$sleep *= 2
Write-Verbose 'Retrying...'
}
$out = cmd /c "($Command) 2>&1" 2>&1 | Out-String
Write-Verbose $out
Write-Verbose "Exit code: $LASTEXITCODE"
if (($LASTEXITCODE -eq 0) -or (-not $Backoff)) {
break
}
}
if (($LASTEXITCODE -ne 0) -and (-not $DoNotThrow)) {
throw ("Failed to execute '{0}': `n{1}" -f $Command, $out)
}
elseif ($Passthru) {
return $out
}
}
}
function Test-IotCore {
(Get-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion').'EditionID' -eq 'IoTUAP'
}
function Write-HostGreen {
Write-Host -ForegroundColor Green @args
}
function Write-HostRed {
Write-Host -ForegroundColor Red @args
}
function Write-HostYellow {
Write-Host -ForegroundColor Yellow @args
}
function Remove-BuiltinWritePermissions([string] $Path) {
$sid = New-Object System.Security.Principal.SecurityIdentifier 'S-1-5-32-545' # BUILTIN\Users
Write-Verbose "Remove BUILTIN\Users permission to $Path"
Invoke-Native "icacls ""$Path"" /inheritance:d"
$acl = Get-Acl -Path $Path
$write = [System.Security.AccessControl.FileSystemRights]::Write
foreach ($access in $acl.Access) {
$accessSid = $access.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier])
if ($accessSid -eq $sid -and
$access.AccessControlType -eq 'Allow' -and
($access.FileSystemRights -band $write) -eq $write)
{
$rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule(`
$sid, 'Write', $access.InheritanceFlags, $access.PropagationFlags, 'Allow')
$acl.RemoveAccessRule($rule) | Out-Null
}
}
Set-Acl -Path $Path -AclObject $acl
}
function Download-File([string] $Description, [string] $Url, [string] $DownloadFilename, [string] $LocalCacheGlob, [ref] $Delete) {
if ($OfflineInstallationPath -ne '') {
if (-not (Test-Path "$OfflineInstallationPath\$LocalCacheGlob")) {
Write-HostRed
Write-HostRed "Could not find $Description at $OfflineInstallationPath\$LocalCacheGlob"
Write-HostRed "Please download it from $Url and save it under $OfflineInstallationPath"
throw
}
$result = (Get-Item "$OfflineInstallationPath\$LocalCacheGlob" | Select-Object -First 1).FullName
$Delete.Value = $false
}
else {
Write-Host "Downloading $Description..."
$OldProgressPreference = $ProgressPreference
$ProgressPreference = 'SilentlyContinue'
$outFile = (Join-Path -Path $env:TEMP -ChildPath $DownloadFileName)
try {
Invoke-WebRequest `
-Uri $Url `
-OutFile $outFile `
-UseBasicParsing `
@InvokeWebRequestParameters
}
finally {
$ProgressPreference = $OldProgressPreference
}
$Delete.Value = $true
$result = $outFile
}
Write-HostGreen "Using $Description from $result"
return $result
}
<#
.SYNOPSIS
Import intermediate certificates into Windows certificate store.
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
PS> Import-IntermediateCertChain $DeviceIdentityCertPath
#>
function Import-IntermediateCertChain ([string] $DeviceIdentityCertPath){
$certificateStore = New-Object System.Security.Cryptography.X509Certificates.X509Store ([System.Security.Cryptography.X509Certificates.StoreName]::CertificateAuthority, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine)
$certificateStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite);
$certEnd = "END CERTIFICATE"
$certs = New-Object System.Collections.ArrayList
$currentCert = New-Object System.Text.StringBuilder
foreach ($line in [System.IO.File]::ReadLines($DeviceIdentityCertPath)) {
if ($line -Match $certEnd){
[void]$currentCert.AppendLine($line)
[void]$certs.Add($currentCert.ToString())
[void]$currentCert.Clear()
}
else {
[void]$currentCert.AppendLine($line)
}
}
# Drop the first certificate and add all remaining to certificate store
$enc = [System.Text.Encoding]::UTF8
for ($i = 1; $i -lt $certs.Count; $i++) {
$bytes = $enc.GetBytes($certs[$i])
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(,$bytes)
$certificateStore.Add($cert)
}
$certificateStore.Dispose();
}
New-Alias -Name Install-SecurityDaemon -Value Install-IoTEdge -Force
New-Alias -Name Uninstall-SecurityDaemon -Value Uninstall-IoTEdge -Force
Export-ModuleMember `
-Function `
Deploy-IoTEdge,
Initialize-IoTEdge,
Get-IoTEdgeLog,
Update-IoTEdge,
Install-IoTEdge,
Import-IntermediateCertChain,
Uninstall-IoTEdge `
-Alias `
Install-SecurityDaemon,
Uninstall-SecurityDaemon
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment