diff --git a/arc_data_services/charts/arcdataservices/values.yaml b/arc_data_services/charts/arcdataservices/values.yaml index b9680220df..a6fc4b4a4f 100644 --- a/arc_data_services/charts/arcdataservices/values.yaml +++ b/arc_data_services/charts/arcdataservices/values.yaml @@ -38,7 +38,7 @@ Azure: InstallerServiceAccount: "" RuntimeServiceAccount: "" systemDefaultValues: - image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.28.0_2024-03-12 + image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.29.0_2024-04-09 imagePullPolicy: Always imagePullSecret: arc-private-registry installerServiceAccount: "" diff --git a/arc_data_services/deploy/scripts/pull-and-push-arc-data-services-images-to-private-registry.py b/arc_data_services/deploy/scripts/pull-and-push-arc-data-services-images-to-private-registry.py index cb96c1f94d..389737bdd8 100644 --- a/arc_data_services/deploy/scripts/pull-and-push-arc-data-services-images-to-private-registry.py +++ b/arc_data_services/deploy/scripts/pull-and-push-arc-data-services-images-to-private-registry.py @@ -54,9 +54,9 @@ def execute_cmd(cmd): if os.getenv("SOURCE_DOCKER_TAG") is None: SOURCE_DOCKER_TAG = ( input( - "Provide container image tag for the images at the source - press ENTER for using 'v1.28.0_2024-03-12': " + "Provide container image tag for the images at the source - press ENTER for using 'v1.29.0_2024-04-09': " ) - or "v1.28.0_2024-03-12" + or "v1.29.0_2024-04-09" ) else: SOURCE_DOCKER_TAG = os.environ["SOURCE_DOCKER_TAG"] diff --git a/arc_data_services/deploy/yaml/bootstrapper-unified.yaml b/arc_data_services/deploy/yaml/bootstrapper-unified.yaml index 0f165f75f3..8ab5fc844c 100644 --- a/arc_data_services/deploy/yaml/bootstrapper-unified.yaml +++ b/arc_data_services/deploy/yaml/bootstrapper-unified.yaml @@ -331,11 +331,11 @@ spec: kubernetes.io/os: linux containers: - name: bootstrapper - image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.28.0_2024-03-12 + image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.29.0_2024-04-09 imagePullPolicy: Always args: - -image - - mcr.microsoft.com/arcdata/arc-bootstrapper:v1.28.0_2024-03-12 + - mcr.microsoft.com/arcdata/arc-bootstrapper:v1.29.0_2024-04-09 - -policy - Always - -chart diff --git a/arc_data_services/deploy/yaml/bootstrapper.yaml b/arc_data_services/deploy/yaml/bootstrapper.yaml index c213cea64a..aeeac5983a 100644 --- a/arc_data_services/deploy/yaml/bootstrapper.yaml +++ b/arc_data_services/deploy/yaml/bootstrapper.yaml @@ -9,11 +9,11 @@ spec: kubernetes.io/os: linux containers: - name: bootstrapper - image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.28.0_2024-03-12 + image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.29.0_2024-04-09 imagePullPolicy: Always args: - -image - - mcr.microsoft.com/arcdata/arc-bootstrapper:v1.28.0_2024-03-12 + - mcr.microsoft.com/arcdata/arc-bootstrapper:v1.29.0_2024-04-09 - -policy - Always - -chart diff --git a/arc_data_services/deploy/yaml/data-controller.yaml b/arc_data_services/deploy/yaml/data-controller.yaml index 46be55a91c..f33bea86e2 100644 --- a/arc_data_services/deploy/yaml/data-controller.yaml +++ b/arc_data_services/deploy/yaml/data-controller.yaml @@ -30,7 +30,7 @@ spec: serviceAccount: sa-arc-controller docker: imagePullPolicy: Always - imageTag: v1.28.0_2024-03-12 + imageTag: v1.29.0_2024-04-09 registry: mcr.microsoft.com repository: arcdata infrastructure: other # Must be a value in the array [alibaba, aws, azure, gcp, onpremises, other] diff --git a/arc_data_services/deploy/yaml/uninstall.yaml b/arc_data_services/deploy/yaml/uninstall.yaml index 8c2837d009..0c002d6310 100644 --- a/arc_data_services/deploy/yaml/uninstall.yaml +++ b/arc_data_services/deploy/yaml/uninstall.yaml @@ -9,7 +9,7 @@ spec: kubernetes.io/os: linux containers: - name: bootstrapper - image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.28.0_2024-03-12 + image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.29.0_2024-04-09 imagePullPolicy: IfNotPresent args: ["-uninstall"] command: ["/opt/bootstrapper/bin/bootstrapper"] diff --git a/arc_data_services/test/launcher/base/kustomization.yaml b/arc_data_services/test/launcher/base/kustomization.yaml index a6c7c628ff..376eb79cb2 100644 --- a/arc_data_services/test/launcher/base/kustomization.yaml +++ b/arc_data_services/test/launcher/base/kustomization.yaml @@ -10,4 +10,4 @@ secretGenerator: images: - name: arc-ci-launcher newName: mcr.microsoft.com/arcdata/arc-ci-launcher - newTag: v1.28.0_2024-03-12 + newTag: v1.29.0_2024-04-09 diff --git a/arc_data_services/upgrade/yaml/bootstrapper-upgrade-job.yaml b/arc_data_services/upgrade/yaml/bootstrapper-upgrade-job.yaml index 6fc69b752c..bfdc579438 100644 --- a/arc_data_services/upgrade/yaml/bootstrapper-upgrade-job.yaml +++ b/arc_data_services/upgrade/yaml/bootstrapper-upgrade-job.yaml @@ -11,10 +11,10 @@ spec: - name: your-private-registry containers: - name: bootstrapper - image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.28.0_2024-03-12 + image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.29.0_2024-04-09 imagePullPolicy: Always command: ["/opt/bootstrapper/bin/bootstrapper"] - args: ["-image", "mcr.microsoft.com/arcdata/arc-bootstrapper:v1.28.0_2024-03-12", "-policy", "Always", "-chart", "/opt/helm/arcdataservices", "-bootstrap"] + args: ["-image", "mcr.microsoft.com/arcdata/arc-bootstrapper:v1.29.0_2024-04-09", "-policy", "Always", "-chart", "/opt/helm/arcdataservices", "-bootstrap"] resources: limits: cpu: 200m diff --git a/arc_data_services/upgrade/yaml/data-controller-upgrade.yaml b/arc_data_services/upgrade/yaml/data-controller-upgrade.yaml index 8ce060ac4e..5a397921ab 100644 --- a/arc_data_services/upgrade/yaml/data-controller-upgrade.yaml +++ b/arc_data_services/upgrade/yaml/data-controller-upgrade.yaml @@ -4,4 +4,4 @@ metadata: name: arc spec: docker: - imageTag: v1.28.0_2024-03-12 + imageTag: v1.29.0_2024-04-09 diff --git a/arc_data_services/upgrade/yaml/request_body/request-body-after.yaml b/arc_data_services/upgrade/yaml/request_body/request-body-after.yaml index 8b601fed08..9e2f91488b 100644 --- a/arc_data_services/upgrade/yaml/request_body/request-body-after.yaml +++ b/arc_data_services/upgrade/yaml/request_body/request-body-after.yaml @@ -27,7 +27,7 @@ }, "docker": { "imagePullPolicy": "Always", - "imageTag": "v1.28.0_2024-03-12", + "imageTag": "v1.29.0_2024-04-09", "registry": "", "repository": "" }, diff --git a/arc_data_services/upgrade/yaml/request_body/request-body-before.yaml b/arc_data_services/upgrade/yaml/request_body/request-body-before.yaml index fa39cbaf74..8eb571c576 100644 --- a/arc_data_services/upgrade/yaml/request_body/request-body-before.yaml +++ b/arc_data_services/upgrade/yaml/request_body/request-body-before.yaml @@ -25,7 +25,7 @@ }, "docker": { "imagePullPolicy": "Always", - "imageTag": "v1.28.0_2024-03-12", + "imageTag": "v1.29.0_2024-04-09", "registry": "", "repository": "" }, diff --git a/azure_arc_data_jumpstart/aks/ARM/artifacts/DataServicesLogonScript.ps1 b/azure_arc_data_jumpstart/aks/ARM/artifacts/DataServicesLogonScript.ps1 index dfd7adb058..edd5dcf7ac 100644 --- a/azure_arc_data_jumpstart/aks/ARM/artifacts/DataServicesLogonScript.ps1 +++ b/azure_arc_data_jumpstart/aks/ARM/artifacts/DataServicesLogonScript.ps1 @@ -116,7 +116,7 @@ az k8s-extension create --name arc-data-services ` --resource-group $Env:resourceGroup ` --auto-upgrade false ` --scope cluster ` - --version 1.28.0 ` + --version 1.29.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper diff --git a/azure_arc_data_jumpstart/aks/DR/ARM/artifacts/DataServicesLogonScript.ps1 b/azure_arc_data_jumpstart/aks/DR/ARM/artifacts/DataServicesLogonScript.ps1 index 924b66a081..46796fedec 100644 --- a/azure_arc_data_jumpstart/aks/DR/ARM/artifacts/DataServicesLogonScript.ps1 +++ b/azure_arc_data_jumpstart/aks/DR/ARM/artifacts/DataServicesLogonScript.ps1 @@ -135,7 +135,7 @@ az k8s-extension create --name arc-data-services ` --resource-group $Env:resourceGroup ` --auto-upgrade false ` --scope cluster ` - --version 1.28.0 ` + --version 1.29.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper ` @@ -229,7 +229,7 @@ az k8s-extension create --name arc-data-services ` --resource-group $Env:resourceGroup ` --auto-upgrade false ` --scope cluster ` - --version 1.28.0 ` + --version 1.29.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper ` diff --git a/azure_arc_data_jumpstart/aks/Migration/ARM/artifacts/DataServicesLogonScript.ps1 b/azure_arc_data_jumpstart/aks/Migration/ARM/artifacts/DataServicesLogonScript.ps1 index 91992b487f..39626071fe 100644 --- a/azure_arc_data_jumpstart/aks/Migration/ARM/artifacts/DataServicesLogonScript.ps1 +++ b/azure_arc_data_jumpstart/aks/Migration/ARM/artifacts/DataServicesLogonScript.ps1 @@ -124,11 +124,10 @@ Set-VMHost -EnableEnhancedSessionMode $true Write-Host "Fetching Nested VMs" Write-Host "`n" $sourceFolder = 'https://jsvhds.blob.core.windows.net/arcbox' -$sas = "?si=ArcBox-RL&spr=https&sv=2022-11-02&sr=c&sig=vg8VRjM00Ya%2FGa5izAq3b0axMpR4ylsLsQ8ap3BhrnA%3D" $Env:AZCOPY_BUFFER_GB=4 Write-Output "Downloading nested VMs VHDX file for SQL. This can take some time, hold tight..." -azcopy cp "$sourceFolder/ArcBox-SQL.vhdx$sas" "$Env:ArcBoxVMDir\ArcBox-SQL.vhdx" --check-length=false --cap-mbps 1200 --log-level=ERROR +azcopy cp "$sourceFolder/ArcBox-SQL.vhdx" "$Env:ArcBoxVMDir\ArcBox-SQL.vhdx" --check-length=false --cap-mbps 1200 --log-level=ERROR # Create the nested SQL VM @@ -225,7 +224,7 @@ az k8s-extension create --name arc-data-services ` --resource-group $Env:resourceGroup ` --auto-upgrade false ` --scope cluster ` - --version 1.28.0 ` + --version 1.29.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper ` diff --git a/azure_arc_data_jumpstart/cluster_api/capi_azure/ARM/artifacts/DataServicesLogonScript.ps1 b/azure_arc_data_jumpstart/cluster_api/capi_azure/ARM/artifacts/DataServicesLogonScript.ps1 index 6c8795f082..b616d1e85b 100644 --- a/azure_arc_data_jumpstart/cluster_api/capi_azure/ARM/artifacts/DataServicesLogonScript.ps1 +++ b/azure_arc_data_jumpstart/cluster_api/capi_azure/ARM/artifacts/DataServicesLogonScript.ps1 @@ -110,7 +110,7 @@ az k8s-extension create --name arc-data-services ` --resource-group $Env:resourceGroup ` --auto-upgrade false ` --scope cluster ` - --version 1.28.0 ` + --version 1.29.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper ` diff --git a/azure_arc_data_jumpstart/eks/terraform/artifacts/DataServicesLogonScript.ps1 b/azure_arc_data_jumpstart/eks/terraform/artifacts/DataServicesLogonScript.ps1 index 24747b565b..57f7949d49 100644 --- a/azure_arc_data_jumpstart/eks/terraform/artifacts/DataServicesLogonScript.ps1 +++ b/azure_arc_data_jumpstart/eks/terraform/artifacts/DataServicesLogonScript.ps1 @@ -134,7 +134,7 @@ az k8s-extension create --name arc-data-services ` --resource-group $Env:resourceGroup ` --auto-upgrade false ` --scope cluster ` - --version 1.28.0 ` + --version 1.29.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper diff --git a/azure_arc_data_jumpstart/gke/terraform/artifacts/DataServicesLogonScript.ps1 b/azure_arc_data_jumpstart/gke/terraform/artifacts/DataServicesLogonScript.ps1 index 46037c28de..da00574d29 100644 --- a/azure_arc_data_jumpstart/gke/terraform/artifacts/DataServicesLogonScript.ps1 +++ b/azure_arc_data_jumpstart/gke/terraform/artifacts/DataServicesLogonScript.ps1 @@ -123,7 +123,7 @@ az k8s-extension create --name arc-data-services ` --resource-group $env:resourceGroup ` --auto-upgrade false ` --scope cluster ` - --version 1.28.0 ` + --version 1.29.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper ` diff --git a/azure_arc_data_jumpstart/kubeadm/azure/ARM/artifacts/DataServicesLogonScript.ps1 b/azure_arc_data_jumpstart/kubeadm/azure/ARM/artifacts/DataServicesLogonScript.ps1 index cbf6967fa6..2f65be8c47 100644 --- a/azure_arc_data_jumpstart/kubeadm/azure/ARM/artifacts/DataServicesLogonScript.ps1 +++ b/azure_arc_data_jumpstart/kubeadm/azure/ARM/artifacts/DataServicesLogonScript.ps1 @@ -146,7 +146,7 @@ az k8s-extension create --name arc-data-services ` --resource-group $Env:resourceGroup ` --auto-upgrade false ` --scope cluster ` - --version 1.28.0 ` + --version 1.29.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper diff --git a/azure_arc_data_jumpstart/microk8s/azure/arm_template/artifacts/DataServicesLogonScript.ps1 b/azure_arc_data_jumpstart/microk8s/azure/arm_template/artifacts/DataServicesLogonScript.ps1 index e3be3a1931..8a483b03d2 100644 --- a/azure_arc_data_jumpstart/microk8s/azure/arm_template/artifacts/DataServicesLogonScript.ps1 +++ b/azure_arc_data_jumpstart/microk8s/azure/arm_template/artifacts/DataServicesLogonScript.ps1 @@ -112,7 +112,7 @@ az k8s-extension create --name arc-data-services ` --resource-group $Env:resourceGroup ` --auto-upgrade false ` --scope cluster ` - --version 1.28.0 ` + --version 1.29.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper ` diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/L1Files/ScalableCluster.json b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/L1Files/ScalableCluster.json index c446d2550f..aceb28477b 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/L1Files/ScalableCluster.json +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/L1Files/ScalableCluster.json @@ -40,6 +40,7 @@ "CpuCount": 4, "MemoryInMB": 4096, "DataSizeInGB": 20, + "LogSizeInGB": 5, "Ip4Address": "Ip4Address-null", "TimeoutSeconds": 300, "TpmPassthrough": false diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/L1Files/ScalableClusterAdd-k3s.json b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/L1Files/ScalableClusterAdd-k3s.json index 8190fc601b..ea22391c2b 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/L1Files/ScalableClusterAdd-k3s.json +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/L1Files/ScalableClusterAdd-k3s.json @@ -31,6 +31,7 @@ "CpuCount": 4, "MemoryInMB": 4096, "DataSizeInGB": 20, + "LogSizeInGB": 5, "Ip4Address": "Ip4Address-null", "TimeoutSeconds": 300, "TpmPassthrough": false diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/L1Files/ScalableClusterAdd-k8s.json b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/L1Files/ScalableClusterAdd-k8s.json index f24cc753c7..f2af3e5795 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/L1Files/ScalableClusterAdd-k8s.json +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/L1Files/ScalableClusterAdd-k8s.json @@ -32,6 +32,7 @@ "CpuCount": 4, "MemoryInMB": 4096, "DataSizeInGB": 20, + "LogSizeInGB": 5, "Ip4Address": "Ip4Address-null", "TimeoutSeconds": 300, "TpmPassthrough": false diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/L1Files/ScalableCluster.json b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/L1Files/ScalableCluster.json index c446d2550f..aceb28477b 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/L1Files/ScalableCluster.json +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/L1Files/ScalableCluster.json @@ -40,6 +40,7 @@ "CpuCount": 4, "MemoryInMB": 4096, "DataSizeInGB": 20, + "LogSizeInGB": 5, "Ip4Address": "Ip4Address-null", "TimeoutSeconds": 300, "TpmPassthrough": false diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/L1Files/ScalableClusterAdd-k3s.json b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/L1Files/ScalableClusterAdd-k3s.json index 8190fc601b..ea22391c2b 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/L1Files/ScalableClusterAdd-k3s.json +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/L1Files/ScalableClusterAdd-k3s.json @@ -31,6 +31,7 @@ "CpuCount": 4, "MemoryInMB": 4096, "DataSizeInGB": 20, + "LogSizeInGB": 5, "Ip4Address": "Ip4Address-null", "TimeoutSeconds": 300, "TpmPassthrough": false diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/L1Files/ScalableClusterAdd-k8s.json b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/L1Files/ScalableClusterAdd-k8s.json index f24cc753c7..f2af3e5795 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/L1Files/ScalableClusterAdd-k8s.json +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/L1Files/ScalableClusterAdd-k8s.json @@ -32,6 +32,7 @@ "CpuCount": 4, "MemoryInMB": 4096, "DataSizeInGB": 20, + "LogSizeInGB": 5, "Ip4Address": "Ip4Address-null", "TimeoutSeconds": 300, "TpmPassthrough": false diff --git a/azure_arc_servers_jumpstart/azure_stack_hci/powershell/azstack_hci_vm_deploy.ps1 b/azure_arc_servers_jumpstart/azure_stack_hci/powershell/azstack_hci_vm_deploy.ps1 index ffe44bd04a..b84ada8172 100644 --- a/azure_arc_servers_jumpstart/azure_stack_hci/powershell/azstack_hci_vm_deploy.ps1 +++ b/azure_arc_servers_jumpstart/azure_stack_hci/powershell/azstack_hci_vm_deploy.ps1 @@ -45,8 +45,7 @@ choco install azcopy10 New-Item -Path $nodepath -Name "ArcJumpstart" -ItemType "directory" Write-Verbose "Downloading Windows Server VHDX file. Hold tight, this might take a few minutes..." -Verbose $sourceFolder = 'https://jsvhds.blob.core.windows.net/scenarios/prod/ArcVM-HCIJS-win.vhdx' -$sas = "?sp=r&st=2023-08-10T19:01:34Z&se=2033-08-11T03:01:34Z&spr=https&sv=2022-11-02&sr=b&sig=ieWak5x0UZMrPBjJJ4BMR5RzXbhjUiGDofN0R3cRtZ0%3D" -azcopy cp $sourceFolder/*$sas $nodepath\ArcJumpstart\ArcVM-HCIJS-win.vhdx +azcopy cp $sourceFolder $nodepath\ArcJumpstart\ArcVM-HCIJS-win.vhdx # Enable CredSSP Set-Item WSMAN:\localhost\client\auth\credssp –value $true diff --git a/azure_arc_sqlsrv_jumpstart/azure/windows/defender_sql/arm_template/scripts/ArcServersLogonScript.ps1 b/azure_arc_sqlsrv_jumpstart/azure/windows/defender_sql/arm_template/scripts/ArcServersLogonScript.ps1 index 9ffb0e74b6..91df508d92 100644 --- a/azure_arc_sqlsrv_jumpstart/azure/windows/defender_sql/arm_template/scripts/ArcServersLogonScript.ps1 +++ b/azure_arc_sqlsrv_jumpstart/azure/windows/defender_sql/arm_template/scripts/ArcServersLogonScript.ps1 @@ -8,7 +8,6 @@ $Env:tempDir = "C:\Temp" # VHD storage details $sourceFolder = "https://jsvhds.blob.core.windows.net/scenarios/prod" -$sas = "?si=JS-RL&spr=https&sv=2022-11-02&sr=c&sig=fIIeEliw5nG78oR6TBCvM70VMz9WXhpF41wdDoOlE8U%3D" $logFilePath = "$Env:ArcJSLogsDir\ArcServersLogonScript.log" if ([System.IO.File]::Exists($logFilePath)) { @@ -146,7 +145,7 @@ elseif ($Env:sqlServerEdition -eq "Enterprise"){ $SQLvmvhdPath = "$Env:ArcJSVMDir\JS-Win-SQL-01.vhdx" if (!([System.IO.File]::Exists($SQLvmvhdPath) )) { - $vhdImageUrl = "$sourceFolder/$vhdImageToDownload$sas" + $vhdImageUrl = "$sourceFolder/$vhdImageToDownload" azcopy cp $vhdImageUrl $SQLvmvhdPath --recursive=true --check-length=false --log-level=ERROR } diff --git a/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/PowerShell/LogonScript.ps1 b/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/PowerShell/LogonScript.ps1 index fd4b1596a4..b6070a6ce8 100644 --- a/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/PowerShell/LogonScript.ps1 +++ b/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/PowerShell/LogonScript.ps1 @@ -437,14 +437,16 @@ $extensionPrincipalId = (az k8s-extension show --cluster-name $arcClusterName -- $eventGridTopicId = (az eventgrid topic list --resource-group $resourceGroup --query "[0].id" -o tsv --only-show-errors) $eventGridNamespaceName = (az eventgrid namespace list --resource-group $resourceGroup --query "[0].name" -o tsv --only-show-errors) $eventGridNamespaceId = (az eventgrid namespace list --resource-group $resourceGroup --query "[0].id" -o tsv --only-show-errors) +$eventGridNamespacePrincipalId = (az eventgrid namespace list --resource-group $resourceGroup -o json --only-show-errors | ConvertFrom-Json)[0].identity.principalId az role assignment create --assignee-object-id $extensionPrincipalId --role "EventGrid Data Sender" --scope $eventGridTopicId --assignee-principal-type ServicePrincipal --only-show-errors -az role assignment create --assignee-object-id $spnObjectId --role "EventGrid Data Sender" --scope $eventGridTopicId --assignee-principal-type ServicePrincipal --only-show-errors +az role assignment create --assignee-object-id $eventGridNamespacePrincipalId --role "EventGrid Data Sender" --scope $eventGridTopicId --assignee-principal-type ServicePrincipal --only-show-errors az role assignment create --assignee-object-id $extensionPrincipalId --role "EventGrid TopicSpaces Subscriber" --scope $eventGridNamespaceId --assignee-principal-type ServicePrincipal --only-show-errors az role assignment create --assignee-object-id $extensionPrincipalId --role 'EventGrid TopicSpaces Publisher' --scope $eventGridNamespaceId --assignee-principal-type ServicePrincipal --only-show-errors az role assignment create --assignee-object-id $extensionPrincipalId --role "EventGrid TopicSpaces Subscriber" --scope $eventGridTopicId --assignee-principal-type ServicePrincipal --only-show-errors az role assignment create --assignee-object-id $extensionPrincipalId --role 'EventGrid TopicSpaces Publisher' --scope $eventGridTopicId --assignee-principal-type ServicePrincipal --only-show-errors +Start-Sleep -Seconds 60 Write-Host "[$(Get-Date -Format t)] INFO: Configuring routing to use system-managed identity" -ForegroundColor DarkGray $eventGridConfig = "{routing-identity-info:{type:'SystemAssigned'}}" diff --git a/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/Settings/mq_cloudConnector.yml b/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/Settings/mq_cloudConnector.yml index 58bd54859b..6c111f134d 100644 --- a/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/Settings/mq_cloudConnector.yml +++ b/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/artifacts/Settings/mq_cloudConnector.yml @@ -18,8 +18,8 @@ metadata: namespace: azure-iot-operations spec: image: - repository: e4kpreview.azurecr.io/mqttbridge - tag: 0.1.0-preview-rc6 + repository: mcr.microsoft.com/azureiotoperations/mqttbridge + tag: 0.1.0-preview pullPolicy: IfNotPresent protocol: v5 bridgeInstances: 1 diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/Bootstrap.ps1 b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/Bootstrap.ps1 new file mode 100644 index 0000000000..b7b3ca4bf4 --- /dev/null +++ b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/Bootstrap.ps1 @@ -0,0 +1,99 @@ +param ( + [string]$adminUsername, + [string]$appId, + [string]$password, + [string]$tenantId, + [string]$subscriptionId, + [string]$location, + [string]$templateBaseUrl, + [string]$resourceGroup, + [string]$windowsNode, + [string]$kubernetesDistribution, + [string]$storageAccountName, + [string]$storageContainer +) +[System.Environment]::SetEnvironmentVariable('adminUsername', $adminUsername,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('appId', $appId,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('password', $password,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('tenantId', $tenantId,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('resourceGroup', $resourceGroup,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('location', $location,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('subscriptionId', $subscriptionId,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('templateBaseUrl', $templateBaseUrl,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('kubernetesDistribution', $kubernetesDistribution,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('windowsNode', $windowsNode,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('storageAccountName', $storageAccountName,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('storageContainer', $storageContainer,[System.EnvironmentVariableTarget]::Machine) + +# Create path +Write-Output "Create deployment path" +$tempDir = "C:\Temp" +New-Item -Path $tempDir -ItemType directory -Force + +Start-Transcript "C:\Temp\Bootstrap.log" + +$ErrorActionPreference = "SilentlyContinue" + +# Downloading GitHub artifacts +Invoke-WebRequest ($templateBaseUrl + "artifacts/LogonScript.ps1") -OutFile "C:\Temp\LogonScript.ps1" +Invoke-WebRequest "https://raw.githubusercontent.com/Azure/arc_jumpstart_docs/main/img/wallpaper/jumpstart_wallpaper_dark.png" -OutFile "C:\Temp\wallpaper.png" + +# Installing tools +Write-Host "Installing Chocolatey Apps" +try { + choco config get cacheLocation +} +catch { + Write-Output "Chocolatey not detected, trying to install now" + Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) +} + +$chocolateyAppList = 'az.powershell,kubernetes-cli,kubernetes-helm' +$appsToInstall = $chocolateyAppList -split "," | ForEach-Object { "$($_.Trim())" } + +foreach ($app in $appsToInstall) { + Write-Host "Installing $app" + & choco install $app /y -Force | Write-Output +} + +Write-Host "Install Azure CLI (64-bit not available via Chocolatey)" +$ProgressPreference = 'SilentlyContinue' +Invoke-WebRequest -Uri https://aka.ms/installazurecliwindowsx64 -OutFile .\AzureCLI.msi +Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet' +Remove-Item .\AzureCLI.msi + +# Enable VirtualMachinePlatform feature, the vm reboot will be done in DSC extension +Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -NoRestart + +# Disable Microsoft Edge sidebar +$RegistryPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Edge' +$Name = 'HubsSidebarEnabled' +$Value = '00000000' +# Create the key if it does not exist +If (-NOT (Test-Path $RegistryPath)) { + New-Item -Path $RegistryPath -Force | Out-Null +} +New-ItemProperty -Path $RegistryPath -Name $Name -Value $Value -PropertyType DWORD -Force + +# Disable Microsoft Edge first-run Welcome screen +$RegistryPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Edge' +$Name = 'HideFirstRunExperience' +$Value = '00000001' +# Create the key if it does not exist +If (-NOT (Test-Path $RegistryPath)) { + New-Item -Path $RegistryPath -Force | Out-Null +} +New-ItemProperty -Path $RegistryPath -Name $Name -Value $Value -PropertyType DWORD -Force + +# Creating scheduled task for LogonScript.ps1 +$Trigger = New-ScheduledTaskTrigger -AtLogOn +$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument 'C:\Temp\LogonScript.ps1' +Register-ScheduledTask -TaskName "LogonScript" -Trigger $Trigger -User $adminUsername -Action $Action -RunLevel "Highest" -Force + +# Disabling Windows Server Manager Scheduled Task +Get-ScheduledTask -TaskName ServerManager | Disable-ScheduledTask + +# Clean up Bootstrap.log +Stop-Transcript +$logSuppress = Get-Content C:\Temp\Bootstrap.log | Where { $_ -notmatch "Host Application: powershell.exe" } +$logSuppress | Set-Content C:\Temp\Bootstrap.log -Force diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/DSCInstallWindowsFeatures.zip b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/DSCInstallWindowsFeatures.zip new file mode 100644 index 0000000000..bc61f0f7e6 Binary files /dev/null and b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/DSCInstallWindowsFeatures.zip differ diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/LogonScript.ps1 b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/LogonScript.ps1 new file mode 100644 index 0000000000..44746496bc --- /dev/null +++ b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/LogonScript.ps1 @@ -0,0 +1,438 @@ +Start-Transcript -Path C:\Temp\LogonScript.log + +## Deploy AKS EE + +# Parameters +$schemaVersion = "1.1" +$versionAksEdgeConfig = "1.0" +$aksEdgeDeployModules = "main" +$aksEEReleasesUrl = "https://api.github.com/repos/Azure/AKS-Edge/releases" +# Requires -RunAsAdministrator + + +if (! [Environment]::Is64BitProcess) { + Write-Host "Error: Run this in 64bit Powershell session" -ForegroundColor Red + exit -1 +} + +if ($env:kubernetesDistribution -eq "k8s") { + $productName = "AKS Edge Essentials - K8s" + $networkplugin = "calico" +} else { + $productName = "AKS Edge Essentials - K3s" + $networkplugin = "flannel" +} + +Write-Host "Fetching the latest AKS Edge Essentials release." +$latestReleaseTag = (Invoke-WebRequest $aksEEReleasesUrl | ConvertFrom-Json)[0].tag_name + +$AKSEEReleaseDownloadUrl = "https://github.com/Azure/AKS-Edge/archive/refs/tags/$latestReleaseTag.zip" +$output = Join-Path "C:\temp" "$latestReleaseTag.zip" +Invoke-WebRequest $AKSEEReleaseDownloadUrl -OutFile $output +Expand-Archive $output -DestinationPath "C:\temp" -Force +$AKSEEReleaseConfigFilePath = "C:\temp\AKS-Edge-$latestReleaseTag\tools\aksedge-config.json" +$jsonContent = Get-Content -Raw -Path $AKSEEReleaseConfigFilePath | ConvertFrom-Json +$schemaVersionAksEdgeConfig = $jsonContent.SchemaVersion +# Clean up the downloaded release files +Remove-Item -Path $output -Force +Remove-Item -Path "C:\temp\AKS-Edge-$latestReleaseTag" -Force -Recurse + +# Here string for the json content +$aideuserConfig = @" +{ + "SchemaVersion": "$latestReleaseTag", + "Version": "$schemaVersion", + "AksEdgeProduct": "$productName", + "AksEdgeProductUrl": "", + "Azure": { + "SubscriptionId": "$env:subscriptionId", + "TenantId": "$env:tenantId", + "ResourceGroupName": "$env:resourceGroup", + "Location": "$env:location" + }, + "AksEdgeConfigFile": "aksedge-config.json" +} +"@ + +if ($env:windowsNode -eq $true) { + $aksedgeConfig = @" +{ + "SchemaVersion": "$schemaVersionAksEdgeConfig", + "Version": "$versionAksEdgeConfig", + "DeploymentType": "SingleMachineCluster", + "Init": { + "ServiceIPRangeSize": 10 + }, + "Network": { + "NetworkPlugin": "$networkplugin", + "InternetDisabled": false + }, + "User": { + "AcceptEula": true, + "AcceptOptionalTelemetry": true + }, + "Machines": [ + { + "LinuxNode": { + "CpuCount": 4, + "MemoryInMB": 13000, + "DataSizeInGB": 30 + }, + "WindowsNode": { + "CpuCount": 2, + "MemoryInMB": 4096 + } + } + ] +} +"@ +} else { + $aksedgeConfig = @" +{ + "SchemaVersion": "$schemaVersionAksEdgeConfig", + "Version": "$versionAksEdgeConfig", + "DeploymentType": "SingleMachineCluster", + "Init": { + "ServiceIPRangeSize": 10 + }, + "Network": { + "NetworkPlugin": "$networkplugin", + "InternetDisabled": false + }, + "User": { + "AcceptEula": true, + "AcceptOptionalTelemetry": true + }, + "Machines": [ + { + "LinuxNode": { + "CpuCount": 4, + "MemoryInMB": 13000, + "DataSizeInGB": 30 + } + } + ] +} +"@ +} + +Set-ExecutionPolicy Bypass -Scope Process -Force +# Download the AksEdgeDeploy modules from Azure/AksEdge +$url = "https://github.com/Azure/AKS-Edge/archive/$aksEdgeDeployModules.zip" +$zipFile = "$aksEdgeDeployModules.zip" +$installDir = "C:\AksEdgeScript" +$workDir = "$installDir\AKS-Edge-main" + +if (-not (Test-Path -Path $installDir)) { + Write-Host "Creating $installDir..." + New-Item -Path "$installDir" -ItemType Directory | Out-Null +} + +Push-Location $installDir + +Write-Host "`n" +Write-Host "About to silently install AKS Edge Essentials, this will take a few minutes." -ForegroundColor Green +Write-Host "`n" + +try { + function download2() { $ProgressPreference = "SilentlyContinue"; Invoke-WebRequest -Uri $url -OutFile $installDir\$zipFile } + download2 +} +catch { + Write-Host "Error: Downloading Aide Powershell Modules failed" -ForegroundColor Red + Stop-Transcript | Out-Null + Pop-Location + exit -1 +} + +if (!(Test-Path -Path "$workDir")) { + Expand-Archive -Path $installDir\$zipFile -DestinationPath "$installDir" -Force +} + +$aidejson = (Get-ChildItem -Path "$workDir" -Filter aide-userconfig.json -Recurse).FullName +Set-Content -Path $aidejson -Value $aideuserConfig -Force +$aksedgejson = (Get-ChildItem -Path "$workDir" -Filter aksedge-config.json -Recurse).FullName +Set-Content -Path $aksedgejson -Value $aksedgeConfig -Force + +$aksedgeShell = (Get-ChildItem -Path "$workDir" -Filter AksEdgeShell.ps1 -Recurse).FullName +. $aksedgeShell + +# Download, install and deploy AKS EE +Write-Host "Step 2: Download, install and deploy AKS Edge Essentials" +# invoke the workflow, the json file already stored above. +$retval = Start-AideWorkflow -jsonFile $aidejson +# report error via Write-Error for Intune to show proper status +if ($retval) { + Write-Host "Deployment Successful. " +} else { + Write-Error -Message "Deployment failed" -Category OperationStopped + Stop-Transcript | Out-Null + Pop-Location + exit -1 +} + +if ($env:windowsNode -eq $true) { + # Get a list of all nodes in the cluster + $nodes = kubectl get nodes -o json | ConvertFrom-Json + + # Loop through each node and check the OSImage field + foreach ($node in $nodes.items) { + $os = $node.status.nodeInfo.osImage + if ($os -like '*windows*') { + # If the OSImage field contains "windows", assign the "worker" role + kubectl label nodes $node.metadata.name node-role.kubernetes.io/worker=worker + } + } +} + +Write-Host "`n" +Write-Host "Checking kubernetes nodes" +Write-Host "`n" +kubectl get nodes -o wide +Write-Host "`n" + +# az version +az -v + +# Login as service principal +az login --service-principal --username $Env:appId --password=$Env:password --tenant $Env:tenantId + +# Set default subscription to run commands against +# "subscriptionId" value comes from clientVM.json ARM template, based on which +# subscription user deployed ARM template to. This is needed in case Service +# Principal has access to multiple subscriptions, which can break the automation logic +az account set --subscription $Env:subscriptionId + +# Installing Azure CLI extensions +# Making extension install dynamic +az config set extension.use_dynamic_install=yes_without_prompt +Write-Host "`n" +Write-Host "Installing Azure CLI extensions" +az extension add --name connectedk8s --version 1.3.17 +az extension add --name k8s-extension +Write-Host "`n" + +# Registering Azure Arc providers +Write-Host "Registering Azure Arc providers, hold tight..." +Write-Host "`n" +az provider register --namespace Microsoft.Kubernetes --wait +az provider register --namespace Microsoft.KubernetesConfiguration --wait +az provider register --namespace Microsoft.HybridCompute --wait +az provider register --namespace Microsoft.GuestConfiguration --wait +az provider register --namespace Microsoft.HybridConnectivity --wait +az provider register --namespace Microsoft.ExtendedLocation --wait + +az provider show --namespace Microsoft.Kubernetes -o table +Write-Host "`n" +az provider show --namespace Microsoft.KubernetesConfiguration -o table +Write-Host "`n" +az provider show --namespace Microsoft.HybridCompute -o table +Write-Host "`n" +az provider show --namespace Microsoft.GuestConfiguration -o table +Write-Host "`n" +az provider show --namespace Microsoft.HybridConnectivity -o table +Write-Host "`n" +az provider show --namespace Microsoft.ExtendedLocation -o table +Write-Host "`n" + +# Onboarding the cluster to Azure Arc +Write-Host "Onboarding the AKS Edge Essentials cluster to Azure Arc..." +Write-Host "`n" + +$kubectlMonShell = Start-Process -PassThru PowerShell { for (0 -lt 1) { kubectl get pod -A; Start-Sleep -Seconds 5; Clear-Host } } + +#Tag +$clusterId = $(kubectl get configmap -n aksedge aksedge -o jsonpath="{.data.clustername}") + +$guid = ([System.Guid]::NewGuid()).ToString().subString(0,5).ToLower() +$Env:arcClusterName = "$Env:resourceGroup-$guid" +if ($env:kubernetesDistribution -eq "k8s") { + az connectedk8s connect --name $Env:arcClusterName ` + --resource-group $Env:resourceGroup ` + --location $env:location ` + --distribution aks_edge_k8s ` + --tags "Project=jumpstart_azure_arc_k8s" "ClusterId=$clusterId" ` + --correlation-id "d009f5dd-dba8-4ac7-bac9-b54ef3a6671a" +} else { + az connectedk8s connect --name $Env:arcClusterName ` + --resource-group $Env:resourceGroup ` + --location $env:location ` + --distribution aks_edge_k3s ` + --tags "Project=jumpstart_azure_arc_k8s" "ClusterId=$clusterId" ` + --correlation-id "d009f5dd-dba8-4ac7-bac9-b54ef3a6671a" +} + + +## Arc - enabled Server +## Configure the OS to allow Azure Arc Agent to be deploy on an Azure VM +Write-Host "`n" +Write-Host "Configure the OS to allow Azure Arc Agent to be deploy on an Azure VM" +Set-Service WindowsAzureGuestAgent -StartupType Disabled -Verbose +Stop-Service WindowsAzureGuestAgent -Force -Verbose +New-NetFirewallRule -Name BlockAzureIMDS -DisplayName "Block access to Azure IMDS" -Enabled True -Profile Any -Direction Outbound -Action Block -RemoteAddress 169.254.169.254 + +## Azure Arc agent Installation +Write-Host "`n" +Write-Host "Onboarding the Azure VM to Azure Arc..." + +# Download the package +function download1() { $ProgressPreference = "SilentlyContinue"; Invoke-WebRequest -Uri https://aka.ms/AzureConnectedMachineAgent -OutFile AzureConnectedMachineAgent.msi } +download1 + +# Install the package +msiexec /i AzureConnectedMachineAgent.msi /l*v installationlog.txt /qn | Out-String + +#Tag +$clusterName = "$env:computername-$env:kubernetesDistribution" + +# Run connect command +& "$env:ProgramFiles\AzureConnectedMachineAgent\azcmagent.exe" connect ` + --service-principal-id $env:appId ` + --service-principal-secret $env:password ` + --resource-group $env:resourceGroup ` + --tenant-id $env:tenantId ` + --location $env:location ` + --subscription-id $env:subscriptionId ` + --tags "Project=jumpstart_azure_arc_servers" "AKSEE=$clusterName"` + --correlation-id "d009f5dd-dba8-4ac7-bac9-b54ef3a6671a" + +# Changing to Client VM wallpaper +$imgPath = "C:\Temp\wallpaper.png" +$code = @' +using System.Runtime.InteropServices; +namespace Win32{ + + public class Wallpaper{ + [DllImport("user32.dll", CharSet=CharSet.Auto)] + static extern int SystemParametersInfo (int uAction , int uParam , string lpvParam , int fuWinIni) ; + + public static void SetWallpaper(string thePath){ + SystemParametersInfo(20,0,thePath,3); + } + } + } +'@ + +add-type $code +[Win32.Wallpaper]::SetWallpaper($imgPath) + +# Function to create Kubernetes secret for Azure Storage account +function Add-AzureStorageAccountSecret { + param ( + [string]$ResourceGroup, + [string]$StorageAccount, + [string]$Namespace, + [string]$SecretName + ) + + # Retrieve the primary key of the specified Azure Storage account. + $secretValue = az storage account keys list --resource-group $ResourceGroup --account-name $StorageAccount --query "[0].value" --output tsv + + # Create the Kubernetes secret with the storage account name and key. + kubectl create secret generic -n $Namespace $SecretName --from-literal=azurestorageaccountkey="$secretValue" --from-literal=azurestorageaccountname="$StorageAccount" +} + +#Begin ESA Installation. +#Documentation: https://aepreviews.ms/docs/edge-storage-accelerator/how-to-install-edge-storage-accelerator/ +# Create a storage account +# Echo the container and account name +Write-Host "Storage Account Name: $env:storageAccountName" +Write-Host "Container Name: $env:storageContainer" + +Write-Host "Creating storage account..." +az storage account create --resource-group "$env:resourceGroup" --name "$env:storageAccountName" --location "$env:location" --sku Standard_RAGRS --kind StorageV2 --allow-blob-public-access false +# Create a container within the storage account +Write-Host "Creating container within the storage account..." +Start-Sleep -Seconds 5 +az storage container create --name "$env:storageContainer" --account-name "$env:storageAccountName" --auth-mode login + + +Write-Host "Checking if local-path storage class is available..." +$localPathStorageClass = kubectl get storageclass | Select-String -Pattern "local-path" +if (-not $localPathStorageClass) { + Write-Host "Local Path Provisioner not found. Installing..." +# Download the local-path-storage.yaml file + $localPathStorageUrl = "https://raw.githubusercontent.com/Azure/AKS-Edge/main/samples/storage/local-path-provisioner/local-path-storage.yaml" + $localPathStoragePath = "local-path-storage.yaml" + Invoke-WebRequest -Uri $localPathStorageUrl -OutFile $localPathStoragePath +# Apply the local-path-storage.yaml file + kubectl apply -f $localPathStoragePath + Write-Host "Local Path Provisioner installed successfully." +} else { + Write-Host "Local Path Provisioner is already installed." +} +Write-Host "Checking fs.inotify.max_user_instances value..." +$maxUserInstances = Invoke-AksEdgeNodeCommand -NodeType "Linux" -Command "sysctl fs.inotify.max_user_instances" | Select-String -Pattern "fs.inotify.max_user_instances\s+=\s+(\d+)" | ForEach-Object { $_.Matches[0].Groups[1].Value } +Write-Host "Current fs.inotify.max_user_instances value: $maxUserInstances" +if ($maxUserInstances -lt 1024) { + Write-Host "Increasing fs.inotify.max_user_instances to 1024..." + Invoke-AksEdgeNodeCommand -NodeType "Linux" -Command "echo 'fs.inotify.max_user_instances = 1024' | sudo tee -a /etc/sysctl.conf && sudo sysctl -p" + Write-Host "fs.inotify.max_user_instances increased to 1024." +} else { + Write-Host "fs.inotify.max_user_instances is already set to 1024 or higher." +} +Write-Host "Installing Open Service Mesh (OSM)..." +az k8s-extension create --resource-group "$env:resourceGroup" --cluster-name "$env:arcClusterName" --cluster-type connectedClusters --extension-type Microsoft.openservicemesh --scope cluster --name osm +Write-Host "Open Service Mesh (OSM) installed successfully." +# Disable ACStor for single-node cluster +Write-Host "Disabling ACStor for single-node cluster..." +# Create the config.json file +$acstorConfig = @{ + "feature.diskStorageClass" = "local-path" + "acstorController.enabled" = $false +} + +$acstorConfigJson = $acstorConfig | ConvertTo-Json -Depth 100 +Set-Content -Path "config.json" -Value $acstorConfigJson +Write-Host "ACStor disabled for single-node cluster." +Write-Host "Checking if Edge Storage Accelerator Arc Extension is installed..." +$extensionExists = az k8s-extension show --resource-group "$env:resourceGroup" --cluster-name "$env:arcClusterName" --cluster-type connectedClusters --name hydraext --query "extensionType" --output tsv +if ($extensionExists -eq "microsoft.edgestorageaccelerator") { + Write-Host "Edge Storage Accelerator Arc Extension is already installed." +} else { + Write-Host "Installing Edge Storage Accelerator Arc Extension..." + az k8s-extension create --resource-group "$env:resourceGroup" --cluster-name "$env:arcClusterName" --cluster-type connectedClusters --name hydraext --extension-type microsoft.edgestorageaccelerator --config-file "config.json" + Write-Host "Edge Storage Accelerator Arc Extension installed successfully." +} + +# Create Kubernetes secret for Azure Storage account +Write-Host "Creating Kubernetes secret for Azure Storage account..." +$secretName = "$env:storageAccountName-secret" +Add-AzureStorageAccountSecret -ResourceGroup $env:resourceGroup -StorageAccount $env:storageAccountName -Namespace "default" -SecretName "esa-secret" +Write-Host "Kubernetes secret created successfully." +Write-Host "Downloading pv.yaml file..." +$pvYamlUrl = "https://raw.githubusercontent.com/microsoft/azure_arc/main/azure_edge_iot_ops_jumpstart/esa_fault_detection/yaml/pv.yaml" + +$pvYamlPath = "pv.yaml" +Invoke-WebRequest -Uri $pvYamlUrl -OutFile $pvYamlPath +# Update the secret name and container name in the pv.yaml file +#$pvYamlContent = Get-Content -Path $pvYamlPath -Raw +#$pvYamlContent = $pvYamlContent -replace '\${CONTAINER_NAME}-secret', $secretName +#$pvYamlContent = $pvYamlContent -replace '\${CONTAINER_NAME}', $env:storageContainer +#Set-Content -Path $pvYamlPath -Value $pvYamlContent +# Apply the pv.yaml file using kubectl +Write-Host "Applying pv.yaml configuration..." +kubectl apply -f $pvYamlPath +Write-Host "pv.yaml configuration applied successfully." +Write-Host "Downloading esa-deploy.yaml file..." +$esadeployYamlUrl = "https://raw.githubusercontent.com/microsoft/azure_arc/main/azure_edge_iot_ops_jumpstart/esa_fault_detection/yaml/esa-deploy.yaml" +$esadeployYamlPath = "esa-deploy.yaml" +Invoke-WebRequest -Uri $esadeployYamlUrl -OutFile $esadeployYamlPath +# Apply the p-deploy.yaml file using kubectl +Write-Host "Applying esadeploy.yaml configuration..." +kubectl apply -f $esadeployYamlPath +Write-Host "esa-deploy.yaml configuration applied successfully." + +# Stop the PowerShell process monitoring Kubernetes pods + +Stop-Process -Id $kubectlMonShell.Id + +# Remove temporary files and directories + +Remove-Item -Path "$installDir\$zipFile" -Force -ErrorAction SilentlyContinue +Remove-Item -Path "$installDir\AKS-Edge-main" -Recurse -Force -ErrorAction SilentlyContinue +Remove-Item -Path "AzureConnectedMachineAgent.msi" -Force -ErrorAction SilentlyContinue + +# Stop the transcript + +Stop-Transcript diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/Dockerfile b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/Dockerfile new file mode 100644 index 0000000000..3a207e5401 --- /dev/null +++ b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/Dockerfile @@ -0,0 +1,27 @@ +# Use an official Python runtime as a parent image +FROM python:3.8-slim-buster + +# Set the working directory to /app +WORKDIR /app + +# Install necessary dependencies +USER root +RUN apt-get update --fix-missing && \ + apt-get install -y python3-opencv && \ + rm -rf /var/lib/apt/lists/* + +# Copy the current directory contents into the container at /app +COPY . /app + +# Install any needed packages specified in requirements.txt +RUN pip install -r requirements.txt + +#Install torch and torchvision CPU only versions +RUN pip3 install torch==1.8.0+cpu torchvision==0.9.0+cpu -f https://download.pytorch.org/whl/cpu/torch_stable.html + +# Make port 80 available to the world outside this container +EXPOSE 8000 + +# Run main.py when the container launches +CMD ["python", "main.py"] + diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/index.html b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/index.html new file mode 100644 index 0000000000..c2ec8da240 --- /dev/null +++ b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/index.html @@ -0,0 +1,41 @@ + + + Jumpstart ESA Scenario + + + + + + + + + +
+
+ +
+
+ +
+
+ + + + + + + + + + +
NameSize (Kb)Last Modified
+
+
+ + + + + \ No newline at end of file diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/main.py b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/main.py new file mode 100644 index 0000000000..6e089597f5 --- /dev/null +++ b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/main.py @@ -0,0 +1,476 @@ +# Original code taken from: https://gist.github.com/raiever/df5b6c48217df521094cbe2d12c32c66 +# import the necessary packages +from flask import Response, Flask, render_template, jsonify +import threading +import argparse +import datetime, time +import cv2 +import os +import time +import math +from math import atan2 +import numpy as np +import json +import sys + +# GLOBAL Variables +OBJECT_AREA_MIN = 9000 +OBJECT_AREA_MAX = 50000 +LOW_H = 0 +LOW_S = 0 +LOW_V = 47 +# Thresholding of an Image in a color range +HIGH_H = 179 +HIGH_S = 255 +HIGH_V = 255 +# Lower and upper value of color Range of the object +# for color thresholding to detect the object +LOWER_COLOR_RANGE = (0, 0, 0) +UPPER_COLOR_RANGE = (174, 73, 255) +COUNT_OBJECT = 0 +HEIGHT_OF_OBJ = 0 +WIDTH_OF_OBJ = 0 + +OBJECT_COUNT = "Object Number : {}".format(COUNT_OBJECT) +source = "rtsp://localhost:554/stream" + +if 'RTSP_URL' in os.environ: + source = os.environ.get('RTSP_URL') +print("Incoming video feed read from RTSP_URL: ", source) + +"""#esa_storage = "/home/aksedge-user/" """ +esa_storage = "./esa_storage" +if 'LOCAL_STORAGE' in os.environ: + esa_storage = os.environ.get('LOCAL_STORAGE') +print("Storing video frames read from LOCAL_STORAGE: ", esa_storage) + +# initialize the output frame and a lock used to ensure thread-safe +# exchanges of the output frames (useful when multiple browsers/tabs are viewing the stream) +outputFrame = None +lock = threading.Lock() + +# initialize a flask object +app = Flask(__name__) + +cap = cv2.VideoCapture(source) +time.sleep(2.0) + +@app.route("/") +def index(): + # return the rendered template + return render_template("index.html") + +def dimensions(box): + """ + Return the length and width of the object. + + :param box: consists of top left, right and bottom left, right co-ordinates + :return: Length and width of the object + """ + (tl, tr, br, bl) = box + x = int(math.sqrt(math.pow((bl[0] - tl[0]), 2) + math.pow((bl[1] - tl[1]), 2))) + y = int(math.sqrt(math.pow((tl[0] - tr[0]), 2) + math.pow((tl[1] - tr[1]), 2))) + + if x > y: + return x, y + else: + return y, x + +def flaw_detection(): + """ + Measurement and defects such as color, crack and orientation of the object + are found. + + :return: None + """ + global HEIGHT_OF_OBJ + global WIDTH_OF_OBJ + global COUNT_OBJECT + global OBJ_DEFECT + global FRAME_COUNT + global OBJECT_COUNT + + while cap.isOpened(): + # Read the frame from the stream + ret, frame = cap.read() + base_dir = os.getcwd() + + if not ret: + break + + FRAME_COUNT += 1 + + # Check every given frame number + # (Number chosen based on the frequency of object on conveyor belt) + if FRAME_COUNT % frame_number == 0: + HEIGHT_OF_OBJ = 0 + WIDTH_OF_OBJ = 0 + OBJ_DEFECT = [] + data_base = [] + # Convert BGR image to HSV color space + img_hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) + + # Thresholding of an Image in a color range + img_threshold = cv2.inRange(img_hsv, (LOW_H, LOW_S, LOW_V), + (HIGH_H, HIGH_S, HIGH_V)) + + # Morphological opening(remove small objects from the foreground) + img_threshold = cv2.erode(img_threshold, + cv2.getStructuringElement( + cv2.MORPH_ELLIPSE, (5, 5))) + img_threshold = cv2.dilate(img_threshold, + cv2.getStructuringElement( + cv2.MORPH_ELLIPSE, (5, 5))) + + # Morphological closing(fill small holes in the foreground) + img_threshold = cv2.dilate(img_threshold, + cv2.getStructuringElement( + cv2.MORPH_ELLIPSE, (5, 5))) + img_threshold = cv2.erode(img_threshold, + cv2.getStructuringElement( + cv2.MORPH_ELLIPSE, (5, 5))) + + # Find the contours on the image + contours, hierarchy = cv2.findContours(img_threshold, + cv2.RETR_LIST, + cv2.CHAIN_APPROX_NONE) + + for cnt in contours: + x, y, w, h = cv2.boundingRect(cnt) + if OBJECT_AREA_MAX > w * h > OBJECT_AREA_MIN: + box = cv2.minAreaRect(cnt) + box = cv2.boxPoints(box) + height, width = dimensions(np.array(box, dtype='int')) + HEIGHT_OF_OBJ = round(height * one_pixel_length * 10, 2) + WIDTH_OF_OBJ = round(width * one_pixel_length * 10, 2) + COUNT_OBJECT += 1 + OBJECT_COUNT = "Object Number : {}".format(COUNT_OBJECT) + + # Check for the orientation of the object + frame, orientation_flag, orientation_defect = \ + detect_orientation(frame, cnt) + if orientation_flag: + value = 1 + data_base.append(value) + OBJ_DEFECT.append(str(orientation_defect)) + else: + value = 0 + data_base.append(value) + + # Check for the color defect of the object + frame, color_flag, color_defect = detect_color(frame, cnt) + if color_flag: + value = 1 + data_base.append(value) + OBJ_DEFECT.append(str(color_defect)) + else: + value = 0 + data_base.append(value) + + # Check for the crack defect of the object + frame, crack_flag, crack_defect = detect_crack(frame, cnt) + if crack_flag: + value = 1 + data_base.append(value) + OBJ_DEFECT.append(str(crack_defect)) + else: + value = 0 + data_base.append(value) + + # Check if none of the defect is found + if not OBJ_DEFECT: + value = 1 + data_base.append(value) + defect = "No Defect" + OBJ_DEFECT.append(defect) + print("No defect detected in object {}" + .format(COUNT_OBJECT)) + cv2.putText(frame, "Length (mm): {}".format(HEIGHT_OF_OBJ), + (5, 80), cv2.FONT_HERSHEY_SIMPLEX, 0.75, + (255, 255, 255), 2) + cv2.putText(frame, "Width (mm): {}".format(WIDTH_OF_OBJ), + (5, 110), cv2.FONT_HERSHEY_SIMPLEX, 0.75, + (255, 255, 255), 2) + cv2.imwrite("{}/esa_storage/Nodefect_{}.png".format( + base_dir, COUNT_OBJECT), + frame[y : y + h, + x : x + w]) + else: + value = 0 + data_base.append(value) + print("Length (mm) = {}, width (mm) = {}".format( + HEIGHT_OF_OBJ, WIDTH_OF_OBJ)) + if not OBJ_DEFECT: + continue + + all_defects = " ".join(OBJ_DEFECT) + cv2.putText(frame, "Press q to quit", (410, 50), + cv2.FONT_HERSHEY_COMPLEX, 1, (255, 255, 255), 2) + cv2.putText(frame, OBJECT_COUNT, (5, 50), cv2.FONT_HERSHEY_SIMPLEX, + 0.75, (255, 255, 255), 2) + cv2.putText(frame, "Defect: {}".format(all_defects), (5, 140), + cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), 2) + cv2.putText(frame, "Length (mm): {}".format(HEIGHT_OF_OBJ), (5, 80), + cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), 2) + cv2.putText(frame, "Width (mm): {}".format(WIDTH_OF_OBJ), (5, 110), + cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), 2) + + ret, buffer = cv2.imencode('.png', frame) + frame=buffer.tobytes() + + yield (b'--frame\r\n' + b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') + + cv2.destroyAllWindows() + cap.release() + +def get_orientation(contours): + """ + Gives the angle of the orientation of the object in radians. + Step 1: Convert 3D matrix of contours to 2D. + Step 2: Apply PCA algorithm to find angle of the data points. + Step 3: If angle is greater than 0.5, return_flag is made to True + else false. + Step 4: Save the image in "Orientation" folder if it has a + orientation defect. + + :param contours: contour of the object from the frame + :return: angle of orientation of the object in radians + """ + size_points = len(contours) + # data_pts stores contour values in 2D + data_pts = np.empty((size_points, 2), dtype=np.float64) + for i in range(data_pts.shape[0]): + data_pts[i, 0] = contours[i, 0, 0] + data_pts[i, 1] = contours[i, 0, 1] + # Use PCA algorithm to find angle of the data points + mean, eigenvector = cv2.PCACompute(data_pts, mean=None) + angle = atan2(eigenvector[0, 1], eigenvector[0, 0]) + return angle + +def detect_orientation(frame, contours, base_dir = os.getcwd()): + """ + Identifies the Orientation of the object based on the detected angle. + + :param frame: Input frame from video + :param contours: contour of the object from the frame + :return: defect_flag, defect + """ + defect = "Orientation" + global OBJECT_COUNT + # Find the orientation of each contour + angle = get_orientation(contours) + # If angle is less than 0.5 then no orientation defect is present + if angle < 0.5: + defect_flag = False + else: + x, y, w, h = cv2.boundingRect(contours) + print("Orientation defect detected in object {}".format(COUNT_OBJECT)) + defect_flag = True + cv2.imwrite("{}/esa_storage/Orientation_{}.png" + .format(base_dir, COUNT_OBJECT), + frame[y: y + h , x : x + w]) + cv2.putText(frame, OBJECT_COUNT, (5, 50), cv2.FONT_HERSHEY_SIMPLEX, + 0.75, (255, 255, 255), 2) + cv2.putText(frame, "Defect: {}".format(defect), (5, 140), + cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), 2) + cv2.putText(frame, "Length (mm): {}".format(HEIGHT_OF_OBJ), (5, 80), + cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), 2) + cv2.putText(frame, "Width (mm): {}".format(WIDTH_OF_OBJ), (5, 110), + cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), 2) + + return frame, defect_flag, defect + +def detect_color(frame, cnt, base_dir = os.getcwd()): + """ + Identifies the color defect W.R.T the set default color of the object. + Step 1: Increase the brightness of the image. + Step 2: Convert the image to HSV Format. HSV color space gives more + information about the colors of the image. + It helps to identify distinct colors in the image. + Step 3: Threshold the image based on the color using "inRange" function. + Range of the color, which is considered as a defect for object, is + passed as one of the argument to inRange function to create a mask. + Step 4: Morphological opening and closing is done on the mask to remove + noises and fill the gaps. + Step 5: Find the contours on the mask image. Contours are filtered based on + the area to get the contours of defective area. Contour of the + defective area is then drawn on the original image to visualize. + Step 6: Save the image in "color" folder if it has a color defect. + + :param frame: Input frame from the video + :param cnt: Contours of the object + :return: color_flag, defect + """ + defect = "Color" + global OBJECT_COUNT + color_flag = False + # Increase the brightness of the image + cv2.convertScaleAbs(frame, frame, 1, 20) + # Convert the captured frame from BGR to HSV + img_hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) + # Threshold the image + img_threshold = cv2.inRange(img_hsv, LOWER_COLOR_RANGE, UPPER_COLOR_RANGE) + # Morphological opening (remove small objects from the foreground) + img_threshold = cv2.erode(img_threshold, + kernel=cv2.getStructuringElement( + cv2.MORPH_ELLIPSE, (5, 5))) + img_threshold = cv2.dilate(img_threshold, + kernel=cv2.getStructuringElement( + cv2.MORPH_ELLIPSE, (5, 5))) + contours, hierarchy = cv2.findContours(img_threshold, cv2.RETR_LIST, + cv2.CHAIN_APPROX_NONE) + for i in range(len(contours)): + area = cv2.contourArea(contours[i]) + if 2000 < area < 10000: + cv2.drawContours(frame, contours[i], -1, (0, 0, 255), 2) + color_flag = True + if color_flag: + x, y, w, h = cv2.boundingRect(cnt) + print("Color defect detected in object {}".format(COUNT_OBJECT)) + cv2.imwrite("{}/esa_storage/Color_{}.png".format(base_dir, COUNT_OBJECT), + frame[y : y + h, x : x + w]) + cv2.putText(frame, OBJECT_COUNT, (5, 50), cv2.FONT_HERSHEY_SIMPLEX, + 0.75, (255, 255, 255), 2) + cv2.putText(frame, "Defect: {}".format(defect), (5, 140), + cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), 2) + cv2.putText(frame, "Length (mm): {}".format(HEIGHT_OF_OBJ), (5, 80), + cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), 2) + cv2.putText(frame, "Width (mm): {}".format(WIDTH_OF_OBJ), (5, 110), + cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), 2) + return frame, color_flag, defect + +def detect_crack(frame, cnt, base_dir = os.getcwd()): + """ + Identify the Crack defect on the object. + Step 1: Convert the image to gray scale. + Step 2: Blur the gray image to remove the noises. + Step 3: Find the edges on the blurred image to get the contours of + possible cracks. + Step 4: Filter the contours to get the contour of the crack. + Step 5: Draw the contour on the orignal image for visualization. + Step 6: Save the image in "crack" folder if it has crack defect. + + :param frame: Input frame from the video + :param cnt: Contours of the object + :return: defect_flag, defect, cnt + """ + defect = "Crack" + global OBJECT_COUNT + defect_flag = False + low_threshold = 130 + kernel_size = 3 + ratio = 3 + # Convert the captured frame from BGR to GRAY + img = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + img = cv2.blur(img, (7, 7)) + # Find the edges + detected_edges = cv2.Canny(img, low_threshold, + low_threshold * ratio, kernel_size) + # Find the contours + contours, hierarchy = cv2.findContours(detected_edges, cv2.RETR_TREE, + cv2.CHAIN_APPROX_NONE) + + if len(contours) != 0: + for i in range(len(contours)): + area = cv2.contourArea(contours[i]) + if area > 20 or area < 9: + cv2.drawContours(frame, contours, i, (0, 255, 0), 2) + defect_flag = True + + if defect_flag: + x, y, w, h = cv2.boundingRect(cnt) + print("Crack defect detected in object {}".format(COUNT_OBJECT)) + cv2.imwrite("{}/esa_storage/Crack_{}.png".format(base_dir, COUNT_OBJECT), + frame[y : y + h , x : x + w ]) + cv2.putText(frame, OBJECT_COUNT, (5, 50), cv2.FONT_HERSHEY_SIMPLEX, + 0.75, (255, 255, 255), 2) + cv2.putText(frame, "Defect: {}".format(defect), (5, 140), + cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), 2) + cv2.putText(frame, "Length (mm): {}".format(HEIGHT_OF_OBJ), + (5, 80), + cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), 2) + cv2.putText(frame, "Width (mm): {}".format(WIDTH_OF_OBJ), (5, 110), + cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), 2) + return frame, defect_flag, defect + +def stream(frameCount): + global outputFrame, lock + if cap.isOpened(): + count = 0 + while True: + ret_val, frame = cap.read() + if not None and frame.shape: + if count % 3 != 2: + frame = cv2.resize(frame, (640,360)) + with lock: + outputFrame = frame.copy() + count += 1 + else: + continue + else: + print('Camera open failed') + +def store_jpg_frame(frame_data): + current_time = datetime.datetime.now() + file_name = current_time.strftime("%Y-%m-%d_%H-%M-%S") + file_name = file_name + ".jpg" + with open(f"{esa_storage}/{file_name}", "wb") as f: + f.write(frame_data) + +@app.route('/video_feed') +def video_feed(): + return Response(flaw_detection(), mimetype='multipart/x-mixed-replace; boundary=frame') + +@app.route('/data') +def data(): + files = [] + for filename in os.listdir(esa_storage): + file_path = os.path.join(esa_storage, filename) + if os.path.isfile(file_path): + size = os.path.getsize(file_path) + modified = os.path.getmtime(file_path) + files.append({'name': filename, 'size': size, 'modified': modified}) + files.sort(key=lambda f: f['modified'], reverse=True) + return jsonify({'files': files}) + +# check to see if this is the main thread of execution +if __name__ == '__main__': + # construct the argument parser and parse command line arguments + ap = argparse.ArgumentParser() + ap.add_argument("-i", "--ip", type=str, required=False, default='0.0.0.0', + help="ip address of the device") + ap.add_argument("-o", "--port", type=int, required=False, default=8000, + help="ephemeral port number of the server (1024 to 65535)") + ap.add_argument("-f", "--frame-count", type=int, default=32, + help="# of frames used to construct the background model") + args = vars(ap.parse_args()) + + input_stream = source + cap = cv2.VideoCapture(input_stream) + if not cap.isOpened(): + print("\nCamera not plugged in... Exiting...\n") + sys.exit(0) + fps = cap.get(cv2.CAP_PROP_FPS) + delay = (int)(1000 / fps) + + one_pixel_length = 0.0264583333 + + OBJ_DEFECT = [] + frame_number = 40 + FRAME_COUNT = 0 + + # Find dimensions and flaw detections such as color, crack and orientation + # of the object. + """flaw_detection()""" + + # start the flask app + app.run(host=args["ip"], port=args["port"], debug=True, threaded=True, use_reloader=False) + + t = threading.Thread(target=stream, args=(args["frame_count"],)) + t.daemon = True + t.start() + +# release the video stream pointer +cap.release() +cv2.destroyAllWindows() \ No newline at end of file diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/requirements.txt b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/requirements.txt new file mode 100644 index 0000000000..366eb08305 --- /dev/null +++ b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/requirements.txt @@ -0,0 +1,14 @@ +Flask==3.0.2 +opencv-python==4.9.0.80 +numpy==1.24.4 +matplotlib>=3.3.0 +pillow>=7.1.2 +pyyaml>=5.3.1 +requests>=2.23.0 +scipy>=1.4.1 +tqdm>=4.64.0 +pandas>=1.1.4 +psutil +dill +py-cpuinfo +openvino \ No newline at end of file diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/static/index.css b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/static/index.css new file mode 100644 index 0000000000..dffeb1dd49 --- /dev/null +++ b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/static/index.css @@ -0,0 +1,15 @@ +body { + background-color: black; +} + +.logo { + width: 30%; + margin:auto; + display:block; +} + +.video-feed { + width: 60%; + margin:auto; + display:block; +} diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/static/index.js b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/static/index.js new file mode 100644 index 0000000000..8d1e81a211 --- /dev/null +++ b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/static/index.js @@ -0,0 +1,22 @@ +var table; +$(document).ready(function () { + table = $('#file-table').DataTable({ + responsive: true, + order: [[2, 'desc']] + }); +}); + +$(function () { + setInterval(function () { + $.getJSON('/data', function (data) { + table.clear().draw(); + data.files.forEach(function(file) { + table.row.add([ + file.name, + parseInt(file.size/1024), + new Date(file.modified * 1000).toLocaleString() + ]).draw(false); + }); + }); + }, 2500); +}); \ No newline at end of file diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/static/jumpstart_scenarios.png b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/static/jumpstart_scenarios.png new file mode 100644 index 0000000000..b2e46c97b9 Binary files /dev/null and b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/static/jumpstart_scenarios.png differ diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/templates/index.html b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/templates/index.html new file mode 100644 index 0000000000..c2ec8da240 --- /dev/null +++ b/azure_edge_iot_ops_jumpstart/esa_fault_detection/artifacts/esa_ai/templates/index.html @@ -0,0 +1,41 @@ + + + Jumpstart ESA Scenario + + + + + + + + + +
+
+ +
+
+ +
+
+ + + + + + + + + + +
NameSize (Kb)Last Modified
+
+
+ + + + + \ No newline at end of file diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/azuredeploy.json b/azure_edge_iot_ops_jumpstart/esa_fault_detection/azuredeploy.json new file mode 100644 index 0000000000..38e8d8578d --- /dev/null +++ b/azure_edge_iot_ops_jumpstart/esa_fault_detection/azuredeploy.json @@ -0,0 +1,401 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "vmName": { + "type": "string", + "defaultValue": "ESA-Demo", + "metadata": { + "description": "The name of you Virtual Machine." + } + }, + "kubernetesDistribution": { + "type": "string", + "defaultValue": "k3s", + "allowedValues": [ + "k8s", + "k3s" + ], + "metadata": { + "description": "Kubernetes distribution" + } + }, + "adminUsername": { + "type": "string", + "defaultValue": "arcdemo", + "metadata": { + "description": "Username for the Virtual Machine." + } + }, + "adminPassword": { + "type": "securestring", + "defaultValue": "ArcPassword123!!", + "metadata": { + "description": "Windows password for the Virtual Machine" + } + }, + "windowsOSVersion": { + "type": "string", + "defaultValue": "2022-datacenter-g2", + "metadata": { + "description": "The Windows version for the VM. This will pick a fully patched image of this given Windows version." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources." + } + }, + "deployBastion": { + "type": "bool", + "metadata": { + "description": "Choice to deploy Bastion to connect to the client VM" + } + }, + "bastionHostName": { + "type": "string", + "defaultValue": "ESA-Demo-Bastion", + "metadata": { + "description": "the Azure Bastion host name" + } + }, + "vmSize": { + "type": "string", + "defaultValue": "Standard_D8s_v3", + "metadata": { + "description": "The size of the VM" + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Unique SPN app ID" + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "The name of the Azure Storage account" + } + }, + "storageContainer": { + "type": "string", + "metadata": { + "description": "The name of the Azure Storage container within the specified account" + } + }, + "password": { + "type": "securestring", + "metadata": { + "description": "Unique SPN password" + } + }, + "tenantId": { + "type": "string", + "metadata": { + "description": "Unique SPN tenant ID" + } + }, + "subscriptionId": { + "type": "string", + "metadata": { + "description": "Azure subscription ID" + } + }, + "githubAccount": { + "type": "string", + "metadata": { + "description": "Target GitHub account" + }, + "defaultValue": "microsoft" + }, + "githubBranch": { + "type": "string", + "metadata": { + "description": "Target GitHub branch" + }, + "defaultValue": "main" + }, + "virtualNetworkName": { + "type": "string", + "defaultValue": "ESA-Demo-VNET", + "metadata": { + "description": "Name of the VNET" + } + }, + "subnetName": { + "type": "string", + "defaultValue": "Subnet", + "metadata": { + "description": "Name of the subnet in the virtual network" + } + }, + "networkSecurityGroupName": { + "type": "string", + "defaultValue": "ESA-Demo-NSG", + "metadata": { + "description": "Name of the Network Security Group" + } + }, + "resourceTags": { + "type": "object", + "defaultValue": { + "Project": "jumpstart_azure_arc_servers" + } + }, + "windowsNode": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Deploy Windows Node for AKS Edge Essentials" + } + } + }, + "variables": { + "templateBaseUrl": "[concat('https://raw.githubusercontent.com/', parameters('githubAccount'), '/azure_arc/', parameters('githubBranch'), '/azure_edge_iot_ops_jumpstart/esa_fault_detection/')]", + "vmName": "[concat(parameters('vmName'))]", + "publicIpAddressName": "[concat(parameters('vmName'), '-PIP' )]", + "networkInterfaceName": "[concat(parameters('vmName'),'-NIC')]", + "networkSecurityGroupName": "[concat(parameters('vmName'), '-NSG')]", + "virtualNetworkName": "[concat(parameters('vmName'), '-VNET')]", + "bastionSubnetName": "AzureBastionSubnet", + "subnetRef": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('virtualNetworkName'), parameters('subnetName'))]", + "bastionSubnetRef": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('virtualNetworkName'), variables('bastionSubnetName'))]", + "osDiskType": "Premium_LRS", + "subnetAddressPrefix": "10.1.0.0/24", + "addressPrefix": "10.1.0.0/16", + "bastionName": "[concat(parameters('bastionHostName'))]", + "bastionSubnetIpPrefix": "10.1.1.64/26", + "PublicIPNoBastion": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" + } + }, + "resources": [ + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2022-07-01", + "name": "[variables('networkInterfaceName')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.Network/publicIpAddresses', variables('publicIpAddressName'))]" + ], + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[variables('subnetRef')]" + }, + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": "[if(not(parameters('deployBastion')),variables('PublicIPNoBastion'),json('null'))]" + } + } + ], + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups/', variables('networkSecurityGroupName'))]" + } + } + }, + { + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2019-02-01", + "name": "[variables('networkSecurityGroupName')]", + "location": "[parameters('location')]" + }, + { + "type": "Microsoft.Network/networkSecurityGroups/securityRules", + "apiVersion": "2022-05-01", + "condition": "[parameters('deployBastion')]", + "dependsOn": [ + "[resourceId('Microsoft.Network/networkSecurityGroups/', variables('networkSecurityGroupName'))]" + ], + "name": "[concat(variables('networkSecurityGroupName'),'/allow_RDP_3389')]", + "properties": { + "priority": 1001, + "protocol": "TCP", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "[variables('bastionSubnetIpPrefix')]", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "3389" + } + }, + { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2022-07-01", + "name": "[variables('virtualNetworkName')]", + "location": "[parameters('location')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('addressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[parameters('subnetName')]", + "properties": { + "addressPrefix": "[variables('subnetAddressPrefix')]", + "privateEndpointNetworkPolicies": "Enabled", + "privateLinkServiceNetworkPolicies": "Enabled" + } + }, + { + "name": "AzureBastionSubnet", + "properties": { + "addressPrefix": "[variables('bastionSubnetIpPrefix')]" + } + } + ] + } + }, + { + "type": "Microsoft.Network/publicIpAddresses", + "apiVersion": "2022-07-01", + "name": "[variables('publicIpAddressName')]", + "location": "[parameters('location')]", + "properties": { + "publicIpAllocationMethod": "Static", + "publicIPAddressVersion": "IPv4", + "idleTimeoutInMinutes": 4 + }, + "sku": { + "name": "[if(not(parameters('deployBastion')),'Basic','Standard')]" + } + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[variables('vmName')]", + "location": "[parameters('location')]", + "tags": "[parameters('resourceTags')]", + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces/', variables('networkInterfaceName'))]" + ], + "properties": { + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "storageProfile": { + "osDisk": { + "name": "[concat(variables('vmName'),'-OSDisk')]", + "caching": "ReadWrite", + "createOption": "fromImage", + "managedDisk": { + "storageAccountType": "[variables('osDiskType')]" + } + }, + "imageReference": { + "publisher": "MicrosoftWindowsServer", + "offer": "WindowsServer", + "sku": "[parameters('windowsOSVersion')]", + "version": "latest" + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" + } + ] + }, + "osProfile": { + "computerName": "[variables('vmName')]", + "adminUsername": "[parameters('adminUsername')]", + "adminPassword": "[parameters('adminPassword')]", + "windowsConfiguration": { + "provisionVMAgent": true, + "enableAutomaticUpdates": false + } + } + } + }, + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "name": "[concat(variables('vmName'),'/Bootstrap')]", + "apiVersion": "2022-11-01", + "location": "[parameters('location')]", + "dependsOn": [ + "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'))]" + ], + "tags": { + "displayName": "Run Bootstrap" + }, + "properties": { + "publisher": "Microsoft.Compute", + "type": "CustomScriptExtension", + "typeHandlerVersion": "1.10", + "autoUpgradeMinorVersion": true, + "protectedSettings": { + "fileUris": [ + "[uri(variables('templateBaseUrl'), concat('artifacts/Bootstrap.ps1'))]" + ], + "commandToExecute": "[concat('powershell.exe -ExecutionPolicy Unrestricted -File Bootstrap.ps1', ' -adminUsername ', parameters('adminUsername'), ' -appId ', parameters('appId'), ' -password ', parameters('password'), ' -tenantId ', parameters('tenantId'), ' -subscriptionId ', subscription().subscriptionId, ' -resourceGroup ', resourceGroup().name, ' -location ', resourceGroup().location, ' -kubernetesDistribution ', parameters('kubernetesDistribution'), ' -windowsNode ', parameters('windowsNode'), ' -templateBaseUrl ', variables('templateBaseUrl'), ' -storageAccountName ', parameters('storageAccountName'), ' -storageContainer ', parameters('storageContainer'))]" + } + } + }, + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[concat(variables('vmName'),'/InstallWindowsFeatures')]", + "location": "[parameters('location')]", + "properties": { + "publisher": "Microsoft.Powershell", + "type": "DSC", + "typeHandlerVersion": "2.77", + "autoUpgradeMinorVersion": true, + "settings": { + "wmfVersion": "latest", + "configuration": { + "url": "[uri(variables('templateBaseUrl'), concat('artifacts/DSCInstallWindowsFeatures.zip'))]", + "script": "DSCInstallWindowsFeatures.ps1", + "function": "InstallWindowsFeatures" + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('vmName'), 'Bootstrap')]" + ] + }, + { + "type": "Microsoft.Network/bastionHosts", + "condition": "[parameters('deployBastion')]", + "name": "[variables('bastionName')]", + "location": "[parameters('location')]", + "apiVersion": "2022-07-01", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]", + "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIpAddressName'))]" + ], + "properties": { + "ipConfigurations": [ + { + "name": "IpConf", + "properties": { + "subnet": { + "id": "[variables('bastionSubnetRef')]" + }, + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" + } + } + } + ] + } + } + ], + "outputs": { + "adminUsername": { + "type": "string", + "value": "[parameters('adminUsername')]" + }, + "publicIP": { + "type": "string", + "value": "[concat(reference(variables('publicIPAddressName')).IpAddress)]" + } + } +} diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/azuredeploy.parameters.json b/azure_edge_iot_ops_jumpstart/esa_fault_detection/azuredeploy.parameters.json new file mode 100644 index 0000000000..ac97ede0f0 --- /dev/null +++ b/azure_edge_iot_ops_jumpstart/esa_fault_detection/azuredeploy.parameters.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "vmSize": { + "value": "Standard_D8s_v3" + }, + "vmName": { + "value": "ESA-Win-Demo" + }, + "kubernetesDistribution": { + "value": "k3s" + }, + "windowsNode": { + "value": false + }, + "adminUsername": { + "value": "arcdemo" + }, + "adminPassword": { + "value": "ArcPassword123!!" + }, + "appId": { + "value": "XXXXX-XXXXX-XXXXX" + }, + "password": { + "value": "XXXXX-XXXXX-XXXXX" + }, + "tenantId": { + "value": "XXXXX-XXXXX-XXXXX" + }, + "subscriptionId": { + "value": "XXXXX-XXXXX-XXXXX" + }, + "location": { + "value": "eastus2" + }, + "deployBastion": { + "value": true + }, + "bastionHostName": { + "value": "Arc-Win-Demo-Bastion" + }, + "storageAccountName": { + "value": "XXXXXX" + }, + "storageContainer": { + "value": "esademo-container" + } + } +} \ No newline at end of file diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/yaml/esa-deploy.yaml b/azure_edge_iot_ops_jumpstart/esa_fault_detection/yaml/esa-deploy.yaml new file mode 100644 index 0000000000..56277254b4 --- /dev/null +++ b/azure_edge_iot_ops_jumpstart/esa_fault_detection/yaml/esa-deploy.yaml @@ -0,0 +1,135 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: esa-pvc + namespace: default +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 5Gi + storageClassName: esa + volumeMode: Filesystem + volumeName: esa-pv +status: + accessModes: + - ReadWriteMany + capacity: + storage: 5Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: esa-webserver +spec: + replicas: 1 + selector: + matchLabels: + app: esa-webserver + template: + metadata: + labels: + app: esa-webserver + spec: + containers: + - name: esa-webserver + image: jumpstartprod.azurecr.io/esa-webserver:latest + ports: + - containerPort: 8000 + env: + - name: RTSP_URL + value: rtsp://virtual-rtsp:8554/stream + - name: LOCAL_STORAGE + value: /app/esa_storage + volumeMounts: + ### This name must match the volumes.name attribute below ### + - name: blob + ### This mountPath is where the PVC will be attached to the pod's filesystem ### + mountPath: "/app/esa_storage" + volumes: + ### User-defined 'name' that will be used to link the volumeMounts. This name must match volumeMounts.name as specified above. ### + - name: blob + persistentVolumeClaim: + ### This claimName must refer to the PVC resource 'name' as defined in the PVC config. This name will match what your PVC resource was actually named. ### + claimName: esa-pvc + + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: virtual-rtsp +spec: + replicas: 1 + selector: + matchLabels: + app: virtual-rtsp + minReadySeconds: 10 + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + template: + metadata: + labels: + app: virtual-rtsp + spec: + initContainers: + - name: init-samples + image: busybox + command: + - wget + - "-O" + - "/samples/bolt-detection.mp4" + - https://github.com/ldabas-msft/jumpstart-resources/raw/main/bolt-detection.mp4 + volumeMounts: + - name: tmp-samples + mountPath: /samples + containers: + - name: virtual-rtsp + image: "agoraarmbladev.azurecr.io/kerberos/virtual-rtsp:latest" + imagePullPolicy: Always + ports: + - containerPort: 8554 + env: + - name: SOURCE_URL + value: "file:///samples/bolt-detection.mp4" + volumeMounts: + - name: tmp-samples + mountPath: /samples + volumes: + - name: tmp-samples + emptyDir: { } +--- +apiVersion: v1 +kind: Service +metadata: + name: virtual-rtsp + labels: + app: virtual-rtsp +spec: + type: LoadBalancer + ports: + - port: 8554 + targetPort: 8554 + name: rtsp + protocol: TCP + selector: + app: virtual-rtsp +--- +apiVersion: v1 +kind: Service +metadata: + name: esa-webserver-svc + labels: + app: esa-webserver +spec: + type: LoadBalancer + ports: + - port: 8000 + targetPort: 8000 + protocol: TCP + selector: + app: esa-webserver \ No newline at end of file diff --git a/azure_edge_iot_ops_jumpstart/esa_fault_detection/yaml/pv.yaml b/azure_edge_iot_ops_jumpstart/esa_fault_detection/yaml/pv.yaml new file mode 100644 index 0000000000..0e8bcf62ff --- /dev/null +++ b/azure_edge_iot_ops_jumpstart/esa_fault_detection/yaml/pv.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: esa-pv + namespace: default +spec: + capacity: + ### This storage capacity value is not enforced at this layer. ### + storage: 10Gi + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: esa + csi: + driver: edgecache.csi.azure.com + readOnly: false + ### Make sure this volumeid is unique in the cluster. You will need to specify it in the spec::volumeName of the PVC. ### + volumeHandle: esa-pv + volumeAttributes: + protocol: edgecache + edgecache-storage-auth: AccountKey + ### FILL IN THE NEXT TWO/THREE VALUES WITH YOUR INFORMATION ### + secretName: esa-secret ### From the previous step, this name will be "{YOUR_STORAGE_ACCOUNT}-secret" + ### If you are using a non-default namespace, please uncomment the line below and add your namespace. ### + #secretNamespace: YOUR_NAMESPACE_HERE + containerName: esademo-container diff --git a/azure_jumpstart_ag/retail/artifacts/L1Files/ScalableCluster.json b/azure_jumpstart_ag/retail/artifacts/L1Files/ScalableCluster.json index b9c6cdf14b..d1058ec19b 100644 --- a/azure_jumpstart_ag/retail/artifacts/L1Files/ScalableCluster.json +++ b/azure_jumpstart_ag/retail/artifacts/L1Files/ScalableCluster.json @@ -40,6 +40,7 @@ "CpuCount": 4, "MemoryInMB": 24576, "DataSizeInGB": 80, + "LogSizeInGB": 5, "Ip4Address": "Ip4Address-null", "TimeoutSeconds": 600, "TpmPassthrough": false diff --git a/azure_jumpstart_ag/retail/artifacts/PowerShell/AgConfig.psd1 b/azure_jumpstart_ag/retail/artifacts/PowerShell/AgConfig.psd1 index bf59adef78..b8e2ffcf03 100644 --- a/azure_jumpstart_ag/retail/artifacts/PowerShell/AgConfig.psd1 +++ b/azure_jumpstart_ag/retail/artifacts/PowerShell/AgConfig.psd1 @@ -99,8 +99,8 @@ ) # VHDX blob url - ProdVHDBlobURL = 'https://jsvhds.blob.core.windows.net/agora/contoso-supermarket-w11/AGBase.vhdx?sp=r&st=2023-05-06T14:38:41Z&se=2033-05-06T22:38:41Z&spr=https&sv=2022-11-02&sr=b&sig=DTDZOvPlzwrjg3gppwVo1TdDZRgPt5AYBfe9YeKEobo%3D' - PreProdVHDBlobURL = 'https://jsvhds.blob.core.windows.net/agora/contoso-supermarket-w11-preprod/*?si=Agora-RL&spr=https&sv=2021-12-02&sr=c&sig=Afl5LPMp5EsQWrFU1bh7ktTsxhtk0QcurW0NVU%2FD76k%3D' + ProdVHDBlobURL = 'https://jsvhds.blob.core.windows.net/agora/base/prod-w11iot/AGBase.vhdx' + PreProdVHDBlobURL = 'https://jsvhds.blob.core.windows.net/agora/base/preprod-w11iot/AGBase.vhdx' # L1 virtual machine configuration HostVMDrive = "V" # This value controls the drive letter where the nested virtual diff --git a/azure_jumpstart_arcbox/artifacts/ArcServersLogonScript.ps1 b/azure_jumpstart_arcbox/artifacts/ArcServersLogonScript.ps1 index 04f5dd619a..5c8f1ec78c 100644 --- a/azure_jumpstart_arcbox/artifacts/ArcServersLogonScript.ps1 +++ b/azure_jumpstart_arcbox/artifacts/ArcServersLogonScript.ps1 @@ -14,8 +14,7 @@ $azureLocation = $env:azureLocation $resourceGroup = $env:resourceGroup # Moved VHD storage account details here to keep only in place to prevent duplicates. -$vhdSourceFolder = "https://jsvhds.blob.core.windows.net/arcbox" -$sas = "*?si=ArcBox-RL&spr=https&sv=2022-11-02&sr=c&sig=vg8VRjM00Ya%2FGa5izAq3b0axMpR4ylsLsQ8ap3BhrnA%3D" +$vhdSourceFolder = "https://jsvhds.blob.core.windows.net/arcbox/*" # Archive exising log file and crate new one $logFilePath = "$Env:ArcBoxLogsDir\ArcServersLogonScript.log" @@ -173,7 +172,7 @@ if ($Env:flavor -ne "DevOps") { $Env:AZCOPY_BUFFER_GB = 4 # Other ArcBox flavors does not have an azcopy network throughput capping Write-Output "Downloading nested VMs VHDX file for SQL. This can take some time, hold tight..." - azcopy cp $vhdSourceFolder/$sas --include-pattern "${SQLvmName}.vhdx" $Env:ArcBoxVMDir --check-length=false --cap-mbps 1200 --log-level=ERROR + azcopy cp $vhdSourceFolder --include-pattern "${SQLvmName}.vhdx" $Env:ArcBoxVMDir --check-length=false --cap-mbps 1200 --log-level=ERROR } # Create the nested VMs if not already created @@ -351,12 +350,12 @@ if ($Env:flavor -ne "DevOps") { if ($Env:flavor -eq "Full") { # The "Full" ArcBox flavor has an azcopy network throughput capping Write-Output "Downloading nested VMs VHDX files. This can take some time, hold tight..." - azcopy cp $vhdSourceFolder/$sas $Env:ArcBoxVMDir --include-pattern "${Win2k19vmName}.vhdx;${Win2k22vmName}.vhdx;${Ubuntu01vmName}.vhdx;${Ubuntu02vmName}.vhdx;" --recursive=true --check-length=false --cap-mbps 1200 --log-level=ERROR + azcopy cp $vhdSourceFolder $Env:ArcBoxVMDir --include-pattern "${Win2k19vmName}.vhdx;${Win2k22vmName}.vhdx;${Ubuntu01vmName}.vhdx;${Ubuntu02vmName}.vhdx;" --recursive=true --check-length=false --cap-mbps 1200 --log-level=ERROR } else { # Other ArcBox flavors does not have an azcopy network throughput capping Write-Output "Downloading nested VMs VHDX files. This can take some time, hold tight..." - azcopy cp $vhdSourceFolder/$sas $Env:ArcBoxVMDir --include-pattern "${Win2k19vmName}.vhdx;${Win2k22vmName}.vhdx;${Ubuntu01vmName}.vhdx;${Ubuntu02vmName}.vhdx;" --recursive=true --check-length=false --log-level=ERROR + azcopy cp $vhdSourceFolder $Env:ArcBoxVMDir --include-pattern "${Win2k19vmName}.vhdx;${Win2k22vmName}.vhdx;${Ubuntu01vmName}.vhdx;${Ubuntu02vmName}.vhdx;" --recursive=true --check-length=false --log-level=ERROR } } diff --git a/azure_jumpstart_arcbox/artifacts/DataOpsLogonScript.ps1 b/azure_jumpstart_arcbox/artifacts/DataOpsLogonScript.ps1 index ed9bd23ec8..6a1b74eaf3 100644 --- a/azure_jumpstart_arcbox/artifacts/DataOpsLogonScript.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DataOpsLogonScript.ps1 @@ -218,7 +218,7 @@ foreach ($cluster in $clusters) { --auto-upgrade false ` --scope cluster ` --release-namespace arc ` - --version 1.28.0 ` + --version 1.29.0 ` --config Microsoft.CustomLocation.ServiceAccount=sa-bootstrapper Write-Host "`n" diff --git a/azure_jumpstart_arcbox/artifacts/DataServicesLogonScript.ps1 b/azure_jumpstart_arcbox/artifacts/DataServicesLogonScript.ps1 index e0dccd9d04..2c84fda11e 100644 --- a/azure_jumpstart_arcbox/artifacts/DataServicesLogonScript.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DataServicesLogonScript.ps1 @@ -100,7 +100,7 @@ az k8s-extension create --name arc-data-services ` --resource-group $Env:resourceGroup ` --auto-upgrade false ` --scope cluster ` - --version 1.28.0 ` + --version 1.29.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-bootstrapper diff --git a/azure_jumpstart_hcibox/artifacts/PowerShell/Bootstrap.ps1 b/azure_jumpstart_hcibox/artifacts/PowerShell/Bootstrap.ps1 index 4ee763bdfd..0051d3e774 100644 --- a/azure_jumpstart_hcibox/artifacts/PowerShell/Bootstrap.ps1 +++ b/azure_jumpstart_hcibox/artifacts/PowerShell/Bootstrap.ps1 @@ -15,7 +15,9 @@ param ( [string]$deployAKSHCI, [string]$deployResourceBridge, [string]$natDNS, - [string]$rdpPort + [string]$rdpPort, + [string]$autoDeployClusterResource, + [string]$autoUpgradeClusterResource ) [System.Environment]::SetEnvironmentVariable('adminUsername', $adminUsername,[System.EnvironmentVariableTarget]::Machine) @@ -34,6 +36,8 @@ param ( [System.Environment]::SetEnvironmentVariable('templateBaseUrl', $templateBaseUrl,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('deployAKSHCI', $deployAKSHCI,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('deployResourceBridge', $deployResourceBridge,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('autoDeployClusterResource', $autoDeployClusterResource,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('autoUpgradeClusterResource', $autoUpgradeClusterResource,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('registerCluster', $registerCluster,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('natDNS', $natDNS,[System.EnvironmentVariableTarget]::Machine) @@ -89,6 +93,17 @@ foreach ($app in $HCIBoxConfig.ChocolateyPackagesList) & choco install $app /y -Force | Write-Output } +# Installing modules +Write-Header "Installing PowerShell modules" + +Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force + +foreach ($module in $HCIBoxConfig.PowerShellModulesList) +{ + Write-Host "Installing $module" + Install-Module -Name $module -AllowClobber -Force +} + Write-Header "Install Azure CLI (64-bit not available via Chocolatey)" $ProgressPreference = 'SilentlyContinue' Invoke-WebRequest -Uri https://aka.ms/installazurecliwindowsx64 -OutFile .\AzureCLI.msi @@ -184,7 +199,7 @@ if (($rdpPort -ne $null) -and ($rdpPort -ne "") -and ($rdpPort -ne "3389")) if ($rdpPort -eq 3389) { netsh advfirewall firewall set rule group="remote desktop" new Enable=Yes - } + } else { $systemroot = get-content env:systemroot @@ -203,5 +218,5 @@ Install-WindowsFeature -Name Hyper-V -IncludeAllSubFeature -IncludeManagementToo # Clean up Bootstrap.log Write-Header "Clean up Bootstrap.log." Stop-Transcript -$logSuppress = Get-Content "$($HCIBoxConfig.Paths.LogsDir)\Bootstrap.log" | Where-Object { $_ -notmatch "Host Application: powershell.exe" } +$logSuppress = Get-Content "$($HCIBoxConfig.Paths.LogsDir)\Bootstrap.log" | Where-Object { $_ -notmatch "Host Application: powershell.exe" } $logSuppress | Set-Content "$($HCIBoxConfig.Paths.LogsDir)\Bootstrap.log" -Force diff --git a/azure_jumpstart_hcibox/artifacts/PowerShell/Generate-ARM-Template.ps1 b/azure_jumpstart_hcibox/artifacts/PowerShell/Generate-ARM-Template.ps1 index 823c89706b..94c2a10394 100644 --- a/azure_jumpstart_hcibox/artifacts/PowerShell/Generate-ARM-Template.ps1 +++ b/azure_jumpstart_hcibox/artifacts/PowerShell/Generate-ARM-Template.ps1 @@ -1,5 +1,5 @@ $WarningPreference = "SilentlyContinue" -$ErrorActionPreference = "Stop" +$ErrorActionPreference = "Stop" $ProgressPreference = 'SilentlyContinue' # Set paths @@ -9,20 +9,8 @@ $Env:HCIBoxDir = "C:\HCIBox" $HCIBoxConfig = Import-PowerShellDataFile -Path $Env:HCIBoxConfigFile Start-Transcript -Path "$($HCIBoxConfig.Paths.LogsDir)\Generate-ARM-Template.log" -# Connect to Azure -Write-Host 'Creating credentials and connecting to Azure' -$azureAppCred = (New-Object System.Management.Automation.PSCredential $env:spnClientID, (ConvertTo-SecureString -String $env:spnClientSecret -AsPlainText -Force)) -Connect-AzAccount -ServicePrincipal -Subscription $env:subscriptionId -Tenant $env:spnTenantId -Credential $azureAppCred - -# Install some modules -Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Install-Module -Name Az.Resources -AllowClobber -Force -Install-Module -Name Az.ConnectedMachine -AllowClobber -Force -Import-Module -Name Az.Resources, Az.ConnectedMachine -Force - -# Add necessary role assignments +# Add necessary role assignments $ErrorActionPreference = "Continue" -New-AzRoleAssignment -ApplicationId $env:spnClientId -RoleDefinitionName "Key Vault Administrator" -ResourceGroup $env:resourceGroup -ErrorAction Continue New-AzRoleAssignment -ObjectId $env:spnProviderId -RoleDefinitionName "Azure Connected Machine Resource Manager" -ResourceGroup $env:resourceGroup -ErrorAction Continue $ErrorActionPreference = "Stop" @@ -41,8 +29,8 @@ foreach ($machine in $arcNodes) { # Get storage account key and convert to base 64 $saKeys = Get-AzStorageAccountKey -ResourceGroupName $env:resourceGroup -Name $env:stagingStorageAccountName $storageAccountAccessKey = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($saKeys[0].value)) - -# Convert user credentials to base64 + +# Convert user credentials to base64 $AzureStackLCM=[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($HCIBoxConfig.LCMDeployUsername):$($HCIBoxConfig.SDNAdminPassword)")) $LocalUser=[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("Administrator:$($HCIBoxConfig.SDNAdminPassword)")) $AzureSPN=[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($env:spnClientId):$($env:spnClientSecret)")) diff --git a/azure_jumpstart_hcibox/artifacts/PowerShell/HCIBox-Config.psd1 b/azure_jumpstart_hcibox/artifacts/PowerShell/HCIBox-Config.psd1 index aa6e81b025..96e12e8448 100644 --- a/azure_jumpstart_hcibox/artifacts/PowerShell/HCIBox-Config.psd1 +++ b/azure_jumpstart_hcibox/artifacts/PowerShell/HCIBox-Config.psd1 @@ -1,7 +1,7 @@ @{ # This is the PowerShell datafile used to provide configuration information for the HCIBox environment. Product keys and password are not encrypted and will be available on all hosts during installation. - + # HCIBox Folders Paths = @{ VMDir = "C:\HCIBox\Virtual Machines" @@ -36,6 +36,11 @@ 'azure-data-studio' ) + PowerShellModulesList = @( + 'Az.Resources', + 'Az.ConnectedMachine' + ) + # VSCode extensions VSCodeExtensions = @( 'ms-vscode-remote.remote-containers', @@ -49,9 +54,9 @@ HostVMDriveLetter = "V" HostVMPath = "V:\VMs" # This value controls the path where the Nested VMs will be stored on all hosts. - guiVHDXPath = "C:\HCIBox\VHD\gui.vhdx" # This value controls the location of the GUI VHDX. + guiVHDXPath = "C:\HCIBox\VHD\gui.vhdx" # This value controls the location of the GUI VHDX. azsHCIVHDXPath = "C:\HCIBox\VHD\azshci.vhdx" # This value controls the location of the Azure Stack HCI VHDX. \ - + MgmtHostConfig = @{ Hostname = "AzSMGMT" IP = "192.168.1.11/24" @@ -71,7 +76,7 @@ StorageBIP = "10.71.2.11" } ) - + # SDN Lab Admin Password SDNAdminPassword = '%staging-password%' # Do not change - this value is replaced during Bootstrap with the password supplied in the ARM deployment @@ -94,7 +99,7 @@ GUIProductKey = "WX4NM-KYWYW-QJJR4-XV3QB-6VM33" # Product key for Windows Server 2019 (Desktop Experience) Datacenter Installation # SDN Lab Domain - SDNDomainFQDN = "jumpstart.local" # Limit name (not the .com) to 14 characters as the name will be used as the NetBIOS name. + SDNDomainFQDN = "jumpstart.local" # Limit name (not the .com) to 14 characters as the name will be used as the NetBIOS name. DCName = "jumpstartdc" # Name of the domain controller virtual machine (limit to 14 characters) # NAT Configuration @@ -105,7 +110,7 @@ natDNS = "%staging-natDNS%" # Do not change - can be configured by passing the optioanl natDNS parameter to the ARM deployment. # Global MTU - SDNLABMTU = 9014 # Controls the MTU for all Hosts. + SDNLABMTU = 9014 # Controls the MTU for all Hosts. #SDN Provisioning ProvisionNC = $false # Provisions Network Controller Automatically. @@ -127,7 +132,7 @@ PhysicalHostInternalIP = "192.168.1.20" # IP Address assigned to Internal Switch vNIC in a Single Host Configuration # SDN Lab DNS - SDNLABDNS = "192.168.1.254" + SDNLABDNS = "192.168.1.254" # SDN Lab Gateway SDNLABRoute = "192.168.1.1" diff --git a/azure_jumpstart_hcibox/artifacts/PowerShell/HCIBoxLogonScript.ps1 b/azure_jumpstart_hcibox/artifacts/PowerShell/HCIBoxLogonScript.ps1 index 15c873821d..729e35127b 100644 --- a/azure_jumpstart_hcibox/artifacts/PowerShell/HCIBoxLogonScript.ps1 +++ b/azure_jumpstart_hcibox/artifacts/PowerShell/HCIBoxLogonScript.ps1 @@ -12,7 +12,7 @@ $HCIBoxConfig = Import-PowerShellDataFile -Path $Env:HCIBoxConfigFile Start-Transcript -Path "$($HCIBoxConfig.Paths.LogsDir)\HCIBoxLogonScript.log" ##################################################################### -# Setup Azure CLI +# Setup Azure CLI and Azure PowerShell ##################################################################### $cliDir = New-Item -Path "$Env:ArcBoxDir\.cli\" -Name ".servers" -ItemType Directory @@ -27,6 +27,11 @@ $Env:AZURE_CONFIG_DIR = $cliDir.FullName Write-Header "Az CLI Login" az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId +# Login to Azure PowerShell with service principal provided by user +$spnpassword = ConvertTo-SecureString $env:spnClientSecret -AsPlainText -Force +$spncredential = New-Object System.Management.Automation.PSCredential ($env:spnClientId, $spnpassword) +Connect-AzAccount -ServicePrincipal -Credential $spncredential -Tenant $env:spntenantId -Subscription $env:subscriptionId + ##################################################################### # Register Azure providers ##################################################################### @@ -43,6 +48,24 @@ az provider register --namespace Microsoft.OperationsManagement --wait az provider register --namespace Microsoft.AzureStackHCI --wait az provider register --namespace Microsoft.ResourceConnector --wait +##################################################################### +# Add RBAC permissions +##################################################################### + +# Add required RBAC permission required for the service principal to deploy Azure Stack HCI + +Write-Header "Add required RBAC permission required for the service principal to deploy Azure Stack HCI" + +$roleAssignment = Get-AzRoleAssignment -ServicePrincipalName $Env:spnClientId -Scope "/subscriptions/$Env:subscriptionId/resourceGroups/$Env:resourceGroup" -RoleDefinitionName "Key Vault Administrator" -ErrorAction SilentlyContinue +if ($null -eq $roleAssignment) { + New-AzRoleAssignment -RoleDefinitionName "Key Vault Administrator" -ServicePrincipalName $Env:spnClientId -Scope "/subscriptions/$Env:subscriptionId/resourceGroups/$Env:resourceGroup" +} + +$roleAssignment = Get-AzRoleAssignment -ServicePrincipalName $Env:spnClientId -Scope "/subscriptions/$Env:subscriptionId/resourceGroups/$Env:resourceGroup" -RoleDefinitionName "Storage Account Contributor" -ErrorAction SilentlyContinue +if ($null -eq $roleAssignment) { + New-AzRoleAssignment -RoleDefinitionName "Storage Account Contributor" -ServicePrincipalName $Env:spnClientId -Scope "/subscriptions/$Env:subscriptionId/resourceGroups/$Env:resourceGroup" +} + ############################################################# # Install VSCode extensions ############################################################# @@ -77,29 +100,27 @@ Stop-Transcript # Build HCI cluster & "$Env:HCIBoxDir\New-HCIBoxCluster.ps1" -& "$Env:HCIBoxDir\Generate-ARM-Template.ps1" - Start-Transcript -Append -Path $Env:HCIBoxLogsDir\HCIBoxLogonScript.log # Changing to Jumpstart ArcBox wallpaper -$code = @' -using System.Runtime.InteropServices; -namespace Win32{ - - public class Wallpaper{ - [DllImport("user32.dll", CharSet=CharSet.Auto)] - static extern int SystemParametersInfo (int uAction , int uParam , string lpvParam , int fuWinIni) ; - - public static void SetWallpaper(string thePath){ - SystemParametersInfo(20,0,thePath,3); +$code = @' +using System.Runtime.InteropServices; +namespace Win32{ + + public class Wallpaper{ + [DllImport("user32.dll", CharSet=CharSet.Auto)] + static extern int SystemParametersInfo (int uAction , int uParam , string lpvParam , int fuWinIni) ; + + public static void SetWallpaper(string thePath){ + SystemParametersInfo(20,0,thePath,3); } } - } + } '@ Write-Header "Changing Wallpaper" $imgPath="$Env:HCIBoxDir\wallpaper.png" -Add-Type $code +Add-Type $code [Win32.Wallpaper]::SetWallpaper($imgPath) # Removing the LogonScript Scheduled Task so it won't run on next reboot @@ -108,7 +129,7 @@ Unregister-ScheduledTask -TaskName "HCIBoxLogonScript" -Confirm:$false # Executing the deployment logs bundle PowerShell script in a new window Write-Header "Uploading Log Bundle" -Invoke-Expression 'cmd /c start Powershell -Command { +Invoke-Expression 'cmd /c start Powershell -Command { $RandomString = -join ((48..57) + (97..122) | Get-Random -Count 6 | % {[char]$_}) Write-Host "Sleeping for 5 seconds before creating deployment logs bundle..." Start-Sleep -Seconds 5 diff --git a/azure_jumpstart_hcibox/artifacts/PowerShell/New-HCIBoxCluster.ps1 b/azure_jumpstart_hcibox/artifacts/PowerShell/New-HCIBoxCluster.ps1 index 5dc2ced12e..24e7127aff 100644 --- a/azure_jumpstart_hcibox/artifacts/PowerShell/New-HCIBoxCluster.ps1 +++ b/azure_jumpstart_hcibox/artifacts/PowerShell/New-HCIBoxCluster.ps1 @@ -23,13 +23,13 @@ function BITSRequest { Get-BitsTransfer $download.name | Resume-BitsTransfer -Asynchronous } [int] $dlProgress = ($download.BytesTransferred / $download.BytesTotal) * 100; - Write-Progress -Activity "Downloading File $filename..." -Status "$dlProgress% Complete:" -PercentComplete $dlProgress; + Write-Progress -Activity "Downloading File $filename..." -Status "$dlProgress% Complete:" -PercentComplete $dlProgress; } Complete-BitsTransfer $download.JobId Write-Progress -Activity "Downloading File $filename..." -Status "Ready" -Completed $ProgressPreference = "SilentlyContinue" } - + function New-InternalSwitch { param ( $HCIBoxConfig @@ -38,27 +38,27 @@ function New-InternalSwitch { $querySwitch = Get-VMSwitch -Name $pswitchname -ErrorAction Ignore if (!$querySwitch) { New-VMSwitch -SwitchType Internal -MinimumBandwidthMode None -Name $pswitchname | Out-Null - + #Assign IP to Internal Switch $InternalAdapter = Get-Netadapter -Name "vEthernet ($pswitchname)" $IP = $HCIBoxConfig.PhysicalHostInternalIP $Prefix = ($($HCIBoxConfig.MgmtHostConfig.IP).Split("/"))[1] $Gateway = $HCIBoxConfig.SDNLABRoute $DNS = $HCIBoxConfig.SDNLABDNS - + $params = @{ AddressFamily = "IPv4" IPAddress = $IP PrefixLength = $Prefix DefaultGateway = $Gateway } - + $InternalAdapter | New-NetIPAddress @params | Out-Null $InternalAdapter | Set-DnsClientServerAddress -ServerAddresses $DNS | Out-Null } - else { - Write-Host "Internal Switch $pswitchname already exists. Not creating a new internal switch." - } + else { + Write-Host "Internal Switch $pswitchname already exists. Not creating a new internal switch." + } } function Get-FormattedWACMAC { @@ -227,7 +227,7 @@ function GenerateAnswerFile { $azsmgmtProdKey = "$($HCIBoxConfig.GUIProductKey)" } $vmServicing = "" - + if ($IsRouterVM -or $IsDCVM) { $components = "" $optionXML = "" @@ -326,7 +326,7 @@ function New-ManagementVM { ) Write-Host "Creating VM $Name" # Create disks - $VHDX1 = New-VHD -ParentPath $VHDXPath -Path "$($HCIBoxConfig.HostVMPath)\$Name.vhdx" -Differencing + $VHDX1 = New-VHD -ParentPath $VHDXPath -Path "$($HCIBoxConfig.HostVMPath)\$Name.vhdx" -Differencing $VHDX2 = New-VHD -Path "$($HCIBoxConfig.HostVMPath)\$Name-Data.vhdx" -SizeBytes 268435456000 -Dynamic # Create VM @@ -345,7 +345,7 @@ function New-ManagementVM { Get-VM $Name | Get-VMNetworkAdapter | Set-VMNetworkAdapter -MacAddressSpoofing On Set-VMNetworkAdapterVlan -VMName $Name -VMNetworkAdapterName SDN -Trunk -NativeVlanId 0 -AllowedVlanIdList 1-200 - Set-VMNetworkAdapterVlan -VMName $Name -VMNetworkAdapterName SDN2 -Trunk -NativeVlanId 0 -AllowedVlanIdList 1-200 + Set-VMNetworkAdapterVlan -VMName $Name -VMNetworkAdapterName SDN2 -Trunk -NativeVlanId 0 -AllowedVlanIdList 1-200 Enable-VMIntegrationService -VMName $Name -Name "Guest Service Interface" return $vmMac @@ -360,16 +360,16 @@ function New-HCINodeVM { ) Write-Host "Creating VM $Name" # Create disks - $VHDX1 = New-VHD -ParentPath $VHDXPath -Path "$($HCIBoxConfig.HostVMPath)\$Name.vhdx" -Differencing + $VHDX1 = New-VHD -ParentPath $VHDXPath -Path "$($HCIBoxConfig.HostVMPath)\$Name.vhdx" -Differencing $VHDX2 = New-VHD -Path "$($HCIBoxConfig.HostVMPath)\$Name-Data.vhdx" -SizeBytes 268435456000 -Dynamic - # Create S2D Storage + # Create S2D Storage New-VHD -Path "$HostVMPath\$Name-S2D_Disk1.vhdx" -SizeBytes $HCIBoxConfig.S2D_Disk_Size -Dynamic | Out-Null New-VHD -Path "$HostVMPath\$Name-S2D_Disk2.vhdx" -SizeBytes $HCIBoxConfig.S2D_Disk_Size -Dynamic | Out-Null New-VHD -Path "$HostVMPath\$Name-S2D_Disk3.vhdx" -SizeBytes $HCIBoxConfig.S2D_Disk_Size -Dynamic | Out-Null New-VHD -Path "$HostVMPath\$Name-S2D_Disk4.vhdx" -SizeBytes $HCIBoxConfig.S2D_Disk_Size -Dynamic | Out-Null New-VHD -Path "$HostVMPath\$Name-S2D_Disk5.vhdx" -SizeBytes $HCIBoxConfig.S2D_Disk_Size -Dynamic | Out-Null - New-VHD -Path "$HostVMPath\$Name-S2D_Disk6.vhdx" -SizeBytes $HCIBoxConfig.S2D_Disk_Size -Dynamic | Out-Null + New-VHD -Path "$HostVMPath\$Name-S2D_Disk6.vhdx" -SizeBytes $HCIBoxConfig.S2D_Disk_Size -Dynamic | Out-Null # Create Nested VM New-VM -Name $Name -MemoryStartupBytes $HCIBoxConfig.NestedVMMemoryinGB -VHDPath $VHDX1.Path -SwitchName $VMSwitch -Generation 2 | Out-Null @@ -395,9 +395,9 @@ function New-HCINodeVM { Get-VM $Name | Get-VMNetworkAdapter | Set-VMNetworkAdapter -MacAddressSpoofing On Set-VMNetworkAdapterVlan -VMName $Name -VMNetworkAdapterName SDN -Trunk -NativeVlanId 0 -AllowedVlanIdList 1-200 - # Set-VMNetworkAdapterVlan -VMName $Name -VMNetworkAdapterName SDN2 -Trunk -NativeVlanId 0 -AllowedVlanIdList 1-200 + # Set-VMNetworkAdapterVlan -VMName $Name -VMNetworkAdapterName SDN2 -Trunk -NativeVlanId 0 -AllowedVlanIdList 1-200 Set-VMNetworkAdapterVlan -VMName $Name -VMNetworkAdapterName StorageA -Trunk -NativeVlanId 0 -AllowedVlanIdList 1-800 - Set-VMNetworkAdapterVlan -VMName $Name -VMNetworkAdapterName StorageB -Trunk -NativeVlanId 0 -AllowedVlanIdList 1-800 + Set-VMNetworkAdapterVlan -VMName $Name -VMNetworkAdapterName StorageB -Trunk -NativeVlanId 0 -AllowedVlanIdList 1-800 Enable-VMIntegrationService -VMName $Name -Name "Guest Service Interface" return $vmMac @@ -409,7 +409,7 @@ function Set-MGMTVHDX { $HCIBoxConfig ) $DriveLetter = $($HCIBoxConfig.HostVMPath).Split(':') - $path = (("\\localhost\") + ($DriveLetter[0] + "$") + ($DriveLetter[1]) + "\" + $($HCIBoxConfig.MgmtHostConfig.Hostname) + ".vhdx") + $path = (("\\localhost\") + ($DriveLetter[0] + "$") + ($DriveLetter[1]) + "\" + $($HCIBoxConfig.MgmtHostConfig.Hostname) + ".vhdx") Write-Host "Performing offline installation of Hyper-V on $($HCIBoxConfig.MgmtHostConfig.Hostname)" Install-WindowsFeature -Vhd $path -Name Hyper-V, RSAT-Hyper-V-Tools, Hyper-V-Powershell -Confirm:$false | Out-Null Start-Sleep -Seconds 20 @@ -421,7 +421,7 @@ function Set-MGMTVHDX { if (!$partition.DriveLetter) { $MountedDrive = "X" $partition | Set-Partition -NewDriveLetter $MountedDrive - } + } else { $MountedDrive = $partition.DriveLetter } @@ -429,8 +429,8 @@ function Set-MGMTVHDX { # Inject Answer File Write-Host "Injecting answer file to $path" $UnattendXML = GenerateAnswerFile -HostName $($HCIBoxConfig.MgmtHostConfig.Hostname) -IsMgmtVM $true -IPAddress $HCIBoxConfig.MgmtHostConfig.IP -VMMac $VMMac -HCIBoxConfig $HCIBoxConfig - - Write-Host "Mounted Disk Volume is: $MountedDrive" + + Write-Host "Mounted Disk Volume is: $MountedDrive" $PantherDir = Get-ChildItem -Path ($MountedDrive + ":\Windows") -Filter "Panther" if (!$PantherDir) { New-Item -Path ($MountedDrive + ":\Windows\Panther") -ItemType Directory -Force | Out-Null } @@ -446,11 +446,11 @@ function Set-MGMTVHDX { Copy-Item -Path $guiVHDXPath -Destination ($MountedDrive + ":\VMs\Base\GUI.vhdx") -Force Copy-Item -Path $azSHCIVHDXPath -Destination ($MountedDrive + ":\VMs\Base\AzSHCI.vhdx") -Force New-Item -Path ($MountedDrive + ":\") -Name "Windows Admin Center" -ItemType Directory -Force | Out-Null - Copy-Item -Path "$($HCIBoxConfig.Paths["WACDir"])\*.msi" -Destination ($MountedDrive + ":\Windows Admin Center") -Recurse -Force + Copy-Item -Path "$($HCIBoxConfig.Paths["WACDir"])\*.msi" -Destination ($MountedDrive + ":\Windows Admin Center") -Recurse -Force # Dismount VHDX Write-Host "Dismounting VHDX File at path $path" - Dismount-VHD $path + Dismount-VHD $path } function Set-HCINodeVHDX { @@ -461,14 +461,14 @@ function Set-HCINodeVHDX { $HCIBoxConfig ) $DriveLetter = $($HCIBoxConfig.HostVMPath).Split(':') - $path = (("\\localhost\") + ($DriveLetter[0] + "$") + ($DriveLetter[1]) + "\" + $Hostname + ".vhdx") + $path = (("\\localhost\") + ($DriveLetter[0] + "$") + ($DriveLetter[1]) + "\" + $Hostname + ".vhdx") Write-Host "Performing offline installation of Hyper-V on $Hostname" Install-WindowsFeature -Vhd $path -Name Hyper-V, RSAT-Hyper-V-Tools, Hyper-V-Powershell -Confirm:$false | Out-Null Start-Sleep -Seconds 5 # Install necessary tools to converge cluster Write-Host "Installing and Configuring Failover Clustering on $Hostname" - Install-WindowsFeature -Vhd $path -Name Failover-Clustering -IncludeAllSubFeature -IncludeManagementTools | Out-Null + Install-WindowsFeature -Vhd $path -Name Failover-Clustering -IncludeAllSubFeature -IncludeManagementTools | Out-Null Start-Sleep -Seconds 15 Write-Host "Mounting VHDX file at $path" @@ -476,25 +476,25 @@ function Set-HCINodeVHDX { if (!$partition.DriveLetter) { $MountedDrive = "Y" $partition | Set-Partition -NewDriveLetter $MountedDrive - } + } else { $MountedDrive = $partition.DriveLetter } Write-Host "Injecting answer file to $path" $UnattendXML = GenerateAnswerFile -HostName $Hostname -IPAddress $IPAddress -VMMac $VMMac -HCIBoxConfig $HCIBoxConfig - Write-Host "Mounted Disk Volume is: $MountedDrive" + Write-Host "Mounted Disk Volume is: $MountedDrive" $PantherDir = Get-ChildItem -Path ($MountedDrive + ":\Windows") -Filter "Panther" if (!$PantherDir) { New-Item -Path ($MountedDrive + ":\Windows\Panther") -ItemType Directory -Force | Out-Null } Set-Content -Value $UnattendXML -Path ($MountedDrive + ":\Windows\Panther\Unattend.xml") -Force New-Item -Path ($MountedDrive + ":\VHD") -ItemType Directory -Force | Out-Null - Copy-Item -Path "$($HCIBoxConfig.Paths.VHDDir)" -Destination ($MountedDrive + ":\VHD") -Recurse -Force + Copy-Item -Path "$($HCIBoxConfig.Paths.VHDDir)" -Destination ($MountedDrive + ":\VHD") -Recurse -Force # Copy-Item -Path "$($HCIBoxConfig.Paths.VHDDir)\Ubuntu.vhdx" -Destination ($MountedDrive + ":\VHD") -Recurse -Force # Dismount VHDX Write-Host "Dismounting VHDX File at path $path" - Dismount-VHD $path + Dismount-VHD $path } function Set-DataDrives { @@ -512,7 +512,7 @@ function Set-DataDrives { Set-Disk -Number 1 -IsOffline $false | Out-Null Initialize-Disk -Number 1 | Out-Null New-Partition -DiskNumber 1 -UseMaximumSize -AssignDriveLetter | Out-Null - Format-Volume -DriveLetter D | Out-Null + Format-Volume -DriveLetter D | Out-Null } } } @@ -522,16 +522,16 @@ function Test-VMAvailable { $VMName, [PSCredential]$Credential ) - Invoke-Command -VMName $VMName -ScriptBlock { + Invoke-Command -VMName $VMName -ScriptBlock { $ErrorOccurred = $false - do { - try { + do { + try { $ErrorActionPreference = 'Stop' Get-VMHost | Out-Null - } - catch { + } + catch { $ErrorOccurred = $true - } + } } while ($ErrorOccurred -eq $true) } -Credential $Credential -ErrorAction Ignore Write-Host "VM $VMName is now online" @@ -549,21 +549,21 @@ function Test-AllVMsAvailable Test-VMAvailable -VMName $VM.Hostname -Credential $Credential } } - + function New-NATSwitch { Param ( $HCIBoxConfig ) Write-Host "Creating NAT Switch on switch $($HCIBoxConfig.InternalSwitch)" - Add-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -DeviceNaming On + Add-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -DeviceNaming On Get-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname | Where-Object { $_.Name -match "Network" } | Connect-VMNetworkAdapter -SwitchName $HCIBoxConfig.natHostVMSwitchName Get-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname | Where-Object { $_.Name -match "Network" } | Rename-VMNetworkAdapter -NewName "NAT" Get-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name NAT | Set-VMNetworkAdapter -MacAddressSpoofing On Add-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name PROVIDER -DeviceNaming On -SwitchName $HCIBoxConfig.InternalSwitch Get-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name PROVIDER | Set-VMNetworkAdapter -MacAddressSpoofing On - Get-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name PROVIDER | Set-VMNetworkAdapterVlan -Access -VlanId $HCIBoxConfig.providerVLAN | Out-Null - + Get-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name PROVIDER | Set-VMNetworkAdapterVlan -Access -VlanId $HCIBoxConfig.providerVLAN | Out-Null + #Create VLAN 110 NIC in order for NAT to work from L3 Connections Add-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name VLAN110 -DeviceNaming On -SwitchName $HCIBoxConfig.InternalSwitch Get-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name VLAN110 | Set-VMNetworkAdapter -MacAddressSpoofing On @@ -572,13 +572,13 @@ function New-NATSwitch { #Create VLAN 200 NIC in order for NAT to work from L3 Connections Add-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name VLAN200 -DeviceNaming On -SwitchName $HCIBoxConfig.InternalSwitch Get-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name VLAN200 | Set-VMNetworkAdapter -MacAddressSpoofing On - Get-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name VLAN200 | Set-VMNetworkAdapterVlan -Access -VlanId $HCIBoxConfig.vlan200VLAN | Out-Null + Get-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name VLAN200 | Set-VMNetworkAdapterVlan -Access -VlanId $HCIBoxConfig.vlan200VLAN | Out-Null #Create Simulated Internet NIC in order for NAT to work from L3 Connections Add-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name simInternet -DeviceNaming On -SwitchName $HCIBoxConfig.InternalSwitch Get-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name simInternet | Set-VMNetworkAdapter -MacAddressSpoofing On Get-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name simInternet | Set-VMNetworkAdapterVlan -Access -VlanId $HCIBoxConfig.simInternetVLAN | Out-Null -} +} function Set-NICs { Param ( @@ -605,12 +605,12 @@ function Set-NICs { # Set Name and IP Addresses on Storage Interfaces $storageNICs = Get-NetAdapterAdvancedProperty | Where-Object { $_.DisplayValue -match "Storage" } foreach ($storageNIC in $storageNICs) { - Rename-NetAdapter -Name $storageNIC.Name -NewName $storageNIC.DisplayValue + Rename-NetAdapter -Name $storageNIC.Name -NewName $storageNIC.DisplayValue } $storageNICs = Get-Netadapter | Where-Object { $_.Name -match "Storage" } foreach ($storageNIC in $storageNICs) { - If ($storageNIC.Name -eq 'StorageA') { New-NetIPAddress -InterfaceAlias $storageNIC.Name -IPAddress $storageAIP -PrefixLength 24 | Out-Null } - If ($storageNIC.Name -eq 'StorageB') { New-NetIPAddress -InterfaceAlias $storageNIC.Name -IPAddress $storageBIP -PrefixLength 24 | Out-Null } + If ($storageNIC.Name -eq 'StorageA') { New-NetIPAddress -InterfaceAlias $storageNIC.Name -IPAddress $storageAIP -PrefixLength 24 | Out-Null } + If ($storageNIC.Name -eq 'StorageB') { New-NetIPAddress -InterfaceAlias $storageNIC.Name -IPAddress $storageBIP -PrefixLength 24 | Out-Null } } # Enable WinRM @@ -637,7 +637,7 @@ function Set-NICs { New-Item -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\CredentialsDelegation ` -Name AllowFreshCredentialsWhenNTLMOnly -Force New-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\CredentialsDelegation\AllowFreshCredentialsWhenNTLMOnly ` - -Name 1 -Value * -PropertyType String -Force + -Name 1 -Value * -PropertyType String -Force } -InDisconnectedSession | Out-Null } } @@ -660,7 +660,7 @@ function Set-FabricNetwork { # Disable Fabric2 Network Adapter # Write-Host "Disabling Fabric2 Adapter" # Get-NetAdapter FABRIC2 | Disable-NetAdapter -Confirm:$false | Out-Null - + # Enable WinRM on AzSMGMT Write-Host "Enabling PSRemoting on $env:COMPUTERNAME" Set-Item WSMan:\localhost\Client\TrustedHosts * -Confirm:$false -Force @@ -670,11 +670,11 @@ function Set-FabricNetwork { Get-ScheduledTask -TaskName ServerManager | Disable-ScheduledTask | Out-Null # Create Hyper-V Networking for AzSMGMT - Import-Module Hyper-V - + Import-Module Hyper-V + Write-Host "Creating VM Switch on $env:COMPUTERNAME" New-VMSwitch -AllowManagementOS $true -Name $HCIBoxConfig.FabricSwitch -NetAdapterName $HCIBoxConfig.FabricNIC -MinimumBandwidthMode None | Out-Null - + Write-Host "Configuring NAT on $env:COMPUTERNAME" $Prefix = ($HCIBoxConfig.natSubnet.Split("/"))[1] $natIP = ($HCIBoxConfig.natSubnet.TrimEnd("0./$Prefix")) + (".1") @@ -713,7 +713,7 @@ function Set-FabricNetwork { Write-Host "Configuring NAT" $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq "Network Adapter" -or $_.RegistryValue -eq "NAT" } - Rename-NetAdapter -name $NIC.name -newname "Internet" | Out-Null + Rename-NetAdapter -name $NIC.name -newname "Internet" | Out-Null $internetIP = $HCIBoxConfig.natHostSubnet.Replace("0/24", "5") $internetGW = $HCIBoxConfig.natHostSubnet.Replace("0/24", "1") Start-Sleep -Seconds 15 @@ -731,7 +731,7 @@ function Set-FabricNetwork { New-NetRoute -DestinationPrefix $HCIBoxConfig.PublicVIPSubnet -NextHop $provGW -InterfaceAlias PROVIDER | Out-Null # Remove Gateway from Fabric NIC - Write-Host "Removing Gateway from Fabric NIC" + Write-Host "Removing Gateway from Fabric NIC" $index = (Get-WmiObject Win32_NetworkAdapter | Where-Object { $_.netconnectionid -match "vSwitch-Fabric" }).InterfaceIndex Remove-NetRoute -InterfaceIndex $index -DestinationPrefix "0.0.0.0/0" -Confirm:$false } @@ -757,7 +757,7 @@ function New-DCVM { $VMName = $HCIBoxConfig.DCName # Create Virtual Machine - Write-Host "Creating $VMName differencing disks" + Write-Host "Creating $VMName differencing disks" New-VHD -ParentPath ($ParentDiskPath + $OSVHDX) -Path ($vmpath + $VMName + '\' + $VMName + '.vhdx') -Differencing | Out-Null Write-Host "Creating $VMName virtual machine" @@ -769,13 +769,13 @@ function New-DCVM { Write-Host "Configuring $VMName's networking" Remove-VMNetworkAdapter -VMName $VMName -Name "Network Adapter" | Out-Null Add-VMNetworkAdapter -VMName $VMName -Name $HCIBoxConfig.DCName -SwitchName $HCIBoxConfig.FabricSwitch -DeviceNaming 'On' | Out-Null - + Write-Host "Configuring $VMName's settings" Set-VMProcessor -VMName $VMName -Count 2 | Out-Null Set-VM -Name $VMName -AutomaticStartAction Start -AutomaticStopAction ShutDown | Out-Null # Inject Answer File - Write-Host "Mounting and injecting answer file into the $VMName VM." + Write-Host "Mounting and injecting answer file into the $VMName VM." New-Item -Path "C:\TempMount" -ItemType Directory | Out-Null Mount-WindowsImage -Path "C:\TempMount" -Index 1 -ImagePath ($vmpath + $VMName + '\' + $VMName + '.vhdx') | Out-Null Write-Host "Applying Unattend file to Disk Image..." @@ -786,9 +786,9 @@ function New-DCVM { Remove-Item "C:\TempMount" | Out-Null # Start Virtual Machine - Write-Host "Starting Virtual Machine $VMName" + Write-Host "Starting Virtual Machine $VMName" Start-VM -Name $VMName | Out-Null - + # Wait until the VM is restarted while ((Invoke-Command -VMName $VMName -Credential $using:localCred { "Test" } -ea SilentlyContinue) -ne "Test") { Start-Sleep -Seconds 1 } @@ -804,7 +804,7 @@ function New-DCVM { Write-Host "Configuring NIC Settings for $DCName" $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq $DCName } - Rename-NetAdapter -name $NIC.name -newname $DCName | Out-Null + Rename-NetAdapter -name $NIC.name -newname $DCName | Out-Null New-NetIPAddress -InterfaceAlias $DCName -IPAddress $ip -PrefixLength $PrefixLength -DefaultGateway $SDNLabRoute | Out-Null Set-DnsClientServerAddress -InterfaceAlias $DCName -ServerAddresses $IP | Out-Null Install-WindowsFeature -name AD-Domain-Services -IncludeManagementTools | Out-Null @@ -820,7 +820,7 @@ function New-DCVM { Write-Host "Stopping $VMName" Get-VM $VMName | Stop-VM Write-Host "Starting $VMName" - Get-VM $VMName | Start-VM + Get-VM $VMName | Start-VM # Wait until DC is created and rebooted while ((Invoke-Command -VMName $VMName -Credential $domainCred -ArgumentList $HCIBoxConfig.DCName { (Get-ADDomainController $args[0]).enabled } -ea SilentlyContinue) -ne $true) { Start-Sleep -Seconds 5 } @@ -846,7 +846,7 @@ function New-DCVM { PasswordNeverExpires = $true } New-ADUser @params - + $params = @{ Name = $adminUser GivenName = 'Jumpstart' @@ -864,7 +864,7 @@ function New-DCVM { $params.Name = 'NC Client' $params.Surname = 'Client' $params.SamAccountName = 'NCClient' - $params.UserPrincipalName = "NCClient@$SDNDomainFQDN" + $params.UserPrincipalName = "NCClient@$SDNDomainFQDN" New-ADUser @params New-ADGroup -name “NCAdmins” -groupscope Global @@ -887,8 +887,8 @@ function New-DCVM { Write-Host "Adding DNS Forwarders" Add-DnsServerForwarder $HCIBoxConfig.natDNS - # Create Enterprise CA - Write-Host "Installing and Configuring Active Directory Certificate Services and Certificate Templates" + # Create Enterprise CA + Write-Host "Installing and Configuring Active Directory Certificate Services and Certificate Templates" Install-WindowsFeature -Name AD-Certificate -IncludeAllSubFeature -IncludeManagementTools | Out-Null Install-AdcsCertificationAuthority -CAtype 'EnterpriseRootCa' -CryptoProviderName 'ECDSA_P256#Microsoft Software Key Storage Provider' -KeyLength 256 -HashAlgorithmName 'SHA256' -ValidityPeriod 'Years' -ValidityPeriodUnits 10 -Confirm:$false | Out-Null @@ -896,23 +896,23 @@ function New-DCVM { $filter = "(CN=WebServer)" $ConfigContext = ([ADSI]"LDAP://RootDSE").configurationNamingContext $ConfigContext = "CN=Certificate Templates,CN=Public Key Services,CN=Services,$ConfigContext" - $ds = New-object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$ConfigContext", $filter) - $Template = $ds.Findone().GetDirectoryEntry() + $ds = New-object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$ConfigContext", $filter) + $Template = $ds.Findone().GetDirectoryEntry() if ($null -ne $Template) { - $objUser = New-Object System.Security.Principal.NTAccount("Domain Computers") - $objectGuid = New-Object Guid 0e10c968-78fb-11d2-90d4-00c04f79dc55 - $ADRight = [System.DirectoryServices.ActiveDirectoryRights]"ExtendedRight" - $ACEType = [System.Security.AccessControl.AccessControlType]"Allow" - $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $objUser, $ADRight, $ACEType, $objectGuid - $Template.ObjectSecurity.AddAccessRule($ACE) + $objUser = New-Object System.Security.Principal.NTAccount("Domain Computers") + $objectGuid = New-Object Guid 0e10c968-78fb-11d2-90d4-00c04f79dc55 + $ADRight = [System.DirectoryServices.ActiveDirectoryRights]"ExtendedRight" + $ACEType = [System.Security.AccessControl.AccessControlType]"Allow" + $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $objUser, $ADRight, $ACEType, $objectGuid + $Template.ObjectSecurity.AddAccessRule($ACE) $Template.commitchanges() - } - + } + CMD.exe /c "certutil -setreg ca\ValidityPeriodUnits 8" | Out-Null Restart-Service CertSvc Start-Sleep -Seconds 60 - + #Issue Certificate Template CMD.exe /c "certutil -SetCATemplates +WebServer" } @@ -934,7 +934,7 @@ function Set-DHCPServerOnDC { # Set up DHCP scope for Arc resource bridge Invoke-Command -VMName $HCIBoxConfig.DCName -Credential $using:domainCred -ArgumentList $HCIBoxConfig -ScriptBlock { $HCIBoxConfig = $args[0] - + Write-Host "Configuring NIC settings for $DCName VLAN200" $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq "VLAN200" } Rename-NetAdapter -name $NIC.name -newname VLAN200 | Out-Null @@ -975,22 +975,22 @@ function New-RouterVM { $ParentDiskPath = "C:\VMs\Base\AzSHCI.vhdx" $vmpath = "D:\VMs\" $VMName = $HCIBoxConfig.BGPRouterName - + # Create Host OS Disk Write-Host "Creating $VMName differencing disks" New-VHD -ParentPath $ParentDiskPath -Path ($vmpath + $VMName + '\' + $VMName + '.vhdx') -Differencing | Out-Null - + # Create VM Write-Host "Creating the $VMName VM." New-VM -Name $VMName -VHDPath ($vmpath + $VMName + '\' + $VMName + '.vhdx') -Path ($vmpath + $VMName) -Generation 2 | Out-Null - + # Set VM Configuration Write-Host "Setting $VMName's VM Configuration" Set-VMMemory -VMName $VMName -DynamicMemoryEnabled $true -StartupBytes $HCIBoxConfig.MEM_BGP -MinimumBytes 500MB -MaximumBytes $HCIBoxConfig.MEM_BGP | Out-Null - Remove-VMNetworkAdapter -VMName $VMName -Name "Network Adapter" | Out-Null + Remove-VMNetworkAdapter -VMName $VMName -Name "Network Adapter" | Out-Null Set-VMProcessor -VMName $VMName -Count 2 | Out-Null Set-VM -Name $VMName -AutomaticStopAction ShutDown | Out-Null - + # Configure VM Networking Write-Host "Configuring $VMName's Networking" Add-VMNetworkAdapter -VMName $VMName -Name Mgmt -SwitchName $HCIBoxConfig.FabricSwitch -DeviceNaming On @@ -1002,31 +1002,31 @@ function New-RouterVM { Set-VMNetworkAdapterVlan -VMName $VMName -VMNetworkAdapterName VLAN110 -Access -VlanId $HCIBoxConfig.vlan110VLAN Set-VMNetworkAdapterVlan -VMName $VMName -VMNetworkAdapterName VLAN200 -Access -VlanId $HCIBoxConfig.vlan200VLAN Set-VMNetworkAdapterVlan -VMName $VMName -VMNetworkAdapterName SIMInternet -Access -VlanId $HCIBoxConfig.simInternetVLAN - Add-VMNetworkAdapter -VMName $VMName -Name NAT -SwitchName NAT -DeviceNaming On - + Add-VMNetworkAdapter -VMName $VMName -Name NAT -SwitchName NAT -DeviceNaming On + # Mount disk and inject Answer File - Write-Host "Mounting Disk Image and Injecting Answer File into the $VMName VM." + Write-Host "Mounting Disk Image and Injecting Answer File into the $VMName VM." New-Item -Path "C:\TempBGPMount" -ItemType Directory | Out-Null Mount-WindowsImage -Path "C:\TempBGPMount" -Index 1 -ImagePath ($vmpath + $VMName + '\' + $VMName + '.vhdx') | Out-Null - New-Item -Path C:\TempBGPMount\windows -ItemType Directory -Name Panther -Force | Out-Null + New-Item -Path C:\TempBGPMount\windows -ItemType Directory -Name Panther -Force | Out-Null Set-Content -Value $using:Unattend -Path "C:\TempBGPMount\Windows\Panther\Unattend.xml" -Force - + # Enable remote access Write-Host "Enabling Remote Access" Enable-WindowsOptionalFeature -Path C:\TempBGPMount -FeatureName RasRoutingProtocols -All -LimitAccess | Out-Null Enable-WindowsOptionalFeature -Path C:\TempBGPMount -FeatureName RemoteAccessPowerShell -All -LimitAccess | Out-Null - Write-Host "Dismounting Disk Image for $VMName VM." + Write-Host "Dismounting Disk Image for $VMName VM." Dismount-WindowsImage -Path "C:\TempBGPMount" -Save | Out-Null Remove-Item "C:\TempBGPMount" - + # Start the VM Write-Host "Starting $VMName VM." - Start-VM -Name $VMName - + Start-VM -Name $VMName + # Wait for VM to be started - while ((Invoke-Command -VMName $VMName -Credential $localcred { "Test" } -ea SilentlyContinue) -ne "Test") { Start-Sleep -Seconds 1 } - - Write-Host "Configuring $VMName" + while ((Invoke-Command -VMName $VMName -Credential $localcred { "Test" } -ea SilentlyContinue) -ne "Test") { Start-Sleep -Seconds 1 } + + Write-Host "Configuring $VMName" Invoke-Command -VMName $VMName -Credential $localCred -ArgumentList $HCIBoxConfig -ScriptBlock { $HCIBoxConfig = $args[0] $DNS = $HCIBoxConfig.SDNLABDNS @@ -1042,30 +1042,30 @@ function New-RouterVM { $VLAN110PFX = $HCIBoxConfig.BGPRouterIP_VLAN110.Split("/")[1] $simInternetIP = $HCIBoxConfig.BGPRouterIP_SimulatedInternet.Split("/")[0] $simInternetPFX = $HCIBoxConfig.BGPRouterIP_SimulatedInternet.Split("/")[1] - + # Renaming NetAdapters and setting up the IPs inside the VM using CDN parameters Write-Host "Configuring $env:COMPUTERNAME's Networking" $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq "Mgmt" } Rename-NetAdapter -name $NIC.name -newname "Mgmt" | Out-Null New-NetIPAddress -InterfaceAlias "Mgmt" -IPAddress $MGMTIP -PrefixLength $MGMTPFX | Out-Null Set-DnsClientServerAddress -InterfaceAlias “Mgmt” -ServerAddresses $DNS | Out-Null - + $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq "PROVIDER" } Rename-NetAdapter -name $NIC.name -newname "PROVIDER" | Out-Null New-NetIPAddress -InterfaceAlias "PROVIDER" -IPAddress $PNVIP -PrefixLength $PNVPFX | Out-Null - + $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq "VLAN200" } Rename-NetAdapter -name $NIC.name -newname "VLAN200" | Out-Null New-NetIPAddress -InterfaceAlias "VLAN200" -IPAddress $VLANIP -PrefixLength $VLANPFX | Out-Null - + $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq "VLAN110" } Rename-NetAdapter -name $NIC.name -newname "VLAN110" | Out-Null New-NetIPAddress -InterfaceAlias "VLAN110" -IPAddress $VLAN110IP -PrefixLength $VLAN110PFX | Out-Null - + $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq "SIMInternet" } Rename-NetAdapter -name $NIC.name -newname "SIMInternet" | Out-Null - New-NetIPAddress -InterfaceAlias "SIMInternet" -IPAddress $simInternetIP -PrefixLength $simInternetPFX | Out-Null - + New-NetIPAddress -InterfaceAlias "SIMInternet" -IPAddress $simInternetIP -PrefixLength $simInternetPFX | Out-Null + # Configure NAT $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq "NAT" } Rename-NetAdapter -name $NIC.name -newname "NAT" | Out-Null @@ -1076,15 +1076,15 @@ function New-RouterVM { if ($natDNS) { Set-DnsClientServerAddress -InterfaceAlias "NAT" -ServerAddresses $natDNS | Out-Null } - + # Configure Trusted Hosts Write-Host "Configuring Trusted Hosts on $env:COMPUTERNAME" Set-Item WSMan:\localhost\Client\TrustedHosts * -Confirm:$false -Force - + # Installing Remote Access - Write-Host "Installing Remote Access on $env:COMPUTERNAME" + Write-Host "Installing Remote Access on $env:COMPUTERNAME" Install-RemoteAccess -VPNType RoutingOnly | Out-Null - + # Adding a BGP Router to the VM # Write-Host "Creating BGP Router on $env:COMPUTERNAME" # Add-BgpRouter -BGPIdentifier $PNVIP -LocalASN $HCIBoxConfig.BGPRouterASN -TransitRouting 'Enabled' -ClusterId 1 -RouteReflector 'Enabled' @@ -1109,13 +1109,13 @@ function New-RouterVM { # Add-BgpPeer @params -PassThru # $params.Name = 'GW02' # $params.PeerIPAddress = $GW02IP - # Add-BgpPeer @params -PassThru + # Add-BgpPeer @params -PassThru # } - + # Enable Large MTU Write-Host "Configuring MTU on all Adapters" - Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Set-NetAdapterAdvancedProperty -RegistryValue $HCIBoxConfig.SDNLABMTU -RegistryKeyword "*JumboPacket" - } + Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Set-NetAdapterAdvancedProperty -RegistryValue $HCIBoxConfig.SDNLABMTU -RegistryKeyword "*JumboPacket" + } } } @@ -1165,9 +1165,9 @@ function New-AdminCenterVM { Set-VMNetworkAdapter -VMName $VMName -StaticMacAddress $HCIBoxConfig.WACMAC # Mac address is linked to the answer file required in next step # Apply custom Unattend.xml file - New-Item -Path C:\TempWACMount\windows -ItemType Directory -Name Panther -Force | Out-Null - - Write-Host "Mounting and Injecting Answer File into the $VMName VM." + New-Item -Path C:\TempWACMount\windows -ItemType Directory -Name Panther -Force | Out-Null + + Write-Host "Mounting and Injecting Answer File into the $VMName VM." Set-Content -Value $using:UnattendXML -Path "C:\TempWACMount\Windows\Panther\Unattend.xml" -Force Write-Host "Dismounting Disk" Dismount-WindowsImage -Path "C:\TempWACMount" -Save | Out-Null @@ -1194,14 +1194,14 @@ function New-AdminCenterVM { Enable-WindowsOptionalFeature -FeatureName RasRoutingProtocols -All -LimitAccess -Online | Out-Null Enable-WindowsOptionalFeature -FeatureName RemoteAccessPowerShell -All -LimitAccess -Online | Out-Null - Write-Host "Rename Network Adapter in $VMName" + Write-Host "Rename Network Adapter in $VMName" Get-NetAdapter | Rename-NetAdapter -NewName Fabric Write-Host "Configuring MTU on all Adapters" - Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Set-NetAdapterAdvancedProperty -RegistryValue $HCIBoxConfig.SDNLABMTU -RegistryKeyword "*JumboPacket" + Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Set-NetAdapterAdvancedProperty -RegistryValue $HCIBoxConfig.SDNLABMTU -RegistryKeyword "*JumboPacket" # Set Gateway $index = (Get-WmiObject Win32_NetworkAdapter | Where-Object { $_.netconnectionid -eq "Fabric" }).InterfaceIndex - $NetInterface = Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object { $_.InterfaceIndex -eq $index } + $NetInterface = Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object { $_.InterfaceIndex -eq $index } $NetInterface.SetGateways($HCIBoxConfig.SDNLABRoute) | Out-Null # Enable CredSSP @@ -1226,7 +1226,7 @@ function New-AdminCenterVM { Import-Module ServerManager Install-WindowsFeature -Name RSAT-NetworkController -IncludeAllSubFeature -IncludeManagementTools | Out-Null } - + # Install Windows features Write-Host "Installing Hyper-V RSAT Tools on $VMName" Install-WindowsFeature -Name RSAT-Hyper-V-Tools -IncludeAllSubFeature -IncludeManagementTools | Out-Null @@ -1240,36 +1240,36 @@ function New-AdminCenterVM { # Stop Server Manager from starting on boot Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\ServerManager" -Name "DoNotOpenServerManagerAtLogon" -Value 1 - + # Create BGP Router Add-BgpRouter -BGPIdentifier $WACIP -LocalASN $HCIBoxConfig.WACASN -TransitRouting 'Enabled' -ClusterId 1 -RouteReflector 'Enabled' $RequestInf = @" -[Version] +[Version] Signature="`$Windows NT$" -[NewRequest] +[NewRequest] Subject = "CN=$($HCIBoxConfig.WACVMName).$($HCIBoxConfig.SDNDomainFQDN)" Exportable = True -KeyLength = 2048 -KeySpec = 1 -KeyUsage = 0xA0 -MachineKeySet = True -ProviderName = "Microsoft RSA SChannel Cryptographic Provider" -ProviderType = 12 -SMIME = FALSE +KeyLength = 2048 +KeySpec = 1 +KeyUsage = 0xA0 +MachineKeySet = True +ProviderName = "Microsoft RSA SChannel Cryptographic Provider" +ProviderType = 12 +SMIME = FALSE RequestType = CMC FriendlyName = "HCIBox Windows Admin Cert" -[Strings] -szOID_SUBJECT_ALT_NAME2 = "2.5.29.17" -szOID_ENHANCED_KEY_USAGE = "2.5.29.37" -szOID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1" +[Strings] +szOID_SUBJECT_ALT_NAME2 = "2.5.29.17" +szOID_ENHANCED_KEY_USAGE = "2.5.29.37" +szOID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1" szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2" -[Extensions] -%szOID_SUBJECT_ALT_NAME2% = "{text}dns=$($HCIBoxConfig.WACVMName).$($HCIBoxConfig.SDNDomainFQDN)" +[Extensions] +%szOID_SUBJECT_ALT_NAME2% = "{text}dns=$($HCIBoxConfig.WACVMName).$($HCIBoxConfig.SDNDomainFQDN)" %szOID_ENHANCED_KEY_USAGE% = "{text}%szOID_PKIX_KP_SERVER_AUTH%,%szOID_PKIX_KP_CLIENT_AUTH%" -[RequestAttributes] +[RequestAttributes] CertificateTemplate= WebServer "@ @@ -1277,7 +1277,7 @@ CertificateTemplate= WebServer Set-Content -Value $RequestInf -Path C:\WACCert\WACCert.inf -Force | Out-Null Register-PSSessionConfiguration -Name 'Microsoft.SDNNested' -RunAsCredential $domainCred -MaximumReceivedDataSizePerCommandMB 1000 -MaximumReceivedObjectSizeMB 1000 - Write-Host "Requesting and installing SSL Certificate on $using:VMName" + Write-Host "Requesting and installing SSL Certificate on $using:VMName" Invoke-Command -ComputerName $VMName -ConfigurationName 'Microsoft.SDNNested' -Credential $domainCred -ArgumentList $HCIBoxConfig -ScriptBlock { $HCIBoxConfig = $args[0] # Get the CA Name @@ -1292,7 +1292,7 @@ CertificateTemplate= WebServer # Request and Accept SSL Certificate Set-Location C:\WACCert certreq -q -f -new WACCert.inf WACCert.req - certreq -q -config $CertAuth -attrib "CertificateTemplate:webserver" -submit WACCert.req WACCert.cer + certreq -q -config $CertAuth -attrib "CertificateTemplate:webserver" -submit WACCert.req WACCert.cer certreq -q -accept WACCert.cer certutil -q -store my @@ -1321,7 +1321,7 @@ CertificateTemplate= WebServer Write-Host 'Installing Az PowerShell' $expression = "choco install az.powershell -y --limit-output" Invoke-Expression $expression - + # Create Shortcut for Hyper-V Manager Write-Host "Creating Shortcut for Hyper-V Manager" Copy-Item -Path "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools\Hyper-V Manager.lnk" -Destination "C:\Users\Public\Desktop" @@ -1337,14 +1337,14 @@ CertificateTemplate= WebServer # Create Shortcut for Active Directory Users and Computers Write-Host "Creating Shortcut for AD Users and Computers" Copy-Item -Path "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools\Active Directory Users and Computers.lnk" -Destination "C:\Users\Public\Desktop" - + # Set Network Profiles - Get-NetConnectionProfile | Where-Object { $_.NetworkCategory -eq "Public" } | Set-NetConnectionProfile -NetworkCategory Private | Out-Null - + Get-NetConnectionProfile | Where-Object { $_.NetworkCategory -eq "Public" } | Set-NetConnectionProfile -NetworkCategory Private | Out-Null + # Disable Automatic Updates $WUKey = "HKLM:\software\Policies\Microsoft\Windows\WindowsUpdate" New-Item -Path $WUKey -Force | Out-Null - New-ItemProperty -Path $WUKey -Name AUOptions -PropertyType Dword -Value 2 -Force | Out-Null + New-ItemProperty -Path $WUKey -Name AUOptions -PropertyType Dword -Value 2 -Force | Out-Null # Install Kubectl Write-Host 'Installing kubectl' @@ -1380,14 +1380,14 @@ CertificateTemplate= WebServer New-ItemProperty -Path $edgePolicyRegistryPath -Name $firstRunRegistryName -Value $firstRunRegistryValue -PropertyType DWORD -Force New-ItemProperty -Path $edgePolicyRegistryPath -Name $savePasswordRegistryName -Value $savePasswordRegistryValue -PropertyType DWORD -Force - Set-ItemProperty -Path $desktopSettingsRegistryPath -Name $autoArrangeRegistryName -Value $autoArrangeRegistryValue -Force + Set-ItemProperty -Path $desktopSettingsRegistryPath -Name $autoArrangeRegistryName -Value $autoArrangeRegistryValue -Force } } } function Test-InternetConnect { $testIP = $HCIBoxConfig.natDNS - $ErrorActionPreference = "Stop" + $ErrorActionPreference = "Stop" $intConnect = Test-NetConnection -ComputerName $testip -Port 53 if (!$intConnect.TcpTestSucceeded) { @@ -1432,7 +1432,7 @@ function Set-HCIDeployPrereqs { $HCIBoxConfig = $args[0] $domainCredNoDomain = new-object -typename System.Management.Automation.PSCredential ` -argumentlist ($HCIBoxConfig.LCMDeployUsername), (ConvertTo-SecureString $HCIBoxConfig.SDNAdminPassword -AsPlainText -Force) - + Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser Install-Module AsHciADArtifactsPreCreationTool -Repository PSGallery -Force -Confirm:$false $domainName = $HCIBoxConfig.SDNDomainFQDN.Split('.') @@ -1448,7 +1448,7 @@ function Set-HCIDeployPrereqs { New-HciAdObjectsPreCreation -AzureStackLCMUserCredential $domainCredNoDomain -AsHciOUName $ouName } } - + foreach ($node in $HCIBoxConfig.NodeHostConfig) { Invoke-Command -VMName $node.Hostname -Credential $localCred -ArgumentList $env:subscriptionId, $env:spnTenantId, $env:spnClientID, $env:spnClientSecret, $env:resourceGroup, $env:azureLocation -ScriptBlock { $subId = $args[0] @@ -1457,19 +1457,19 @@ function Set-HCIDeployPrereqs { $clientSecret = $args[3] $resourceGroup = $args[4] $location = $args[5] - + # Prep nodes for Azure Arc onboarding winrm quickconfig -quiet netsh advfirewall firewall add rule name="ICMP Allow incoming V4 echo request" protocol=icmpv4:8,any dir=in action=allow - + # Register PSGallery as a trusted repo Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force Register-PSRepository -Default -InstallationPolicy Trusted -ErrorAction SilentlyContinue Set-PSRepository -Name PSGallery -InstallationPolicy Trusted - - #Install Arc registration script from PSGallery + + #Install Arc registration script from PSGallery Install-Module AzsHCI.ARCinstaller -Force - + #Install required PowerShell modules in your node for registration Install-Module Az.Accounts -Force Install-Module Az.ConnectedMachine -Force @@ -1481,18 +1481,116 @@ function Set-HCIDeployPrereqs { # Workaround for BITS transfer issue Get-NetAdapter StorageA | Disable-NetAdapter -Confirm:$false | Out-Null Get-NetAdapter StorageB | Disable-NetAdapter -Confirm:$false | Out-Null - + #Invoke the registration script. Invoke-AzStackHciArcInitialization -SubscriptionID $subId -ResourceGroup $resourceGroup -TenantID $tenantId -Region $location -Cloud "AzureCloud" -ArmAccessToken $armtoken.Token -AccountID $clientId - + Get-NetAdapter StorageA | Enable-NetAdapter -Confirm:$false | Out-Null Get-NetAdapter StorageB | Enable-NetAdapter -Confirm:$false | Out-Null } } + + Get-AzConnectedMachine -ResourceGroupName $env:resourceGroup | foreach-object { + + Write-Host "Checking extension status for $($PSItem.Name)" + + $requiredExtensions = @('AzureEdgeTelemetryAndDiagnostics', 'AzureEdgeDeviceManagement', 'AzureEdgeLifecycleManager') + $attempts = 0 + $maxAttempts = 90 + + do { + $attempts++ + $extension = Get-AzConnectedMachineExtension -MachineName $PSItem.Name -ResourceGroupName $env:resourceGroup + + foreach ($extensionName in $requiredExtensions) { + $extensionTest = $extension | Where-Object { $_.Name -eq $extensionName } + if (!$extensionTest) { + Write-Host "$($PSItem.Name) : Extension $extensionName is missing" -ForegroundColor Yellow + $Wait = $true + } elseif ($extensionTest.ProvisioningState -ne "Succeeded") { + Write-Host "$($PSItem.Name) : Extension $extensionName is in place, but not yet provisioned. Current state: $($extensionTest.ProvisioningState)" -ForegroundColor Yellow + $Wait = $true + } elseif ($extensionTest.ProvisioningState -eq "Succeeded") { + Write-Host "$($PSItem.Name) : Extension $extensionName is in place and provisioned. Current state: $($extensionTest.ProvisioningState)" -ForegroundColor Green + $Wait = $false + } + } + + if ($Wait){ + Write-Host "Waiting for extension installation to complete, sleeping for 2 minutes. Attempt $attempts of $maxAttempts" + Start-Sleep -Seconds 120 + } else { + break + } + + } while ($attempts -lt $maxAttempts) + + } + +} + +function Update-HCICluster { + param ( + $HCIBoxConfig, + [PSCredential]$domainCred + ) + + $session = New-PSSession -VMName $HCIBoxConfig.NodeHostConfig[0].Hostname -Credential $domainCred + + Write-Host "Getting current version of the cluster" + + Invoke-Command -Session $session -ScriptBlock { + + Get-StampInformation | Select-Object StampVersion,ServicesVersion,InitialDeployedVersion + + } + + Write-Host "Test environment readiness for update" + + Invoke-Command -Session $session -ScriptBlock { + + Test-EnvironmentReadiness | Select-Object Name,Status,Severity + + } + + Write-Host "Getting available updates" + + Invoke-Command -Session $session -ScriptBlock { + + Get-SolutionUpdate | Select-Object DisplayName, State + + } -OutVariable updates + + if ($updates.Count -gt 0) { + + Write-Host "Starting update process" + + Invoke-Command -Session $session -ScriptBlock { + + Get-SolutionUpdate | Start-SolutionUpdate + + } + + } + else { + + Write-Host "No updates available" + return + + } + + Invoke-Command -Session $session -ScriptBlock { + + Get-SolutionUpdate | Select-Object Version,State,UpdateStateProperties,HealthState + + } + + $session | Remove-PSSession + } #endregion - + #region Main $guiVHDXPath = $HCIBoxConfig.guiVHDXPath $azSHCIVHDXPath = $HCIBoxConfig.azSHCIVHDXPath @@ -1515,7 +1613,7 @@ foreach ($path in $HCIBoxConfig.Paths.GetEnumerator()) { } # Download HCIBox VHDs -Write-Host "[Build cluster - Step 1/10] Downloading HCIBox VHDs" -ForegroundColor Green +Write-Host "[Build cluster - Step 1/11] Downloading HCIBox VHDs" -ForegroundColor Green BITSRequest -Params @{'Uri'='https://aka.ms/VHD-HCIBox-HCI-Prod'; 'Filename'="$($HCIBoxConfig.Paths.VHDDir)\AZSHCI.vhdx" } BITSRequest -Params @{'Uri'='https://aka.ms/VHDHash-HCIBox-HCI-Prod'; 'Filename'="$($HCIBoxConfig.Paths.VHDDir)\AZSHCI.sha256" } $checksum = Get-FileHash -Path "$($HCIBoxConfig.Paths.VHDDir)\AZSHCI.vhdx" @@ -1525,7 +1623,7 @@ if ($checksum.Hash -eq $hash) { } else { Write-Error "AZSCHI.vhdx is corrupt. Aborting deployment. Re-run C:\HCIBox\HCIBoxLogonScript.ps1 to retry" - throw + throw } BITSRequest -Params @{'Uri'='https://aka.ms/VHD-HCIBox-Mgmt-Prod'; 'Filename'="$($HCIBoxConfig.Paths.VHDDir)\GUI.vhdx"} BITSRequest -Params @{'Uri'='https://aka.ms/VHDHash-HCIBox-Mgmt-Prod'; 'Filename'="$($HCIBoxConfig.Paths.VHDDir)\GUI.sha256" } @@ -1536,7 +1634,7 @@ if ($checksum.Hash -eq $hash) { } else { Write-Error "GUI.vhdx is corrupt. Aborting deployment. Re-run C:\HCIBox\HCIBoxLogonScript.ps1 to retry" - throw + throw } # BITSRequest -Params @{'Uri'='https://partner-images.canonical.com/hyper-v/desktop/focal/current/ubuntu-focal-hyperv-amd64-ubuntu-desktop-hyperv.vhdx.zip'; 'Filename'="$($HCIBoxConfig.Paths.VHDDir)\Ubuntu.vhdx.zip"} # Expand-Archive -Path "$($HCIBoxConfig.Paths.VHDDir)\Ubuntu.vhdx.zip" -DestinationPath $($HCIBoxConfig.Paths.VHDDir) @@ -1550,7 +1648,7 @@ $domainCred = new-object -typename System.Management.Automation.PSCredential ` -argumentlist (($HCIBoxConfig.SDNDomainFQDN.Split(".")[0]) +"\Administrator"), (ConvertTo-SecureString $HCIBoxConfig.SDNAdminPassword -AsPlainText -Force) # Enable PSRemoting -Write-Host "[Build cluster - Step 2/10] Preparing Azure VM virtualization host..." -ForegroundColor Green +Write-Host "[Build cluster - Step 2/11] Preparing Azure VM virtualization host..." -ForegroundColor Green Write-Host "Enabling PS Remoting on client..." Enable-PSRemoting set-item WSMan:localhost\client\trustedhosts -value * -Force @@ -1578,22 +1676,22 @@ Copy-Item -Path $HCIBoxConfig.guiVHDXPath -Destination $guipath -Force | Out-Nul Copy-Item -Path $HCIBoxConfig.azSHCIVHDXPath -Destination $hcipath -Force | Out-Null ################################################################################ -# Create the three nested Virtual Machines +# Create the three nested Virtual Machines ################################################################################ # First create the Management VM (AzSMGMT) -Write-Host "[Build cluster - Step 3/10] Creating Management VM (AzSMGMT)..." -ForegroundColor Green +Write-Host "[Build cluster - Step 3/11] Creating Management VM (AzSMGMT)..." -ForegroundColor Green $mgmtMac = New-ManagementVM -Name $($HCIBoxConfig.MgmtHostConfig.Hostname) -VHDXPath "$HostVMPath\GUI.vhdx" -VMSwitch $InternalSwitch -HCIBoxConfig $HCIBoxConfig Set-MGMTVHDX -VMMac $mgmtMac -HCIBoxConfig $HCIBoxConfig # Create the HCI host node VMs -Write-Host "[Build cluster - Step 4/10] Creating HCI node VMs (AzSHOSTx)..." -ForegroundColor Green +Write-Host "[Build cluster - Step 4/11] Creating HCI node VMs (AzSHOSTx)..." -ForegroundColor Green foreach ($VM in $HCIBoxConfig.NodeHostConfig) { $mac = New-HCINodeVM -Name $VM.Hostname -VHDXPath $hcipath -VMSwitch $InternalSwitch -HCIBoxConfig $HCIBoxConfig Set-HCINodeVHDX -HostName $VM.Hostname -IPAddress $VM.IP -VMMac $mac -HCIBoxConfig $HCIBoxConfig } - + # Start Virtual Machines -Write-Host "[Build cluster - Step 5/10] Starting VMs..." -ForegroundColor Green +Write-Host "[Build cluster - Step 5/11] Starting VMs..." -ForegroundColor Green Write-Host "Starting VM: $($HCIBoxConfig.MgmtHostConfig.Hostname)" Start-VM -Name $HCIBoxConfig.MgmtHostConfig.Hostname foreach ($VM in $HCIBoxConfig.NodeHostConfig) { @@ -1604,20 +1702,20 @@ foreach ($VM in $HCIBoxConfig.NodeHostConfig) { ####################################################################################### # Prep the virtualization environment ####################################################################################### -Write-Host "[Build cluster - Step 6/10] Configuring host networking and storage..." -ForegroundColor Green +Write-Host "[Build cluster - Step 6/11] Configuring host networking and storage..." -ForegroundColor Green # Wait for AzSHOSTs to come online Test-AllVMsAvailable -HCIBoxConfig $HCIBoxConfig -Credential $localCred Start-Sleep -Seconds 60 # Format and partition data drives Set-DataDrives -HCIBoxConfig $HCIBoxConfig -Credential $localCred - + # Configure networking Set-NICs -HCIBoxConfig $HCIBoxConfig -Credential $localCred - + # Restart Machines Restart-VMs -HCIBoxConfig $HCIBoxConfig -Credential $localCred - + # Wait for AzSHOSTs to come online Test-AllVMsAvailable -HCIBoxConfig $HCIBoxConfig -Credential $localCred @@ -1631,11 +1729,11 @@ Set-FabricNetwork -HCIBoxConfig $HCIBoxConfig -localCred $localCred # Provision the router, domain controller, and WAC VMs and join the hosts to the domain ####################################################################################### # Provision Router VM on AzSMGMT -Write-Host "[Build cluster - Step 7/10] Build router VM..." -ForegroundColor Green +Write-Host "[Build cluster - Step 7/11] Build router VM..." -ForegroundColor Green New-RouterVM -HCIBoxConfig $HCIBoxConfig -localCred $localCred # Provision Domain controller VM on AzSMGMT -Write-Host "[Build cluster - Step 8/10] Building Domain Controller VM..." -ForegroundColor Green +Write-Host "[Build cluster - Step 8/11] Building Domain Controller VM..." -ForegroundColor Green New-DCVM -HCIBoxConfig $HCIBoxConfig -localCred $localCred -domainCred $domainCred # Provision Admincenter VM @@ -1646,11 +1744,58 @@ New-DCVM -HCIBoxConfig $HCIBoxConfig -localCred $localCred -domainCred $domainCr # Prepare the cluster for deployment ####################################################################################### # New-S2DCluster -HCIBoxConfig $HCIBoxConfig -domainCred $domainCred -Write-Host "[Build cluster - Step 9/10] Preparing HCI cluster Azure deployment..." -ForegroundColor Green +Write-Host "[Build cluster - Step 9/11] Preparing HCI cluster Azure deployment..." -ForegroundColor Green Set-HCIDeployPrereqs -HCIBoxConfig $HCIBoxConfig -localCred $localCred -domainCred $domainCred -# Cluster complete. Finish up and add RDP Link to Desktop to WAC machine. -Write-Host "[Build cluster - Step 10/10] Tidying up..." -ForegroundColor Green +& "$Env:HCIBoxDir\Generate-ARM-Template.ps1" + +####################################################################################### +# Validate and deploy the cluster +####################################################################################### + +Write-Host "[Build cluster - Step 10/11] Validate cluster deployment..." -ForegroundColor Green + +if ($env:autoDeployClusterResource) { + +$TemplateFile = Join-Path -Path $env:HCIBoxDir -ChildPath "hci.json" +$TemplateParameterFile = Join-Path -Path $env:HCIBoxDir -ChildPath "hci.parameters.json" + +New-AzResourceGroupDeployment -Name 'hcicluster-validate' -ResourceGroupName $env:resourceGroup -TemplateFile $TemplateFile -TemplateParameterFile $TemplateParameterFile -OutVariable ClusterValidationDeployment + + +Write-Host "[Build cluster - Step 11/11] Run cluster deployment..." -ForegroundColor Green + +if ($ClusterValidationDeployment.ProvisioningState -eq "Succeeded") { + + Write-Host "Validation succeeded. Deploying HCI cluster..." + New-AzResourceGroupDeployment -Name 'hcicluster-deploy' -ResourceGroupName $env:resourceGroup -TemplateFile $TemplateFile -deploymentMode "Deploy" -TemplateParameterFile $TemplateParameterFile -OutVariable ClusterDeployment + + if ($env:autoUpgradeClusterResource -and $ClusterDeployment.ProvisioningState -eq "Succeeded") { + + Write-Host "Deployment succeeded. Upgrading HCI cluster..." + + Update-HCICluster -HCIBoxConfig $HCIBoxConfig -domainCred $domainCred + + } + else { + + Write-Host '$autoUpgradeClusterResource is false, skipping HCI cluster upgrade...follow the documentation to upgrade the cluster manually' + + } + +} +else { + + Write-Error "Validation failed. Aborting deployment. Re-run New-AzResourceGroupDeployment to retry." + +} + +} +else { + Write-Host '$autoDeployClusterResource is false, skipping HCI cluster deployment...follow the documentation to deploy the cluster manually' +} + + $endtime = Get-Date $timeSpan = New-TimeSpan -Start $starttime -End $endtime @@ -1658,6 +1803,6 @@ Write-Host Write-Host "Successfully deployed HCIBox infrastructure." -ForegroundColor Green Write-Host "Infrastructure deployment time was $($timeSpan.Hours):$($timeSpan.Minutes) (hh:mm)." -ForegroundColor Green -Stop-Transcript +Stop-Transcript -#endregion \ No newline at end of file +#endregion \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/hci.parameters.json b/azure_jumpstart_hcibox/artifacts/hci.parameters.json index 49075bc318..0ee2983ffb 100644 --- a/azure_jumpstart_hcibox/artifacts/hci.parameters.json +++ b/azure_jumpstart_hcibox/artifacts/hci.parameters.json @@ -65,9 +65,6 @@ "storageConnectivitySwitchless": { "value": false }, - "networkingPattern": { - "value": "convergedManagementCompute" - }, "intentList": { "value": [ { @@ -131,7 +128,7 @@ { "name": "StorageNetwork1", "networkAdapterName": "StorageA", - "vlanId": "storageNicAVLAN-staging" + "vlanId": "storageNicAVLAN-staging" }, { "name": "StorageNetwork2", diff --git a/azure_jumpstart_hcibox/bicep/host/host.bicep b/azure_jumpstart_hcibox/bicep/host/host.bicep index afb315ba33..ebc6cbe2be 100644 --- a/azure_jumpstart_hcibox/bicep/host/host.bicep +++ b/azure_jumpstart_hcibox/bicep/host/host.bicep @@ -63,6 +63,12 @@ param natDNS string = '8.8.8.8' @description('Override default RDP port using this parameter. Default is 3389. No changes will be made to the client VM.') param rdpPort string = '3389' +@description('Choice to enable automatic deployment of Azure Arc enabled HCI cluster resource after the client VM deployment is complete. Default is false.') +param autoDeployClusterResource bool = false + +@description('Choice to enable automatic upgrade of Azure Arc enabled HCI cluster resource after the client VM deployment is complete. Only applicable when autoDeployClusterResource is true. Default is false.') +param autoUpgradeClusterResource bool = false + var encodedPassword = base64(windowsAdminPassword) var bastionName = 'HCIBox-Bastion' var publicIpAddressName = deployBastion == false ? '${vmName}-PIP' : '${bastionName}-PIP' @@ -251,7 +257,7 @@ resource vmBootstrap 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = fileUris: [ uri(templateBaseUrl, 'artifacts/PowerShell/Bootstrap.ps1') ] - commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -subscriptionId ${subscription().subscriptionId} -spnProviderId ${spnProviderId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -stagingStorageAccountName ${stagingStorageAccountName} -workspaceName ${workspaceName} -templateBaseUrl ${templateBaseUrl} -registerCluster ${registerCluster} -deployAKSHCI ${deployAKSHCI} -deployResourceBridge ${deployResourceBridge} -natDNS ${natDNS} -rdpPort ${rdpPort}' + commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -subscriptionId ${subscription().subscriptionId} -spnProviderId ${spnProviderId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -stagingStorageAccountName ${stagingStorageAccountName} -workspaceName ${workspaceName} -templateBaseUrl ${templateBaseUrl} -registerCluster ${registerCluster} -deployAKSHCI ${deployAKSHCI} -deployResourceBridge ${deployResourceBridge} -natDNS ${natDNS} -rdpPort ${rdpPort} -autoDeployClusterResource ${autoDeployClusterResource} -autoUpgradeClusterResource ${autoUpgradeClusterResource}' } } } diff --git a/azure_jumpstart_hcibox/bicep/main.azd.bicep b/azure_jumpstart_hcibox/bicep/main.azd.bicep index 0b658ee657..32f6287ecd 100644 --- a/azure_jumpstart_hcibox/bicep/main.azd.bicep +++ b/azure_jumpstart_hcibox/bicep/main.azd.bicep @@ -47,6 +47,12 @@ param location string @description('Override default RDP port using this parameter. Default is 3389.') param rdpPort string = '3389' +@description('Choice to enable automatic deployment of Azure Arc enabled HCI cluster resource after the client VM deployment is complete. Default is false.') +param autoDeployClusterResource bool = false + +@description('Choice to enable automatic upgrade of Azure Arc enabled HCI cluster resource after the client VM deployment is complete. Only applicable when autoDeployClusterResource is true. Default is false.') +param autoUpgradeClusterResource bool = false + var templateBaseUrl = 'https://raw.githubusercontent.com/${githubAccount}/azure_arc/${githubBranch}/azure_jumpstart_hcibox/' targetScope = 'subscription' @@ -100,6 +106,8 @@ module hostDeployment 'host/host.bicep' = { natDNS: natDNS location: location rdpPort: rdpPort + autoDeployClusterResource: autoDeployClusterResource + autoUpgradeClusterResource: autoUpgradeClusterResource } } diff --git a/azure_jumpstart_hcibox/bicep/main.azd.parameters.json b/azure_jumpstart_hcibox/bicep/main.azd.parameters.json index c5d4442177..0aefe617b5 100644 --- a/azure_jumpstart_hcibox/bicep/main.azd.parameters.json +++ b/azure_jumpstart_hcibox/bicep/main.azd.parameters.json @@ -28,6 +28,12 @@ }, "deployBastion": { "value": "${JS_DEPLOY_BASTION}" + }, + "autoDeployClusterResource": { + "value": "${JS_AUTO_DEPLOY_CLUSTER_RESOURCE}" + }, + "autoUpgradeClusterResource": { + "value": "${JS_AUTO_UPGRADE_CLUSTER_RESOURCE}" } } -} \ No newline at end of file +} diff --git a/azure_jumpstart_hcibox/bicep/main.bicep b/azure_jumpstart_hcibox/bicep/main.bicep index cd32561036..3c4c25e4c2 100644 --- a/azure_jumpstart_hcibox/bicep/main.bicep +++ b/azure_jumpstart_hcibox/bicep/main.bicep @@ -41,6 +41,12 @@ param location string = resourceGroup().location @description('Override default RDP port using this parameter. Default is 3389. No changes will be made to the client VM.') param rdpPort string = '3389' +@description('Choice to enable automatic deployment of Azure Arc enabled HCI cluster resource after the client VM deployment is complete. Default is false.') +param autoDeployClusterResource bool = false + +@description('Choice to enable automatic upgrade of Azure Arc enabled HCI cluster resource after the client VM deployment is complete. Only applicable when autoDeployClusterResource is true. Default is false.') +param autoUpgradeClusterResource bool = false + var templateBaseUrl = 'https://raw.githubusercontent.com/${githubAccount}/azure_arc/${githubBranch}/azure_jumpstart_hcibox/' module mgmtArtifactsAndPolicyDeployment 'mgmt/mgmtArtifacts.bicep' = { @@ -83,5 +89,7 @@ module hostDeployment 'host/host.bicep' = { natDNS: natDNS location: location rdpPort: rdpPort + autoDeployClusterResource: autoDeployClusterResource + autoUpgradeClusterResource: autoUpgradeClusterResource } } diff --git a/azure_jumpstart_hcibox/bicep/main.parameters.json b/azure_jumpstart_hcibox/bicep/main.parameters.json index ef61e3e5c2..25a48972db 100644 --- a/azure_jumpstart_hcibox/bicep/main.parameters.json +++ b/azure_jumpstart_hcibox/bicep/main.parameters.json @@ -25,6 +25,12 @@ }, "deployBastion": { "value": false + }, + "autoDeployClusterResource": { + "value": false + }, + "autoUpgradeClusterResource": { + "value": false } } -} \ No newline at end of file +} diff --git a/azure_jumpstart_hcibox/scripts/preprovision.ps1 b/azure_jumpstart_hcibox/scripts/preprovision.ps1 index de4b3aa92f..98f24d4096 100644 --- a/azure_jumpstart_hcibox/scripts/preprovision.ps1 +++ b/azure_jumpstart_hcibox/scripts/preprovision.ps1 @@ -166,8 +166,14 @@ azd env set JS_RDP_PORT $JS_RDP_PORT # Attempt to retrieve provider id for Microsoft.AzureStackHCI Write-Host "Attempting to retrieve Microsoft.AzureStackHCI provider id..." $spnProviderId=$(az ad sp list --display-name "Microsoft.AzureStackHCI" --output json) | ConvertFrom-Json -if ($null -ne $spnProviderId.id) { + if ($null -ne $spnProviderId.id) { azd env set SPN_PROVIDER_ID -- $($spnProviderId.id) + else { + Write-Warning "Microsoft.AzureStackHCI provider id not found, aborting..." + + Write-Host 'Consider the following options: 1) Request access from a tenant administrator to get read-permissions to service principals. + 2) Ask a tenant administrator to run the command $(az ad sp list --display-name "Microsoft.AzureStackHCI" --output json) | ConvertFrom-Json and send you the ID from the output. You can then manually add that value to the AZD .env file: SPN_PROVIDER_ID="xxx" or use the Bicep-based deployment specifying spnProviderId="xxx" in the deployment parameter-file.' -ForegroundColor Yellow + throw "Microsoft.AzureStackHCI provider id not found" } ########################################################################