diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6df1323bc9..2b29705892 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,7 +8,10 @@ assignees: '' --- - + + +> **Note:** For ease of issues and pull requests management and tracking, we kindly ask you to provide a meaningful and concise title to this issue and answer all questions to the best of your ability. + **Is your issue related to a Jumpstart scenario, ArcBox, HCIBox, or Agora?** @@ -37,6 +40,7 @@ assignees: '' - [Jumpstart ArcBox for DataOps troubleshooting](https://azurearcjumpstart.com/azure_jumpstart_arcbox/DataOps#basic-troubleshooting) - [HCIBox troubleshooting](https://azurearcjumpstart.com/azure_jumpstart_hcibox/getting_started#basic-troubleshooting) - [Agora - Contoso Supermarket troubleshooting](https://azurearcjumpstart.com/azure_jumpstart_ag/retail/contoso_supermarket/troubleshooting) +- [Agora - Contoso Motors troubleshooting](https://arcjumpstart.com/azure_jumpstart_ag/manufacturing/contoso_motors/troubleshooting) ---> **Screenshots** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index d4c6fce7ed..224f83547f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -6,10 +6,12 @@ labels: '' assignees: '' --- - + + +> **Note:** For ease of issues and pull requests management and tracking, we kindly ask you to provide a meaningful and concise title to this feature request and answer all questions to the best of your ability. **Is your feature request related to a new Jumpstart scenario you would like to contribute?** - + **Is your feature request related to a problem? Please describe.** diff --git a/.github/policies/issues.yml b/.github/policies/issues.yml new file mode 100644 index 0000000000..8abdf57aa5 --- /dev/null +++ b/.github/policies/issues.yml @@ -0,0 +1,116 @@ +name: Housekeeping - Issues management +description: Arc Jumpstart management of issues using GitHub Policies +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + scheduledSearches: + - description: + frequencies: + - hourly: + hour: 3 + filters: + - isIssue + - isOpen + - hasLabel: + label: needs author feedback + - noActivitySince: + days: 7 + - isNotLabeledWith: + label: no recent activity + actions: + - addLabel: + label: no recent activity + - addReply: + reply: This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **7 days**. It will be closed if no further activity occurs **within 7 days of this comment**. Thank you ${issueAuthor} for your contributions to the repository! + - description: + frequencies: + - hourly: + hour: 3 + filters: + - isIssue + - isOpen + - hasLabel: + label: no recent activity + - noActivitySince: + days: 14 + actions: + - addReply: + reply: Because of no activity for over **14 days**, we're closing this issue. Please open a new issue report if more support is required. Thank you for your support ${issueAuthor}! + - closeIssue + - description: + frequencies: + - hourly: + hour: 12 + filters: + - isOpen + - isIssue + - hasLabel: + label: investigate + - noActivitySince: + days: 3 + actions: + - addReply: + reply: "${assignees}\nGentle ping that this issue needs attention. " + eventResponderTasks: + - if: + - payloadType: Issues + - isAction: + action: Opened + then: + - addReply: + reply: 'Hi ${issueAuthor}! Thank you for opening this issue. We appreciate your contribution and welcome you to our community! We are glad to have you here and to have your input on the Arc Jumpstart.' + description: + - if: + - payloadType: Issue_Comment + - hasLabel: + label: no recent activity + - isAction: + action: Created + - isOpen + then: + - removeLabel: + label: no recent activity + - addLabel: + label: investigate + description: + - if: + - payloadType: Issues + - not: + isAction: + action: Closed + - hasLabel: + label: no recent activity + - not: + labelAdded: + label: no recent activity + then: + - removeLabel: + label: no recent activity + description: + - if: + - payloadType: Issue_Comment + then: + - cleanEmailReply + description: + - if: + - payloadType: Issue_Comment + - isActivitySender: + issueAuthor: True + - or: + - hasLabel: + label: needs author feedback + - hasLabel: + label: no recent activity + then: + - removeLabel: + label: needs author feedback + - removeLabel: + label: no recent activity + - addLabel: + label: review + description: +onFailure: +onSuccess: diff --git a/.github/policies/pullrequests.yml b/.github/policies/pullrequests.yml new file mode 100644 index 0000000000..45bac05086 --- /dev/null +++ b/.github/policies/pullrequests.yml @@ -0,0 +1,133 @@ +name: Housekeeping - PRs management +description: Arc Jumpstart management of PRs using GitHub Policies +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + scheduledSearches: + - description: + frequencies: + - hourly: + hour: 3 + filters: + - isPullRequest + - isOpen + - hasLabel: + label: needs author feedback + - noActivitySince: + days: 7 + - isNotLabeledWith: + label: no recent activity + actions: + - addLabel: + label: no recent activity + - addReply: + reply: This pull request has been automatically marked as stale because it was marked as requiring author feedback but has not had any activity for **7 days**. It will be closed if no further activity occurs **within 7 days of this comment**. Thank you ${issueAuthor} for your contributions to this project! + - description: + frequencies: + - hourly: + hour: 3 + filters: + - isOpen + - isPullRequest + - hasLabel: + label: no recent activity + - noActivitySince: + days: 14 + actions: + - addReply: + reply: Because of no activity for over **14 days**, we're closing this pull request. Please open a new issue report if more support is required. Thank you for your support ${issueAuthor}! + - description: + frequencies: + - hourly: + hour: 12 + filters: + - isOpen + - isPullRequest + - hasLabel: + label: investigate + - noActivitySince: + days: 3 + actions: + - addReply: + reply: "${assignees}\nGentle ping that this issue needs attention. " + eventResponderTasks: + - if: + - payloadType: Pull_Request + - isAction: + action: Opened + then: + - addReply: + reply: 'Hi ${issueAuthor}! Thank you for opening this Pull Request. Someone will review it soon. Thank you for committing to making the Arc Jumpstart better.' + description: + - if: + - payloadType: Pull_Request_Review + - isAction: + action: Submitted + - isReviewState: + reviewState: Changes_requested + then: + - addLabel: + label: needs author feedback + description: + - if: + - payloadType: Pull_Request + - isActivitySender: + issueAuthor: True + - not: + isAction: + action: Closed + - or: + - hasLabel: + label: needs author feedback + - hasLabel: + label: no recent activity + then: + - removeLabel: + label: needs author feedback + - removeLabel: + label: no recent activity + - addLabel: + label: review + description: + - if: + - payloadType: Pull_Request + - isAction: + action: Null + - isOpen + then: + - addLabel: + label: review + description: + - if: + - payloadType: Pull_Request_Review + - isAction: + action: Submitted + - isOpen + then: + - removeLabel: + label: review + - removeLabel: + label: no recent activity + description: + - if: + - payloadType: Pull_Request_Review + - isActivitySender: + issueAuthor: True + - or: + - hasLabel: + label: needs author feedback + - hasLabel: + label: no recent activity + then: + - removeLabel: + label: needs author feedback + - removeLabel: + label: no recent activity + - addLabel: + label: review + description: +onFailure: +onSuccess: diff --git a/.github/workflows/housekeeping-stale.yml b/.github/workflows/housekeeping-stale.yml deleted file mode 100644 index 44e80c6109..0000000000 --- a/.github/workflows/housekeeping-stale.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Housekeeping - Close stale issues and PRs -on: - schedule: - - cron: '0 0 * * *' - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v4 - with: - stale-issue-message: 'This issue is being marked stale due to a period of inactivity. If this issue is still relevant, please comment or remove the stale label. Otherwise, this issue will close in 5 days.' - #stale-pr-message: 'This PR is being marked stale due to a period of inactivity. If this PR is still relevant, please comment or remove the stale label. Otherwise, this PR will close in 30 days.' - close-issue-message: 'This issue was closed because it has been stalled for 30 days with no activity. If this issue is still relevant, please re-open a new issue.' - #close-pr-message: 'This PR was closed because it has been stalled for 30 days with no activity. If this PR is still relevant, please re-open a new PR against main.' - days-before-issue-stale: 30 - #days-before-pr-stale: 30 - days-before-issue-close: 5 - #days-before-pr-close: 30 - # Don't add stale label to PRs / issues with milestones "upcoming" attached. - #exempt-milestones: "upcoming" - # Don't add stale label to PRs / issues with this label - exempt-issue-labels: 'blocked,must,should,keep,pinned' - diff --git a/.github/workflows/housekeeping-welcome.yaml b/.github/workflows/housekeeping-welcome.yaml deleted file mode 100644 index c933ea6404..0000000000 --- a/.github/workflows/housekeeping-welcome.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: Housekeeping - Issues and PRs auto message - -on: - fork: - push: - branches: [main] - issues: - types: [opened] - issue_comment: - types: [created] - pull_request_target: - types: [opened] - pull_request_review_comment: - types: [created] - -jobs: - welcome: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: EddieHubCommunity/gh-action-community/src/welcome@main - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - issue-message: 'Hey friend! Thanks for opening this issue. We appreciate your contribution and welcome you to our community! We are glad to have you here and to have your input on the Azure Arc Jumpstart.' - pr-message: 'Hi friend! Thanks you for opening this Pull Request. Someone will review it soon. Thank you for committing to making the Azure Arc Jumpstart better.' diff --git a/.gitignore b/.gitignore index 1c59886ca0..e2d4b7e2cd 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,4 @@ terraform.rc *.out .VSCodeCounter/* +azure_jumpstart_ag/manufacturing/bicep/main.*.parameters.json diff --git a/README.md b/README.md index bb0079b385..316dba9c9d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Azure Arc Jumpstart source code +# Arc Jumpstart source code Welcome to the Arc Jumpstart source code repository! This repository is your go-to resource for working with and contributing to the Arc Jumpstart automation scripts and tools and acts as the backend source code repository which complements our [documentation repository](https://github.com/Azure/arc_jumpstart_docs) that eventually populates the [Arc Jumpstart](https://aka.ms/arcjumpstart) website. @@ -38,6 +38,12 @@ As we continuously improve and expand Arc Jumpstart, we recommend keeping your l git pull origin main ``` +You can use partial clones if you want to reduce the time and size it takes to clone this repository. By default, when you clone this repository, you get all the files and their associated metadata, including blobs and diff history. However, if you don't need all of this information, you can use the following command to clone the repository without the blobs: + +```bash +git clone --filter=blob:none https://github.com/microsoft/azure_arc +``` + ## Contribution and feedback We value your input! If you have suggestions, feedback, or valuable insights to share, feel free to open an issue. Your contributions help us improve the documentation for the entire community. diff --git a/arc_data_services/arcdata-installer.yaml b/arc_data_services/arcdata-installer.yaml index edc1617e35..d1f6a32e0d 100644 --- a/arc_data_services/arcdata-installer.yaml +++ b/arc_data_services/arcdata-installer.yaml @@ -262,4 +262,4 @@ subjects: roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: arcdata-installer-role \ No newline at end of file + name: arcdata-installer-role diff --git a/arc_data_services/charts/arcdataservices/values.yaml b/arc_data_services/charts/arcdataservices/values.yaml index 6de48401b8..3e4b78ac55 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.26.0_2023-12-12 + image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.31.0_2024-07-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 ad9b4fb64a..1c07983083 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.26.0_2023-12-12': " + "Provide container image tag for the images at the source - press ENTER for using 'v1.31.0_2024-07-09': " ) - or "v1.26.0_2023-12-12" + or "v1.31.0_2024-07-09" ) else: SOURCE_DOCKER_TAG = os.environ["SOURCE_DOCKER_TAG"] @@ -102,14 +102,12 @@ def execute_cmd(cmd): "arc-dns", "arc-ha-orchestrator", "arc-ha-supervisor", - "arc-kafka", "arc-monitor-collectd", "arc-monitor-opensearch", "arc-monitor-opensearch-dashboards", "arc-monitor-fluentbit", "arc-monitor-grafana", "arc-monitor-influxdb", - "arc-monitor-opentelemetry-collector", "arc-monitor-telegraf", "arc-postgres-14", "arc-postgresql-agent", diff --git a/arc_data_services/deploy/yaml/bootstrapper-unified.yaml b/arc_data_services/deploy/yaml/bootstrapper-unified.yaml index f659a5427d..e77f5d2bd3 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.26.0_2023-12-12 + image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.31.0_2024-07-09 imagePullPolicy: Always args: - -image - - mcr.microsoft.com/arcdata/arc-bootstrapper:v1.26.0_2023-12-12 + - mcr.microsoft.com/arcdata/arc-bootstrapper:v1.31.0_2024-07-09 - -policy - Always - -chart diff --git a/arc_data_services/deploy/yaml/bootstrapper.yaml b/arc_data_services/deploy/yaml/bootstrapper.yaml index fde96af450..f27c02dfc3 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.26.0_2023-12-12 + image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.31.0_2024-07-09 imagePullPolicy: Always args: - -image - - mcr.microsoft.com/arcdata/arc-bootstrapper:v1.26.0_2023-12-12 + - mcr.microsoft.com/arcdata/arc-bootstrapper:v1.31.0_2024-07-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 58887a4f13..0b98bac09b 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.26.0_2023-12-12 + imageTag: v1.31.0_2024-07-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 3fb6926568..0b2bdf1d72 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.26.0_2023-12-12 + image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.31.0_2024-07-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 7f4b6b5d6e..96fc91d628 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.26.0_2023-12-12 + newTag: v1.31.0_2024-07-09 diff --git a/arc_data_services/upgrade/yaml/bootstrapper-upgrade-job.yaml b/arc_data_services/upgrade/yaml/bootstrapper-upgrade-job.yaml index a7ca228e81..eec1d41000 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.26.0_2023-12-12 + image: mcr.microsoft.com/arcdata/arc-bootstrapper:v1.31.0_2024-07-09 imagePullPolicy: Always command: ["/opt/bootstrapper/bin/bootstrapper"] - args: ["-image", "mcr.microsoft.com/arcdata/arc-bootstrapper:v1.26.0_2023-12-12", "-policy", "Always", "-chart", "/opt/helm/arcdataservices", "-bootstrap"] + args: ["-image", "mcr.microsoft.com/arcdata/arc-bootstrapper:v1.31.0_2024-07-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 05abff9b7a..db0baf99a9 100644 --- a/arc_data_services/upgrade/yaml/data-controller-upgrade.yaml +++ b/arc_data_services/upgrade/yaml/data-controller-upgrade.yaml @@ -4,4 +4,5 @@ metadata: name: arc spec: docker: - imageTag: v1.26.0_2023-12-12 + imageTag: v1.31.0_2024-07-09 + \ No newline at end of file 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 56d9e012ec..679dbfad32 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.26.0_2023-12-12", + "imageTag": "v1.31.0_2024-07-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 a068746922..05af7eaa05 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.26.0_2023-12-12", + "imageTag": "v1.31.0_2024-07-09", "registry": "", "repository": "" }, diff --git a/azure_arc_app_services_jumpstart/aks/ARM/artifacts/Bootstrap.ps1 b/azure_arc_app_services_jumpstart/aks/ARM/artifacts/Bootstrap.ps1 index fd3231b007..57a0252763 100644 --- a/azure_arc_app_services_jumpstart/aks/ARM/artifacts/Bootstrap.ps1 +++ b/azure_arc_app_services_jumpstart/aks/ARM/artifacts/Bootstrap.ps1 @@ -76,10 +76,10 @@ Invoke-WebRequest "https://raw.githubusercontent.com/Azure/arc_jumpstart_docs/ma # Downloading GitHub artifacts for AppServicesLogonScript.ps1 if ($deployAppService -eq $true -Or $deployFunction -eq $true -Or $deployApiMgmt -eq $true -Or $deployLogicApp -eq $true) { Invoke-WebRequest ($templateBaseUrl + "artifacts/AppServicesLogonScript.ps1") -OutFile "C:\Temp\AppServicesLogonScript.ps1" -Invoke-WebRequest ($templateBaseUrl + "artifacts/deployAppService.ps1") -OutFile "C:\Temp\deployAppService.ps1" -Invoke-WebRequest ($templateBaseUrl + "artifacts/deployFunction.ps1") -OutFile "C:\Temp\deployFunction.ps1" -Invoke-WebRequest ($templateBaseUrl + "artifacts/deployApiMgmt.ps1") -OutFile "C:\Temp\deployApiMgmt.ps1" -Invoke-WebRequest ($templateBaseUrl + "artifacts/deployLogicApp.ps1") -OutFile "C:\Temp\deployLogicApp.ps1" +Invoke-WebRequest ($templateBaseUrl + "artifacts/deployAppService.ps1") -OutFile "C:\Temp\deployAppService.ps1" +Invoke-WebRequest ($templateBaseUrl + "artifacts/deployFunction.ps1") -OutFile "C:\Temp\deployFunction.ps1" +Invoke-WebRequest ($templateBaseUrl + "artifacts/deployApiMgmt.ps1") -OutFile "C:\Temp\deployApiMgmt.ps1" +Invoke-WebRequest ($templateBaseUrl + "artifacts/deployLogicApp.ps1") -OutFile "C:\Temp\deployLogicApp.ps1" } # Downloading GitHub artifacts for ContainerAppsLogonScript.ps1 @@ -91,9 +91,9 @@ Invoke-WebRequest ($templateBaseUrl + "artifacts/ContainerAppsLogonScript.ps1") workflow ClientTools_01 { $chocolateyAppList = 'azure-cli,az.powershell,kubernetes-cli,vcredist140,microsoft-edge,azcopy10,vscode,putty.install,kubernetes-helm,azurefunctions-vscode,dotnetcore-sdk,dotnet-sdk,dotnet-runtime,vscode-csharp,microsoftazurestorageexplorer,7zip' - $kubectlVersion = '1.26.6' + $kubectlVersion = '1.28.5' #Run commands in parallel. - Parallel + Parallel { InlineScript { param ( @@ -108,11 +108,11 @@ workflow ClientTools_01 iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) } } - if ([string]::IsNullOrWhiteSpace($using:chocolateyAppList) -eq $false){ - Write-Host "Chocolatey Apps Specified" - + if ([string]::IsNullOrWhiteSpace($using:chocolateyAppList) -eq $false){ + Write-Host "Chocolatey Apps Specified" + $appsToInstall = $using:chocolateyAppList -split "," | foreach { "$($_.Trim())" } - + foreach ($app in $appsToInstall) { if ($app -eq "kubernetes-cli"){ @@ -173,5 +173,5 @@ Get-ScheduledTask -TaskName ServerManager | Disable-ScheduledTask # Clean up Bootstrap.log Stop-Transcript -$logSuppress = Get-Content C:\Temp\Bootstrap.log | Where-Object { $_ -notmatch "Host Application: powershell.exe" } +$logSuppress = Get-Content C:\Temp\Bootstrap.log | Where-Object { $_ -notmatch "Host Application: powershell.exe" } $logSuppress | Set-Content C:\Temp\Bootstrap.log -Force diff --git a/azure_arc_app_services_jumpstart/aks/ARM/artifacts/deployAppService.ps1 b/azure_arc_app_services_jumpstart/aks/ARM/artifacts/deployAppService.ps1 index a28047d478..fcf497b3e4 100644 --- a/azure_arc_app_services_jumpstart/aks/ARM/artifacts/deployAppService.ps1 +++ b/azure_arc_app_services_jumpstart/aks/ARM/artifacts/deployAppService.ps1 @@ -7,7 +7,7 @@ $customLocationId = $(az customlocation show --name "jumpstart-cl" --resource-gr az appservice plan create --resource-group $Env:resourceGroup --name Jumpstart --custom-location $customLocationId --per-site-scaling --is-linux --sku K1 Write-Host "`n" -Write-Host "Deploy a sample Azure Arc Jumpstart web application" +Write-Host "Deploy a sample Arc Jumpstart web application" Write-Host "`n" az webapp create --plan Jumpstart --resource-group $Env:resourceGroup --name jumpstart-app --custom-location $customLocationId --deployment-container-image-name azurearcjumpstart.azurecr.io/hello-arc:latest az webapp config appsettings set --resource-group $Env:resourceGroup --name jumpstart-app --settings WEBSITES_PORT=8080 \ No newline at end of file diff --git a/azure_arc_app_services_jumpstart/aks/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json b/azure_arc_app_services_jumpstart/aks/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json index dbecdd9340..fb94944133 100644 --- a/azure_arc_app_services_jumpstart/aks/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json +++ b/azure_arc_app_services_jumpstart/aks/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json @@ -11,7 +11,7 @@ } }, "method": "post", - "body": "Azure Arc Jumpstart is amazing!", + "body": "Arc Jumpstart is amazing!", "headers": { "ReadFileMetadataFromServer": true }, diff --git a/azure_arc_app_services_jumpstart/aks/ARM/azuredeploy.json b/azure_arc_app_services_jumpstart/aks/ARM/azuredeploy.json index ee1096cec4..7d08bdfc89 100644 --- a/azure_arc_app_services_jumpstart/aks/ARM/azuredeploy.json +++ b/azure_arc_app_services_jumpstart/aks/ARM/azuredeploy.json @@ -47,7 +47,7 @@ } }, "kubernetesVersion": { - "defaultValue": "1.26.6", + "defaultValue": "1.28.5", "type": "string", "metadata": { "description": "The version of Kubernetes" diff --git a/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/deployAppService.ps1 b/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/deployAppService.ps1 index 1e87418140..7e9f6f09f9 100644 --- a/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/deployAppService.ps1 +++ b/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/deployAppService.ps1 @@ -7,7 +7,7 @@ $customLocationId = $(az customlocation show --name "$Env:capiArcAppClusterName- az appservice plan create --resource-group $Env:resourceGroup --name Jumpstart --custom-location $customLocationId --per-site-scaling --is-linux --sku K1 Write-Host "`n" -Write-Host "Deploy a sample Azure Arc Jumpstart web application" +Write-Host "Deploy a sample Arc Jumpstart web application" Write-Host "`n" az webapp create --plan Jumpstart --resource-group $Env:resourceGroup --name jumpstart-app --custom-location $customLocationId --deployment-container-image-name azurearcjumpstart.azurecr.io/hello-arc:latest az webapp config appsettings set --resource-group $Env:resourceGroup --name jumpstart-app --settings WEBSITES_PORT=8080 \ No newline at end of file diff --git a/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/installCAPI.sh b/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/installCAPI.sh index 21fcc1d827..bd6d5d5c58 100644 --- a/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/installCAPI.sh +++ b/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/installCAPI.sh @@ -222,7 +222,7 @@ while true; do # Iterate over each node and check its status for node in $nodes; do ready=$(kubectl get nodes $node --kubeconfig=./$CLUSTER_NAME.kubeconfig -o json | jq -r '.status.conditions[] | select(.type=="Ready") | .status') - + if [[ $ready != "True" ]]; then echo "Node $node is not ready." all_ready=false @@ -255,7 +255,7 @@ sudo service sshd restart echo "" sudo -u $adminUsername kubectl apply -f ${templateBaseUrl}artifacts/capiStorageClass.yaml -# Renaming CAPI cluster context name +# Renaming CAPI cluster context name echo "" sudo -u $adminUsername kubectl config rename-context "$CLUSTER_NAME-admin@$CLUSTER_NAME" "arcapp-capi" @@ -272,7 +272,7 @@ sudo -u $adminUsername az k8s-extension create --name "azuremonitor-containers" # Enabling Azure Policy for Kubernetes on the cluster echo "" -sudo -u $adminUsername az k8s-extension create --name "arc-azurepolicy" --cluster-name $capiArcAppClusterName --resource-group $AZURE_RESOURCE_GROUP --cluster-type connectedClusters --extension-type Microsoft.PolicyInsights +sudo -u $adminUsername az k8s-extension create --name "arc-azurepolicy" --cluster-name $capiArcAppClusterName --resource-group $AZURE_RESOURCE_GROUP --cluster-type connectedClusters --extension-type Microsoft.PolicyInsights # Deploying The Azure disk Container Storage Interface (CSI) Kubernetes driver echo "" diff --git a/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json b/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json index dbecdd9340..fb94944133 100644 --- a/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json +++ b/azure_arc_app_services_jumpstart/cluster_api/capi_azure/ARM/artifacts/logicAppCode/CreateBlobFromQueueMessage/workflow.json @@ -11,7 +11,7 @@ } }, "method": "post", - "body": "Azure Arc Jumpstart is amazing!", + "body": "Arc Jumpstart is amazing!", "headers": { "ReadFileMetadataFromServer": true }, diff --git a/azure_arc_data_jumpstart/aks/ARM/artifacts/DataServicesLogonScript.ps1 b/azure_arc_data_jumpstart/aks/ARM/artifacts/DataServicesLogonScript.ps1 index aa3590bb40..c9df85e806 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.26.0 ` + --version 1.31.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper diff --git a/azure_arc_data_jumpstart/aks/ARM/artifacts/DeployPostgreSQL.ps1 b/azure_arc_data_jumpstart/aks/ARM/artifacts/DeployPostgreSQL.ps1 index 1f6cb363f4..73d0e32414 100644 --- a/azure_arc_data_jumpstart/aks/ARM/artifacts/DeployPostgreSQL.ps1 +++ b/azure_arc_data_jumpstart/aks/ARM/artifacts/DeployPostgreSQL.ps1 @@ -74,7 +74,7 @@ Start-Sleep -Seconds 60 # Downloading demo database and restoring onto Postgres Write-Host "`n" Write-Host "Downloading AdventureWorks.sql template for PostgreSQL (1/3)" -kubectl exec $pgWorkerPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jsvhds.blob.core.windows.net/sampledb/AdventureWorks2019.sql?sp=r&st=2023-08-10T18:30:33Z&se=2033-08-11T02:30:33Z&spr=https&sv=2022-11-02&sr=b&sig=5kD1PR4gwaCWlefSknggq%2BYsx1FgBrp5Kv1pH42d1nE%3D'" 2>&1 | Out-Null +kubectl exec $pgWorkerPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jumpstartprodsg.blob.core.windows.net/sampledb/AdventureWorks2019.sql'" 2>&1 | Out-Null Write-Host "Creating AdventureWorks database on PostgreSQL (2/3)" kubectl exec $pgWorkerPodName -n arc -c postgres -- psql -U postgres -c 'CREATE DATABASE "adventureworks2019";' postgres 2>&1 | Out-Null Write-Host "Restoring AdventureWorks database on PostgreSQL (3/3)" diff --git a/azure_arc_data_jumpstart/aks/ARM/azuredeploy.json b/azure_arc_data_jumpstart/aks/ARM/azuredeploy.json index fa96fa4084..675439eefe 100644 --- a/azure_arc_data_jumpstart/aks/ARM/azuredeploy.json +++ b/azure_arc_data_jumpstart/aks/ARM/azuredeploy.json @@ -47,7 +47,7 @@ } }, "kubernetesVersion": { - "defaultValue": "1.26.6", + "defaultValue": "1.28.5", "type": "string", "metadata": { "description": "The version of Kubernetes" @@ -87,7 +87,7 @@ "metadata": { "description": "SQL Managed Instance high-availability deployment" } - }, + }, "deployPostgreSQL": { "type": "bool", "defaultValue": false, 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 1a46c4e9d7..ce0c466af2 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.26.0 ` + --version 1.31.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.26.0 ` + --version 1.31.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper ` diff --git a/azure_arc_data_jumpstart/aks/DR/ARM/azuredeploy.json b/azure_arc_data_jumpstart/aks/DR/ARM/azuredeploy.json index 77af8a31b8..7c26bac7dc 100644 --- a/azure_arc_data_jumpstart/aks/DR/ARM/azuredeploy.json +++ b/azure_arc_data_jumpstart/aks/DR/ARM/azuredeploy.json @@ -47,7 +47,7 @@ } }, "kubernetesVersion": { - "defaultValue": "1.26.6", + "defaultValue": "1.28.5", "type": "string", "metadata": { "description": "The version of Kubernetes" 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 a8c8a0ed8e..728989d9dd 100644 --- a/azure_arc_data_jumpstart/aks/Migration/ARM/artifacts/DataServicesLogonScript.ps1 +++ b/azure_arc_data_jumpstart/aks/Migration/ARM/artifacts/DataServicesLogonScript.ps1 @@ -123,12 +123,11 @@ 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" +$sourceFolder = 'https://jumpstartprodsg.blob.core.windows.net/arcbox' $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.26.0 ` + --version 1.31.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper ` diff --git a/azure_arc_data_jumpstart/aks/Migration/ARM/azuredeploy.json b/azure_arc_data_jumpstart/aks/Migration/ARM/azuredeploy.json index e325dc1db5..bb4cbb1829 100644 --- a/azure_arc_data_jumpstart/aks/Migration/ARM/azuredeploy.json +++ b/azure_arc_data_jumpstart/aks/Migration/ARM/azuredeploy.json @@ -47,7 +47,7 @@ } }, "kubernetesVersion": { - "defaultValue": "1.26.6", + "defaultValue": "1.28.5", "type": "string", "metadata": { "description": "The version of Kubernetes" @@ -80,7 +80,7 @@ "metadata": { "description": "SQL Managed Instance high-availability deployment" } - }, + }, "githubAccount": { "type": "string", "metadata": { diff --git a/azure_arc_data_jumpstart/aro/ARM/artifacts/DeployPostgreSQL.ps1 b/azure_arc_data_jumpstart/aro/ARM/artifacts/DeployPostgreSQL.ps1 index 443ddb2045..af5c839cf1 100644 --- a/azure_arc_data_jumpstart/aro/ARM/artifacts/DeployPostgreSQL.ps1 +++ b/azure_arc_data_jumpstart/aro/ARM/artifacts/DeployPostgreSQL.ps1 @@ -74,7 +74,7 @@ Start-Sleep -Seconds 60 # Downloading demo database and restoring onto Postgres Write-Host "`n" Write-Host "Downloading AdventureWorks.sql template for PostgreSQL (1/3)" -kubectl exec $pgWorkerPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jsvhds.blob.core.windows.net/sampledb/AdventureWorks2019.sql?sp=r&st=2023-08-10T18:30:33Z&se=2033-08-11T02:30:33Z&spr=https&sv=2022-11-02&sr=b&sig=5kD1PR4gwaCWlefSknggq%2BYsx1FgBrp5Kv1pH42d1nE%3D'" 2>&1 | Out-Null +kubectl exec $pgWorkerPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jumpstartprodsg.blob.core.windows.net/sampledb/AdventureWorks2019.sql'" 2>&1 | Out-Null Write-Host "Creating AdventureWorks database on PostgreSQL (2/3)" kubectl exec $pgWorkerPodName -n arc -c postgres -- psql -U postgres -c 'CREATE DATABASE "adventureworks2019";' postgres 2>&1 | Out-Null Write-Host "Restoring AdventureWorks database on PostgreSQL (3/3)" 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 3a584c1626..96620040eb 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.26.0 ` + --version 1.31.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper ` diff --git a/azure_arc_data_jumpstart/cluster_api/capi_azure/ARM/artifacts/DeployPostgreSQL.ps1 b/azure_arc_data_jumpstart/cluster_api/capi_azure/ARM/artifacts/DeployPostgreSQL.ps1 index bde051b517..767ee14084 100644 --- a/azure_arc_data_jumpstart/cluster_api/capi_azure/ARM/artifacts/DeployPostgreSQL.ps1 +++ b/azure_arc_data_jumpstart/cluster_api/capi_azure/ARM/artifacts/DeployPostgreSQL.ps1 @@ -74,7 +74,7 @@ Start-Sleep -Seconds 60 # Downloading demo database and restoring onto Postgres Write-Host "`n" Write-Host "Downloading AdventureWorks.sql template for PostgreSQL (1/3)" -kubectl exec $pgWorkerPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jsvhds.blob.core.windows.net/sampledb/AdventureWorks2019.sql?sp=r&st=2023-08-10T18:30:33Z&se=2033-08-11T02:30:33Z&spr=https&sv=2022-11-02&sr=b&sig=5kD1PR4gwaCWlefSknggq%2BYsx1FgBrp5Kv1pH42d1nE%3D'" 2>&1 | Out-Null +kubectl exec $pgWorkerPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jumpstartprodsg.blob.core.windows.net/sampledb/AdventureWorks2019.sql'" 2>&1 | Out-Null Write-Host "Creating AdventureWorks database on PostgreSQL (2/3)" kubectl exec $pgWorkerPodName -n arc -c postgres -- psql -U postgres -c 'CREATE DATABASE "adventureworks2019";' postgres 2>&1 | Out-Null Write-Host "Restoring AdventureWorks database on PostgreSQL (3/3)" diff --git a/azure_arc_data_jumpstart/cluster_api/capi_azure/ARM/artifacts/installCAPI.sh b/azure_arc_data_jumpstart/cluster_api/capi_azure/ARM/artifacts/installCAPI.sh index 39b807f5f2..310f2d9c0a 100644 --- a/azure_arc_data_jumpstart/cluster_api/capi_azure/ARM/artifacts/installCAPI.sh +++ b/azure_arc_data_jumpstart/cluster_api/capi_azure/ARM/artifacts/installCAPI.sh @@ -204,7 +204,7 @@ while true; do # Iterate over each node and check its status for node in $nodes; do ready=$(kubectl get nodes $node --kubeconfig=./$CLUSTER_NAME.kubeconfig -o json | jq -r '.status.conditions[] | select(.type=="Ready") | .status') - + if [[ $ready != "True" ]]; then echo "Node $node is not ready." all_ready=false @@ -237,7 +237,7 @@ sudo service sshd restart echo "" sudo -u $adminUsername kubectl apply -f ${templateBaseUrl}artifacts/capiStorageClass.yaml -# Renaming CAPI cluster context name +# Renaming CAPI cluster context name echo "" sudo -u $adminUsername kubectl config rename-context "$CLUSTER_NAME-admin@$CLUSTER_NAME" "arcdata-capi" diff --git a/azure_arc_data_jumpstart/eks/terraform/artifacts/DataServicesLogonScript.ps1 b/azure_arc_data_jumpstart/eks/terraform/artifacts/DataServicesLogonScript.ps1 index bdf0160787..46cbd22566 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.26.0 ` + --version 1.31.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper diff --git a/azure_arc_data_jumpstart/eks/terraform/artifacts/DeployPostgreSQL.ps1 b/azure_arc_data_jumpstart/eks/terraform/artifacts/DeployPostgreSQL.ps1 index f1f91d6d63..b359387bf8 100644 --- a/azure_arc_data_jumpstart/eks/terraform/artifacts/DeployPostgreSQL.ps1 +++ b/azure_arc_data_jumpstart/eks/terraform/artifacts/DeployPostgreSQL.ps1 @@ -74,7 +74,7 @@ Start-Sleep -Seconds 60 # Downloading demo database and restoring onto Postgres Write-Host "`n" Write-Host "Downloading AdventureWorks.sql template for PostgreSQL (1/3)" -kubectl exec $pgWorkerPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jsvhds.blob.core.windows.net/sampledb/AdventureWorks2019.sql?sp=r&st=2023-08-10T18:30:33Z&se=2033-08-11T02:30:33Z&spr=https&sv=2022-11-02&sr=b&sig=5kD1PR4gwaCWlefSknggq%2BYsx1FgBrp5Kv1pH42d1nE%3D'" 2>&1 | Out-Null +kubectl exec $pgWorkerPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jumpstartprodsg.blob.core.windows.net/sampledb/AdventureWorks2019.sql'" 2>&1 | Out-Null Write-Host "Creating AdventureWorks database on PostgreSQL (2/3)" kubectl exec $pgWorkerPodName -n arc -c postgres -- psql -U postgres -c 'CREATE DATABASE "adventureworks2019";' postgres 2>&1 | Out-Null Write-Host "Restoring AdventureWorks database on PostgreSQL (3/3)" diff --git a/azure_arc_data_jumpstart/gke/terraform/artifacts/DataServicesLogonScript.ps1 b/azure_arc_data_jumpstart/gke/terraform/artifacts/DataServicesLogonScript.ps1 index 5781f4e40c..aabcce1349 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.26.0 ` + --version 1.31.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper ` diff --git a/azure_arc_data_jumpstart/gke/terraform/artifacts/DeployPostgreSQL.ps1 b/azure_arc_data_jumpstart/gke/terraform/artifacts/DeployPostgreSQL.ps1 index e24004cf7e..6e992905a3 100644 --- a/azure_arc_data_jumpstart/gke/terraform/artifacts/DeployPostgreSQL.ps1 +++ b/azure_arc_data_jumpstart/gke/terraform/artifacts/DeployPostgreSQL.ps1 @@ -73,7 +73,7 @@ Start-Sleep -Seconds 60 # Downloading demo database and restoring onto Postgres Write-Host "Downloading AdventureWorks.sql template for PostgreSQL (1/3)" -kubectl exec $pgCoordinatorPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jsvhds.blob.core.windows.net/sampledb/AdventureWorks2019.sql?sp=r&st=2023-08-10T18:30:33Z&se=2033-08-11T02:30:33Z&spr=https&sv=2022-11-02&sr=b&sig=5kD1PR4gwaCWlefSknggq%2BYsx1FgBrp5Kv1pH42d1nE%3D'" 2>&1 | Out-Null +kubectl exec $pgCoordinatorPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jumpstartprodsg.blob.core.windows.net/sampledb/AdventureWorks2019.sql'" 2>&1 | Out-Null Write-Host "Creating AdventureWorks database on PostgreSQL (2/3)" kubectl exec $pgCoordinatorPodName -n arc -c postgres -- psql -U postgres -c 'CREATE DATABASE "adventureworks2019";' postgres 2>&1 | Out-Null Write-Host "Restoring AdventureWorks database on PostgreSQL (3/3)" 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 bc00407836..08a91d03bf 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.26.0 ` + --version 1.31.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper diff --git a/azure_arc_data_jumpstart/kubeadm/azure/ARM/artifacts/DeployPostgreSQL.ps1 b/azure_arc_data_jumpstart/kubeadm/azure/ARM/artifacts/DeployPostgreSQL.ps1 index bd12efd80b..d9a0302d38 100644 --- a/azure_arc_data_jumpstart/kubeadm/azure/ARM/artifacts/DeployPostgreSQL.ps1 +++ b/azure_arc_data_jumpstart/kubeadm/azure/ARM/artifacts/DeployPostgreSQL.ps1 @@ -75,7 +75,7 @@ Start-Sleep -Seconds 60 # Downloading demo database and restoring onto Postgres Write-Host "`n" Write-Host "Downloading AdventureWorks.sql template for PostgreSQL (1/3)" -kubectl exec $pgWorkerPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jsvhds.blob.core.windows.net/sampledb/AdventureWorks2019.sql?sp=r&st=2023-08-10T18:30:33Z&se=2033-08-11T02:30:33Z&spr=https&sv=2022-11-02&sr=b&sig=5kD1PR4gwaCWlefSknggq%2BYsx1FgBrp5Kv1pH42d1nE%3D'" 2>&1 | Out-Null +kubectl exec $pgWorkerPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jumpstartprodsg.blob.core.windows.net/sampledb/AdventureWorks2019.sql'" 2>&1 | Out-Null Write-Host "Creating AdventureWorks database on PostgreSQL (2/3)" kubectl exec $pgWorkerPodName -n arc -c postgres -- psql -U postgres -c 'CREATE DATABASE "adventureworks2019";' postgres 2>&1 | Out-Null Write-Host "Restoring AdventureWorks database on PostgreSQL (3/3)" 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 a4051145c3..6d766adf25 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.26.0 ` + --version 1.31.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-arc-bootstrapper ` diff --git a/azure_arc_data_jumpstart/microk8s/azure/arm_template/artifacts/DeployPostgreSQL.ps1 b/azure_arc_data_jumpstart/microk8s/azure/arm_template/artifacts/DeployPostgreSQL.ps1 index 98b584cfc5..a56ef880f9 100644 --- a/azure_arc_data_jumpstart/microk8s/azure/arm_template/artifacts/DeployPostgreSQL.ps1 +++ b/azure_arc_data_jumpstart/microk8s/azure/arm_template/artifacts/DeployPostgreSQL.ps1 @@ -71,7 +71,7 @@ Start-Sleep -Seconds 60 # Downloading demo database and restoring onto Postgres Write-Host "`n" Write-Host "Downloading AdventureWorks.sql template for PostgreSQL (1/3)" -kubectl exec $pgWorkerPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jsvhds.blob.core.windows.net/sampledb/AdventureWorks2019.sql?sp=r&st=2023-08-10T18:30:33Z&se=2033-08-11T02:30:33Z&spr=https&sv=2022-11-02&sr=b&sig=5kD1PR4gwaCWlefSknggq%2BYsx1FgBrp5Kv1pH42d1nE%3D'" 2>&1 | Out-Null +kubectl exec $pgWorkerPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jumpstartprodsg.blob.core.windows.net/sampledb/AdventureWorks2019.sql'" 2>&1 | Out-Null Write-Host "Creating AdventureWorks database on PostgreSQL (2/3)" kubectl exec $pgWorkerPodName -n arc -c postgres -- psql -U postgres -c 'CREATE DATABASE "adventureworks2019";' postgres 2>&1 | Out-Null Write-Host "Restoring AdventureWorks database on PostgreSQL (3/3)" diff --git a/azure_arc_k8s_jumpstart/aks/arm_template/azuredeploy.json b/azure_arc_k8s_jumpstart/aks/arm_template/azuredeploy.json index c31adf8607..92d94ca679 100644 --- a/azure_arc_k8s_jumpstart/aks/arm_template/azuredeploy.json +++ b/azure_arc_k8s_jumpstart/aks/arm_template/azuredeploy.json @@ -78,7 +78,7 @@ "metadata": { "description": "boolean flag to turn on and off of RBAC" } - }, + }, "osType": { "type": "string", "defaultValue": "Linux", @@ -90,7 +90,7 @@ } }, "kubernetesVersion": { - "defaultValue": "1.27.1", + "defaultValue": "1.28.5", "type": "string", "metadata": { "description": "The version of Kubernetes." @@ -112,7 +112,7 @@ "tags": "[parameters('resourceTags')]", "properties": { "kubernetesVersion": "[parameters('kubernetesVersion')]", - "enableRBAC": "[parameters('enableRBAC')]", + "enableRBAC": "[parameters('enableRBAC')]", "dnsPrefix": "[parameters('dnsPrefix')]", "agentPoolProfiles": [ { diff --git a/azure_arc_k8s_jumpstart/aks/arm_template/azuredeploy.parameters.json b/azure_arc_k8s_jumpstart/aks/arm_template/azuredeploy.parameters.json index b46ffd30a5..e39928723e 100644 --- a/azure_arc_k8s_jumpstart/aks/arm_template/azuredeploy.parameters.json +++ b/azure_arc_k8s_jumpstart/aks/arm_template/azuredeploy.parameters.json @@ -4,7 +4,7 @@ "parameters": { "clusterName": { "value": "" - }, + }, "linuxAdminUsername": { "value": "" }, @@ -18,7 +18,7 @@ "value": "" }, "kubernetesVersion": { - "value": "" + "value": "" } } } \ No newline at end of file diff --git a/azure_arc_k8s_jumpstart/aks/gitops/basic/az_k8sconfig_aks.sh b/azure_arc_k8s_jumpstart/aks/gitops/basic/az_k8sconfig_aks.sh index 5fc1016c33..1b7173841d 100755 --- a/azure_arc_k8s_jumpstart/aks/gitops/basic/az_k8sconfig_aks.sh +++ b/azure_arc_k8s_jumpstart/aks/gitops/basic/az_k8sconfig_aks.sh @@ -8,7 +8,7 @@ export password='' export tenantId='' export resourceGroup='' export arcClusterName='' -export appClonedRepo='' +export appClonedRepo='' export namespace='hello-arc' # Getting AKS credentials diff --git a/azure_arc_k8s_jumpstart/aks/gitops/helm/az_k8sconfig_helm_aks.sh b/azure_arc_k8s_jumpstart/aks/gitops/helm/az_k8sconfig_helm_aks.sh index 7adbebdd91..65f706d368 100755 --- a/azure_arc_k8s_jumpstart/aks/gitops/helm/az_k8sconfig_helm_aks.sh +++ b/azure_arc_k8s_jumpstart/aks/gitops/helm/az_k8sconfig_helm_aks.sh @@ -8,7 +8,7 @@ export password='' export tenantId='' export resourceGroup='' export arcClusterName='' -export appClonedRepo='' +export appClonedRepo='' export ingressNamespace='ingress-nginx' export namespace='hello-arc' diff --git a/azure_arc_k8s_jumpstart/aks/terraform/variables.tf b/azure_arc_k8s_jumpstart/aks/terraform/variables.tf index 9b7daf6c82..1cd989c407 100644 --- a/azure_arc_k8s_jumpstart/aks/terraform/variables.tf +++ b/azure_arc_k8s_jumpstart/aks/terraform/variables.tf @@ -20,7 +20,7 @@ variable "location" { variable "kubernetes_version" { description = "Kubernetes version deployed" - default = "1.27.7" + default = "1.28.5" } variable "node_count" { diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/Bootstrap.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/Bootstrap.ps1 index 5b03d7a538..4f772e4fb1 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/Bootstrap.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/Bootstrap.ps1 @@ -12,7 +12,8 @@ param ( [string]$rdpPort, [string]$githubAccount, [string]$githubBranch, - [string]$kubernetesDistribution + [string]$kubernetesDistribution, + [string]$AKSEEPinnedSchemaVersion ) # Inject ARM template parameters as environment variables @@ -30,6 +31,7 @@ param ( [System.Environment]::SetEnvironmentVariable('githubAccount', $githubAccount, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('githubBranch', $githubBranch, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('kubernetesDistribution', $kubernetesDistribution, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('AKSEEPinnedSchemaVersion', $AKSEEPinnedSchemaVersion, [System.EnvironmentVariableTarget]::Machine) # Create path Write-Output "Create deployment path" 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/bicep_template/artifacts/LogonScript.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/LogonScript.ps1 index 4ac2adbfae..287d8a8e9e 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/LogonScript.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/artifacts/LogonScript.ps1 @@ -14,11 +14,12 @@ $azureLocation = $env:azureLocation $spnClientId = $env:spnClientId $spnClientSecret = $env:spnClientSecret $spnTenantId = $env:spnTenantId -$vhdxUri = "https://jsvhds.blob.core.windows.net/scenarios/prod/JSW11IoTBase.vhdx?sp=r&st=2023-05-09T12:36:32Z&se=2033-05-09T20:36:32Z&spr=https&sv=2022-11-02&sr=b&sig=xFROrqGkKDIdrXqiAMLZGLEwTMToOWoNNDMVz1zvPMc%3D" +$vhdxUri = "https://jumpstartprodsg.blob.core.windows.net/scenarios/prod/JSW11IoTBase.vhdx" $hypervVMUser = "Administrator" $hypervVMPassword = "JS123!!" $kubernetesDistribution = $env:kubernetesDistribution $aksEEReleasesUrl = "https://api.github.com/repos/Azure/AKS-Edge/releases" +$AKSEEPinnedSchemaVersion = $Env:AKSEEPinnedSchemaVersion Write-Header "Executing LogonScript.ps1" @@ -248,6 +249,7 @@ Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { $logsFolder = "$deploymentFolder\Logs" Start-Transcript -Path $logsFolder\AKSEEBootstrap.log $SiteConfig = $using:SiteConfig + $AKSEEPinnedSchemaVersion = $using:AKSEEPinnedSchemaVersion ########################################## # Deploying AKS Edge Essentials clusters @@ -299,18 +301,21 @@ Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { } 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 -$schemaVersion = $jsonContent.SchemaVersion -# Clean up the downloaded release files -Remove-Item -Path $output -Force -Remove-Item -Path "C:\temp\AKS-Edge-$latestReleaseTag" -Force -Recurse +if ($AKSEEPinnedSchemaVersion -ne "useLatest") { + $SchemaVersion = $AKSEEPinnedSchemaVersion +}else{ + $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 + $schemaVersion = $jsonContent.SchemaVersion + # Clean up the downloaded release files + Remove-Item -Path $output -Force + Remove-Item -Path "C:\temp\AKS-Edge-$latestReleaseTag" -Force -Recurse +} ############################################################################### # Setting up replacment parameters for AKS Edge Essentials config json file diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/clientVm/clientVm.bicep b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/clientVm/clientVm.bicep index 9864ea8918..37d329c5d1 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/clientVm/clientVm.bicep +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/clientVm/clientVm.bicep @@ -50,6 +50,9 @@ param githubAccount string = 'microsoft' @description('Target GitHub branch') param githubBranch string = 'main' +@description('The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing.') +param AKSEEPinnedSchemaVersion string = 'useLatest' + var encodedPassword = base64(windowsAdminPassword) var bastionName = 'AKS-EE-Full-Bastion' var publicIpAddressName = deployBastion == false ? '${vmName}-PIP' : '${bastionName}-PIP' @@ -153,7 +156,7 @@ resource vmBootstrap 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = fileUris: [ uri(templateBaseUrl, 'artifacts/Bootstrap.ps1') ] - commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -templateBaseUrl ${templateBaseUrl} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -kubernetesDistribution ${kubernetesDistribution}' + commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -templateBaseUrl ${templateBaseUrl} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -kubernetesDistribution ${kubernetesDistribution} -AKSEEPinnedSchemaVersion ${AKSEEPinnedSchemaVersion}' } } } diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/main.bicep b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/main.bicep index c3bd7906b0..333f517c89 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/main.bicep +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/main.bicep @@ -42,6 +42,11 @@ param subnetNameCloud string = 'AKS-EE-Full-Subnet' @description('AKS Edge Essentials Kubernetes distribution') param kubernetesDistribution string +@description('''The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing. +To pin a specific version, use the format '1.13'. To use the latest schema version, use 'useLatest'. +''') +param AKSEEPinnedSchemaVersion string = '1.13' + var templateBaseUrl = 'https://raw.githubusercontent.com/${githubAccount}/azure_arc/${githubBranch}/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full/bicep_template/' module networkDeployment 'mgmt/network.bicep' = { @@ -69,5 +74,6 @@ module clientVmDeployment 'clientVm/clientVm.bicep' = { location: location subnetId: networkDeployment.outputs.subnetId kubernetesDistribution: kubernetesDistribution + AKSEEPinnedSchemaVersion: AKSEEPinnedSchemaVersion } } diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/Bootstrap.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/Bootstrap.ps1 index 28b1dff0da..4d2e55dc4a 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/Bootstrap.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/Bootstrap.ps1 @@ -12,7 +12,8 @@ param ( [string]$rdpPort, [string]$githubAccount, [string]$githubBranch, - [string]$kubernetesDistribution + [string]$kubernetesDistribution, + [string]$AKSEEPinnedSchemaVersion ) # Inject ARM template parameters as environment variables @@ -30,6 +31,7 @@ param ( [System.Environment]::SetEnvironmentVariable('githubAccount', $githubAccount, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('githubBranch', $githubBranch, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('kubernetesDistribution', $kubernetesDistribution, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('AKSEEPinnedSchemaVersion', $AKSEEPinnedSchemaVersion, [System.EnvironmentVariableTarget]::Machine) # Create path Write-Output "Create deployment path" 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_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/LogonScript.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/LogonScript.ps1 index b2715aaf5d..8c299965a9 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/LogonScript.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/artifacts/LogonScript.ps1 @@ -14,12 +14,13 @@ $azureLocation = $env:azureLocation $spnClientId = $env:spnClientId $spnClientSecret = $env:spnClientSecret $spnTenantId = $env:spnTenantId -$vhdxUri = "https://jsvhds.blob.core.windows.net/scenarios/prod/JSW11IoTBase.vhdx?sp=r&st=2023-05-09T12:36:32Z&se=2033-05-09T20:36:32Z&spr=https&sv=2022-11-02&sr=b&sig=xFROrqGkKDIdrXqiAMLZGLEwTMToOWoNNDMVz1zvPMc%3D" +$vhdxUri = "https://jumpstartprodsg.blob.core.windows.net/scenarios/prod/JSW11IoTBase.vhdx" $hypervVMUser = "Administrator" $hypervVMPassword = "JS123!!" $kubernetesDistribution = $env:kubernetesDistribution $templateBaseUrl = $env:templateBaseUrl $aksEEReleasesUrl = "https://api.github.com/repos/Azure/AKS-Edge/releases" +$AKSEEPinnedSchemaVersion = $Env:AKSEEPinnedSchemaVersion Write-Header "Executing LogonScript.ps1" @@ -233,7 +234,6 @@ Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { $deploymentFolder = "C:\Deployment" # Deployment folder is already pre-created in the VHD image $logsFolder = "$deploymentFolder\Logs" $kubeFolder = "$env:USERPROFILE\.kube" - # Set up an array of folders $folders = @($logsFolder, $kubeFolder) @@ -252,6 +252,7 @@ Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { $logsFolder = "$deploymentFolder\Logs" Start-Transcript -Path $logsFolder\AKSEEBootstrap.log $SiteConfig = $using:SiteConfig + $AKSEEPinnedSchemaVersion = $using:AKSEEPinnedSchemaVersion ########################################## # Deploying AKS Edge Essentials clusters @@ -303,19 +304,21 @@ Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { } 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 -$schemaVersion = $jsonContent.SchemaVersion -# Clean up the downloaded release files -Remove-Item -Path $output -Force -Remove-Item -Path "C:\temp\AKS-Edge-$latestReleaseTag" -Force -Recurse - +if ($AKSEEPinnedSchemaVersion -ne "useLatest") { + $SchemaVersion = $AKSEEPinnedSchemaVersion +}else{ + $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 + $schemaVersion = $jsonContent.SchemaVersion + # Clean up the downloaded release files + Remove-Item -Path $output -Force + Remove-Item -Path "C:\temp\AKS-Edge-$latestReleaseTag" -Force -Recurse +} ############################################################################### # Setting up replacment parameters for AKS Edge Essentials config json file diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/clientVm/clientVm.bicep b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/clientVm/clientVm.bicep index 9864ea8918..37d329c5d1 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/clientVm/clientVm.bicep +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/clientVm/clientVm.bicep @@ -50,6 +50,9 @@ param githubAccount string = 'microsoft' @description('Target GitHub branch') param githubBranch string = 'main' +@description('The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing.') +param AKSEEPinnedSchemaVersion string = 'useLatest' + var encodedPassword = base64(windowsAdminPassword) var bastionName = 'AKS-EE-Full-Bastion' var publicIpAddressName = deployBastion == false ? '${vmName}-PIP' : '${bastionName}-PIP' @@ -153,7 +156,7 @@ resource vmBootstrap 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = fileUris: [ uri(templateBaseUrl, 'artifacts/Bootstrap.ps1') ] - commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -templateBaseUrl ${templateBaseUrl} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -kubernetesDistribution ${kubernetesDistribution}' + commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -templateBaseUrl ${templateBaseUrl} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -kubernetesDistribution ${kubernetesDistribution} -AKSEEPinnedSchemaVersion ${AKSEEPinnedSchemaVersion}' } } } diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/main.bicep b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/main.bicep index 008ee87be7..9c90aa1ae1 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/main.bicep +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/main.bicep @@ -42,6 +42,11 @@ param subnetNameCloud string = 'AKS-EE-Full-Subnet' @description('AKS Edge Essentials Kubernetes distribution') param kubernetesDistribution string +@description('''The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing. +To pin a specific version, use the format '1.13'. To use the latest schema version, use 'useLatest'. +''') +param AKSEEPinnedSchemaVersion string = '1.13' + var templateBaseUrl = 'https://raw.githubusercontent.com/${githubAccount}/azure_arc/${githubBranch}/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_full_akri/bicep_template/' module networkDeployment 'mgmt/network.bicep' = { @@ -69,5 +74,6 @@ module clientVmDeployment 'clientVm/clientVm.bicep' = { location: location subnetId: networkDeployment.outputs.subnetId kubernetesDistribution: kubernetesDistribution + AKSEEPinnedSchemaVersion: AKSEEPinnedSchemaVersion } } diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/Bootstrap.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/Bootstrap.ps1 index 04d4e88b71..e9a0bcae7e 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/Bootstrap.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/Bootstrap.ps1 @@ -8,7 +8,8 @@ param ( [string]$templateBaseUrl, [string]$resourceGroup, [string]$windowsNode, - [string]$kubernetesDistribution + [string]$kubernetesDistribution, + [string]$AKSEEPinnedSchemaVersion ) [System.Environment]::SetEnvironmentVariable('adminUsername', $adminUsername,[System.EnvironmentVariableTarget]::Machine) @@ -21,6 +22,7 @@ param ( [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('AKSEEPinnedSchemaVersion', $AKSEEPinnedSchemaVersion, [System.EnvironmentVariableTarget]::Machine) # Create path Write-Output "Create deployment path" diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/LogonScript.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/LogonScript.ps1 index d27781a9d3..da3866f1e5 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/LogonScript.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/artifacts/LogonScript.ps1 @@ -8,7 +8,7 @@ $schemaVersion = "1.1" $versionAksEdgeConfig = "1.0" $aksEdgeDeployModules = "main" $aksEEReleasesUrl = "https://api.github.com/repos/Azure/AKS-Edge/releases" - +$AKSEEPinnedSchemaVersion = $Env:AKSEEPinnedSchemaVersion # Requires -RunAsAdministrator New-Variable -Name AksEdgeRemoteDeployVersion -Value $AksEdgeRemoteDeployVersion -Option Constant -ErrorAction SilentlyContinue @@ -27,18 +27,21 @@ if ($env:kubernetesDistribution -eq "k8s") { } 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 +if ($AKSEEPinnedSchemaVersion -ne "useLatest") { + $SchemaVersion = $AKSEEPinnedSchemaVersion +}else{ + $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 + $schemaVersion = $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 = @" diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/azuredeploy.json b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/azuredeploy.json index 7859d554d7..6cf8d1f8dd 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/azuredeploy.json +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single/arm_template/azuredeploy.json @@ -139,6 +139,13 @@ "metadata": { "description": "Deploy Windows Node for AKS Edge Essentials" } + }, + "AKSEEPinnedSchemaVersion": { + "type": "string", + "defaultValue": "1.13", + "metadata": { + "description": "The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing.To pin a specific version, use the format '1.13'. To use the latest schema version, use 'useLatest'" + } } }, "variables": { @@ -322,7 +329,7 @@ "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'))]" + "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'), ' -AKSEEPinnedSchemaVersion ', parameters('AKSEEPinnedSchemaVersion'))]" } } }, diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/Bootstrap.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/Bootstrap.ps1 index 04d4e88b71..e9a0bcae7e 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/Bootstrap.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/Bootstrap.ps1 @@ -8,7 +8,8 @@ param ( [string]$templateBaseUrl, [string]$resourceGroup, [string]$windowsNode, - [string]$kubernetesDistribution + [string]$kubernetesDistribution, + [string]$AKSEEPinnedSchemaVersion ) [System.Environment]::SetEnvironmentVariable('adminUsername', $adminUsername,[System.EnvironmentVariableTarget]::Machine) @@ -21,6 +22,7 @@ param ( [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('AKSEEPinnedSchemaVersion', $AKSEEPinnedSchemaVersion, [System.EnvironmentVariableTarget]::Machine) # Create path Write-Output "Create deployment path" diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/LogonScript.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/LogonScript.ps1 index 82e7d079c9..dcd82d9330 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/LogonScript.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/artifacts/LogonScript.ps1 @@ -8,6 +8,7 @@ $schemaVersion = "1.1" $versionAksEdgeConfig = "1.0" $aksEdgeDeployModules = "main" $aksEEReleasesUrl = "https://api.github.com/repos/Azure/AKS-Edge/releases" +$AKSEEPinnedSchemaVersion = $Env:AKSEEPinnedSchemaVersion # Requires -RunAsAdministrator New-Variable -Name AksEdgeRemoteDeployVersion -Value $AksEdgeRemoteDeployVersion -Option Constant -ErrorAction SilentlyContinue @@ -26,18 +27,21 @@ if ($env:kubernetesDistribution -eq "k8s") { } 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 +if ($AKSEEPinnedSchemaVersion -ne "useLatest") { + $SchemaVersion = $AKSEEPinnedSchemaVersion +}else{ + $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 + $schemaVersion = $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 = @" diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/azuredeploy.json b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/azuredeploy.json index 43af3f02e6..5119b87a30 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/azuredeploy.json +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_akri/arm_template/azuredeploy.json @@ -139,6 +139,13 @@ "metadata": { "description": "Deploy Windows Node for AKS Edge Essentials" } + }, + "AKSEEPinnedSchemaVersion": { + "type": "string", + "defaultValue": "1.13", + "metadata": { + "description": "The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing.To pin a specific version, use the format '1.13'. To use the latest schema version, use 'useLatest'" + } } }, "variables": { @@ -322,7 +329,7 @@ "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'))]" + "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'), ' -AKSEEPinnedSchemaVersion ', parameters('AKSEEPinnedSchemaVersion'))]" } } }, diff --git a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_vi/artifacts/LogonScript.ps1 b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_vi/artifacts/LogonScript.ps1 index 667872d0b8..052fc8e969 100644 --- a/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_vi/artifacts/LogonScript.ps1 +++ b/azure_arc_k8s_jumpstart/aks_hybrid/aks_edge_essentials_single_vi/artifacts/LogonScript.ps1 @@ -3,7 +3,7 @@ Start-Transcript -Path C:\Temp\LogonScript.log Set-ExecutionPolicy Bypass -Scope Process -Force # Parameters -$schemaVersionAksEdgeConfig = "1.9" +$schemaVersionAksEdgeConfig = "1.13" $versionAksEdgeConfig = "1.0" $guid = ([System.Guid]::NewGuid()).ToString().subString(0,5).ToLower() $clusterName = "$Env:resourceGroup-$guid" @@ -136,6 +136,7 @@ Connect-AksEdgeArc -JsonConfigFilePath $tempDir\aksedge-config.json ##################################################################### helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update +Start-Sleep -Seconds 5 helm install ingress-nginx ingress-nginx/ingress-nginx ##################################################################### @@ -149,19 +150,24 @@ Start-Sleep -Seconds 30 ### Video Indexer setup ##################################################################### $viApiVersion="2023-06-02-preview" -$extensionName="videoindexer" -#$version="1.0.28-preview" # switch to blank +$extensionName="video-indexer" +$version="1.0.41" # switch to blank $namespace="video-indexer" $releaseTrain="release" # switch to release $storageClass="longhorn" Write-Host "Create Cognitive Services on VI resource provider" $createResourceUri = "https://management.azure.com/subscriptions/${env:subscriptionId}/resourceGroups/${env:resourceGroup}/providers/Microsoft.VideoIndexer/accounts/${env:videoIndexerAccountName}/CreateExtensionDependencies?api-version=${viApiVersion}" + $result = $(az rest --method post --uri $createResourceUri) | ConvertFrom-Json -Write-Host "Retrieving Cognitive Service Credentials..." + $getSecretsUri="https://management.azure.com/subscriptions/${env:subscriptionId}/resourceGroups/${env:resourceGroup}/providers/Microsoft.VideoIndexer/accounts/${env:videoIndexerAccountName}/ListExtensionDependenciesData?api-version=$viApiVersion" -$csResourcesData=$(az rest --method post --uri $getSecretsUri) | ConvertFrom-Json +while ($null -eq $csResourcesData) { + Write-Host "Retrieving Cognitive Service Credentials..." + $csResourcesData=$(az rest --method post --uri $getSecretsUri) | ConvertFrom-Json + Start-Sleep -Seconds 10 +} Write-Host Write-Host "Getting VM public IP address..." @@ -177,14 +183,16 @@ az k8s-extension create --name $extensionName ` --cluster-name $clusterName ` --resource-group $Env:resourceGroup ` --cluster-type connectedClusters ` - --release-train $releaseTrain ` + --version $version ` --auto-upgrade-minor-version false ` --config-protected-settings "speech.endpointUri=$($csResourcesData.speechCognitiveServicesEndpoint)" ` --config-protected-settings "speech.secret=$($csResourcesData.speechCognitiveServicesPrimaryKey)" ` --config-protected-settings "translate.endpointUri=$($csResourcesData.translatorCognitiveServicesEndpoint)" ` --config-protected-settings "translate.secret=$($csResourcesData.translatorCognitiveServicesPrimaryKey)" ` - --config "videoIndexer.accountId=${Env:videoIndexerAccountId}" ` + --config-protected-settings "ocr.endpointUri=$($csResourcesData.ocrCognitiveServicesEndpoint)" ` + --config-protected-settings "ocr.secret=$($csResourcesData.ocrCognitiveServicesPrimaryKey)" ` --config "frontend.endpointUri=https://$ipAddress" ` + --config "videoIndexer.accountId=${Env:videoIndexerAccountId}" ` --config "storage.storageClass=$storageClass" ` --config "storage.accessMode=ReadWriteMany" @@ -195,7 +203,7 @@ New-NetFirewallRule -DisplayName "Allow Inbound Port 443" -Direction Inbound -Lo Write-Host "Adding port forward for VI frontend..." Start-Sleep -Seconds 20 -$ing = kubectl get ing videoindexer-vi-arc -n $namespace -o json | ConvertFrom-Json +$ing = kubectl get ing video-indexer-vi-arc -n $namespace -o json | ConvertFrom-Json $ingIp = $ing.status.loadBalancer.ingress.ip netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=80 connectaddress=$ingIp connectport=80 netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=443 connectaddress=$ingIp connectport=443 diff --git a/azure_arc_k8s_jumpstart/aks_iot_edge/terraform/variable.tf b/azure_arc_k8s_jumpstart/aks_iot_edge/terraform/variable.tf index a78820c944..6fe0146039 100644 --- a/azure_arc_k8s_jumpstart/aks_iot_edge/terraform/variable.tf +++ b/azure_arc_k8s_jumpstart/aks_iot_edge/terraform/variable.tf @@ -165,7 +165,7 @@ variable "name" { variable "kubernetes_version" { type = string description = "(Required) Version of Kubernetes specified when creating the AKS managed cluster. If not specified, the latest recommended version will be used at provisioning time (but won't auto-upgrade). NOTE: Upgrading your cluster may take up to 10 minutes per node." - default = "1.27.1" + default = "1.28.5" } variable "dns_prefix" { diff --git a/azure_arc_k8s_jumpstart/cluster_api/capi_azure/artifacts/installCAPI.sh b/azure_arc_k8s_jumpstart/cluster_api/capi_azure/artifacts/installCAPI.sh index 50dcf96145..c0ba5ee41a 100644 --- a/azure_arc_k8s_jumpstart/cluster_api/capi_azure/artifacts/installCAPI.sh +++ b/azure_arc_k8s_jumpstart/cluster_api/capi_azure/artifacts/installCAPI.sh @@ -73,7 +73,7 @@ echo "" # Installing snap echo "" - echo "Installing snap" + echo "Installing snap" sudo apt install snapd echo "" diff --git a/azure_arc_k8s_jumpstart/cluster_api/capi_azure/installCAPI.sh b/azure_arc_k8s_jumpstart/cluster_api/capi_azure/installCAPI.sh index 1dae523237..31636832fa 100644 --- a/azure_arc_k8s_jumpstart/cluster_api/capi_azure/installCAPI.sh +++ b/azure_arc_k8s_jumpstart/cluster_api/capi_azure/installCAPI.sh @@ -75,13 +75,13 @@ echo "" # Installing snap echo "" - echo "Installing snap" + echo "Installing snap" sudo apt install snapd echo "" # Installing jq echo "" - echo "Installing jq" + echo "Installing jq" sudo apt install jq -y echo "" @@ -219,7 +219,7 @@ EOF # Iterate over each node and check its status for node in $nodes; do ready=$(kubectl get nodes $node --kubeconfig=./$CLUSTER_NAME.kubeconfig -o json | jq -r '.status.conditions[] | select(.type=="Ready") | .status') - + if [[ $ready != "True" ]]; then echo "Node $node is not ready." all_ready=false diff --git a/azure_arc_k8s_jumpstart/cluster_api/gitops/basic/az_k8sconfig_capi.sh b/azure_arc_k8s_jumpstart/cluster_api/gitops/basic/az_k8sconfig_capi.sh index cbf809d6e3..08e457a9f4 100644 --- a/azure_arc_k8s_jumpstart/cluster_api/gitops/basic/az_k8sconfig_capi.sh +++ b/azure_arc_k8s_jumpstart/cluster_api/gitops/basic/az_k8sconfig_capi.sh @@ -8,7 +8,7 @@ export password='' export tenantId='' export resourceGroup='' export arcClusterName='' -export appClonedRepo='' +export appClonedRepo='' export namespace='hello-arc' diff --git a/azure_arc_k8s_jumpstart/cluster_api/gitops/helm/az_k8sconfig_helm_capi.sh b/azure_arc_k8s_jumpstart/cluster_api/gitops/helm/az_k8sconfig_helm_capi.sh index 60f7b08814..a623f4fbd8 100644 --- a/azure_arc_k8s_jumpstart/cluster_api/gitops/helm/az_k8sconfig_helm_capi.sh +++ b/azure_arc_k8s_jumpstart/cluster_api/gitops/helm/az_k8sconfig_helm_capi.sh @@ -8,7 +8,7 @@ export password='' export tenantId='' export resourceGroup='' export arcClusterName='' -export appClonedRepo='' +export appClonedRepo='' export ingressNamespace='ingress-nginx' export namespace='hello-arc' diff --git a/azure_arc_k8s_jumpstart/gke/gitops/basic/az_k8sconfig_gke.sh b/azure_arc_k8s_jumpstart/gke/gitops/basic/az_k8sconfig_gke.sh index 02076d5bbe..fca71def63 100755 --- a/azure_arc_k8s_jumpstart/gke/gitops/basic/az_k8sconfig_gke.sh +++ b/azure_arc_k8s_jumpstart/gke/gitops/basic/az_k8sconfig_gke.sh @@ -6,7 +6,7 @@ export password='' export tenantId='' export resourceGroup='' export arcClusterName='' -export appClonedRepo='' +export appClonedRepo='' export namespace='hello-arc' # Installing Helm 3 diff --git a/azure_arc_k8s_jumpstart/gke/gitops/helm/az_k8sconfig_helm_gke.sh b/azure_arc_k8s_jumpstart/gke/gitops/helm/az_k8sconfig_helm_gke.sh index 6372716bb0..a8119146e5 100755 --- a/azure_arc_k8s_jumpstart/gke/gitops/helm/az_k8sconfig_helm_gke.sh +++ b/azure_arc_k8s_jumpstart/gke/gitops/helm/az_k8sconfig_helm_gke.sh @@ -8,7 +8,7 @@ export password='' export tenantId='' export resourceGroup='' export arcClusterName='' -export appClonedRepo='' +export appClonedRepo='' export ingressNamespace='ingress-nginx' export namespace='hello-arc' diff --git a/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s.sh b/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s.sh index c812a62017..ae1de49d2b 100644 --- a/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s.sh +++ b/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s.sh @@ -8,7 +8,7 @@ export password='' export tenantId='' export resourceGroup='' export arcClusterName='' -export appClonedRepo='' +export appClonedRepo='' export namespace='hello-arc' # Logging in to Azure using service principal diff --git a/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s_windows.ps1 b/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s_windows.ps1 index 2e75226eec..a88b86dd9b 100644 --- a/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s_windows.ps1 +++ b/azure_arc_k8s_jumpstart/microk8s/gitops/helm/az_k8sconfig_helm_microk8s_windows.ps1 @@ -6,7 +6,7 @@ $password="" $tenantId="" $resourceGroup="" $arcClusterName="" -$appClonedRepo="" +$appClonedRepo="" $namespace='hello-arc' # Logging in to Azure using service principal diff --git a/azure_arc_k8s_jumpstart/rancher_k3s/azure/arm_template/scripts/installK3s.sh b/azure_arc_k8s_jumpstart/rancher_k3s/azure/arm_template/scripts/installK3s.sh index 6540406168..f765ef7f5a 100644 --- a/azure_arc_k8s_jumpstart/rancher_k3s/azure/arm_template/scripts/installK3s.sh +++ b/azure_arc_k8s_jumpstart/rancher_k3s/azure/arm_template/scripts/installK3s.sh @@ -25,10 +25,10 @@ sed -i '6s/^/export vmName=/' vars.sh sed -i '7s/^/export azureLocation=/' vars.sh sed -i '8s/^/export templateBaseUrl=/' vars.sh -chmod +x vars.sh +chmod +x vars.sh . ./vars.sh -export K3S_VERSION="1.28.2+k3s1" # Do not change! +export K3S_VERSION="1.28.5+k3s1" # Do not change! # Creating login message of the day (motd) sudo curl -v -o /etc/profile.d/welcomeK3s.sh ${templateBaseUrl}scripts/welcomeK3s.sh diff --git a/azure_arc_k8s_jumpstart/rancher_k3s/azure/terraform/scripts/installK3s.sh b/azure_arc_k8s_jumpstart/rancher_k3s/azure/terraform/scripts/installK3s.sh index 16bf2e80b6..5c781ac6fd 100644 --- a/azure_arc_k8s_jumpstart/rancher_k3s/azure/terraform/scripts/installK3s.sh +++ b/azure_arc_k8s_jumpstart/rancher_k3s/azure/terraform/scripts/installK3s.sh @@ -25,10 +25,10 @@ sed -i '6s/^/export vmName=/' vars.sh sed -i '7s/^/export azureLocation=/' vars.sh sed -i '8s/^/export templateBaseUrl=/' vars.sh -chmod +x vars.sh +chmod +x vars.sh . ./vars.sh -export K3S_VERSION="1.28.2+k3s1" # Do not change! +export K3S_VERSION="1.28.5+k3s1" # Do not change! # Creating login message of the day (motd) sudo curl -v -o /etc/profile.d/welcomeK3s.sh ${templateBaseUrl}scripts/welcomeK3s.sh diff --git a/azure_arc_k8s_jumpstart/rancher_k3s/vmware/terraform/scripts/vars.sh b/azure_arc_k8s_jumpstart/rancher_k3s/vmware/terraform/scripts/vars.sh index a0bd074eb5..e14450ef29 100644 --- a/azure_arc_k8s_jumpstart/rancher_k3s/vmware/terraform/scripts/vars.sh +++ b/azure_arc_k8s_jumpstart/rancher_k3s/vmware/terraform/scripts/vars.sh @@ -14,4 +14,4 @@ export TF_VAR_vsphere_password='' export TF_VAR_vsphere_server='' export TF_VAR_admin_user='' export TF_VAR_admin_password='' -export K3S_VERSION="1.28.2+k3s1" # Do not change! +export K3S_VERSION="1.28.5+k3s1" # Do not change! diff --git a/azure_arc_ml_jumpstart/aks/arm_template/aks.json b/azure_arc_ml_jumpstart/aks/arm_template/aks.json index f5a3dec36c..9b5a3b52da 100644 --- a/azure_arc_ml_jumpstart/aks/arm_template/aks.json +++ b/azure_arc_ml_jumpstart/aks/arm_template/aks.json @@ -88,7 +88,7 @@ } }, "kubernetesVersion": { - "defaultValue": "1.19.11", + "defaultValue": "1.28.5", "type": "string", "metadata": { "description": "The version of Kubernetes." diff --git a/azure_arc_ml_jumpstart/aks/arm_template/artifacts/azuredeploy.parameters.example.json b/azure_arc_ml_jumpstart/aks/arm_template/artifacts/azuredeploy.parameters.example.json index ee4e8ecf8d..d1cf95c54d 100644 --- a/azure_arc_ml_jumpstart/aks/arm_template/artifacts/azuredeploy.parameters.example.json +++ b/azure_arc_ml_jumpstart/aks/arm_template/artifacts/azuredeploy.parameters.example.json @@ -30,7 +30,7 @@ "value": "https://raw.githubusercontent.com/microsoft/azure_arc/main/azure_arc_ml_jumpstart/aks/arm_template/" }, "kubernetesVersion": { - "value": "1.19.11" + "value": "1.28.5" }, "dnsPrefix": { "value": "arcml" diff --git a/azure_arc_ml_jumpstart/aks/arm_template/azuredeploy.json b/azure_arc_ml_jumpstart/aks/arm_template/azuredeploy.json index 24657801f7..e9bd67f1c1 100644 --- a/azure_arc_ml_jumpstart/aks/arm_template/azuredeploy.json +++ b/azure_arc_ml_jumpstart/aks/arm_template/azuredeploy.json @@ -53,7 +53,7 @@ } }, "kubernetesVersion": { - "defaultValue": "1.26.6", + "defaultValue": "1.28.5", "type": "string", "metadata": { "description": "The version of Kubernetes." diff --git a/azure_arc_ml_jumpstart/aks/arm_template/azuredeploy.parameters.json b/azure_arc_ml_jumpstart/aks/arm_template/azuredeploy.parameters.json index 622334f481..ebfd1fdc2a 100644 --- a/azure_arc_ml_jumpstart/aks/arm_template/azuredeploy.parameters.json +++ b/azure_arc_ml_jumpstart/aks/arm_template/azuredeploy.parameters.json @@ -30,7 +30,7 @@ "value": "https://raw.githubusercontent.com/microsoft/azure_arc/main/azure_arc_ml_jumpstart/aks/arm_template/" }, "kubernetesVersion": { - "value": "1.19.11" + "value": "1.28.5" }, "dnsPrefix": { "value": "arcml" diff --git a/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/aws_infra.tf b/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/aws_infra.tf index 736538e8a8..a542bf16e2 100644 --- a/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/aws_infra.tf +++ b/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/aws_infra.tf @@ -65,7 +65,7 @@ data "aws_ami" "ubuntu" { most_recent = true filter { name = "name" - values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"] + values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] } filter { name = "virtualization-type" @@ -74,11 +74,11 @@ data "aws_ami" "ubuntu" { owners = ["099720109477"] # canonical } -data "aws_ami" "Windows_2019" { +data "aws_ami" "Windows_2022" { most_recent = true filter { name = "name" - values = ["Windows_Server-2019-English-Full-Base-*"] + values = ["Windows_Server-2022-English-Full-Base-*"] } filter { name = "architecture" @@ -92,10 +92,14 @@ data "aws_ami" "centos" { most_recent = true filter { name = "name" - values = ["CentOS 7.8.2003 x86_64"] + values = ["Fedora-Cloud-Base-39*"] } filter { name = "architecture" values = ["x86_64"] } + filter { + name = "virtualization-type" + values = ["hvm"] + } } \ No newline at end of file diff --git a/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/scripts/vars.sh b/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/scripts/vars.sh index 31f5ae60ee..912d4233e9 100644 --- a/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/scripts/vars.sh +++ b/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/scripts/vars.sh @@ -6,4 +6,4 @@ export TF_VAR_client_id={client id} export TF_VAR_client_secret={client secret} export TF_VAR_tenant_id={tenant id} export AWS_ACCESS_KEY_ID={aws access key} -export AWS_SECRET_ACCESS_KEY={aws secret key} +export AWS_SECRET_ACCESS_KEY={aws secret key} \ No newline at end of file diff --git a/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/server_ansible.tf b/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/server_ansible.tf index d7ca89fd20..48cb25fa6b 100644 --- a/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/server_ansible.tf +++ b/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/server_ansible.tf @@ -10,7 +10,7 @@ resource "aws_instance" "ansible" { } connection { - user = "centos" + user = "fedora" private_key = file("~/.ssh/id_rsa") agent = false host = aws_instance.ansible.public_ip @@ -18,24 +18,23 @@ resource "aws_instance" "ansible" { provisioner "file" { source = "ansible_config" - destination = "~/ansible" + destination = "/home/fedora/ansible" } provisioner "file" { source = "~/.ssh/id_rsa" - destination = "~/.ssh/id_rsa" + destination = "/home/fedora/.ssh/id_rsa" } provisioner "remote-exec" { inline = [ - "sudo yum install epel-release -y", - "sudo yum install nano python3 python3-pip -y", + "sudo dnf install python3-pip ansible -y", "sudo python3 -m pip install --upgrade setuptools", "sudo python3 -m pip install wheel setuptools_rust", "sudo python3 -m pip install --upgrade pip", "sudo curl https://sh.rustup.rs -sSf | sh -s -- -y", - "sudo python3 -m pip install ansible botocore boto3 pywinrm", - "sudo chmod 600 /home/centos/.ssh/id_rsa" + "sudo python3 -m pip install botocore boto3 pywinrm", + "sudo chmod 600 /home/fedora/.ssh/id_rsa" ] } diff --git a/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/servers_windows.tf b/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/servers_windows.tf index 476e3c3b2b..5d155e0ed8 100644 --- a/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/servers_windows.tf +++ b/azure_arc_servers_jumpstart/aws/scaled_deployment/ansible/terraform/servers_windows.tf @@ -4,7 +4,7 @@ locals { resource "aws_instance" "windows" { count = var.server_count - ami = data.aws_ami.Windows_2019.image_id + ami = data.aws_ami.Windows_2022.image_id instance_type = "t2.micro" key_name = aws_key_pair.keypair.id vpc_security_group_ids = [aws_security_group.ingress-all.id] 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..14d0e07cd1 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 @@ -44,9 +44,8 @@ choco install azcopy10 # Download of Windows VM VHDX file 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 +$sourceFolder = 'https://jumpstartprodsg.blob.core.windows.net/scenarios/prod/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_servers_jumpstart/esu/artifacts/LogonScript.ps1 b/azure_arc_servers_jumpstart/esu/artifacts/LogonScript.ps1 index 792f274456..b449532c3f 100644 --- a/azure_arc_servers_jumpstart/esu/artifacts/LogonScript.ps1 +++ b/azure_arc_servers_jumpstart/esu/artifacts/LogonScript.ps1 @@ -139,7 +139,7 @@ Start-Transcript -Path $logFilePath -Force -ErrorAction SilentlyContinue $imageName = "JSWin2K12Base" $vmvhdPath = "$Env:ESUVMDir\${imageName}.vhdx" # Moved VHD storage account details here to keep only in place to prevent duplicates. - $vhdDownload = "https://jsvhds.blob.core.windows.net/scenarios/prod/JSWin2K12Base.vhdx?sp=r&st=2023-09-11T08:05:53Z&se=2025-11-08T17:05:53Z&spr=https&sv=2022-11-02&sr=b&sig=zoVpd9AMzsTRRE0a7eLJYeFURexY4R9VSzOGKfLOx%2FQ%3D" + $vhdDownload = "https://jumpstartprodsg.blob.core.windows.net/scenarios/prod/JSWin2K12Base.vhdx" Write-Host "Fetching VM" @@ -209,7 +209,7 @@ Start-Transcript -Path $logFilePath -Force -ErrorAction SilentlyContinue $imageName = "JSSQL12Base" $vmvhdPath = "$Env:ESUVMDir\${imageName}.vhdx" # Moved VHD storage account details here to keep only in place to prevent duplicates. - $vhdDownload = "https://jsvhds.blob.core.windows.net/scenarios/prod/JSSQL12Base.vhdx?sp=r&st=2023-09-27T06:57:38Z&se=2027-09-11T14:57:38Z&spr=https&sv=2022-11-02&sr=b&sig=BXtEL%2B7RdLairRHXd3TA6n5q%2FNktjItvcU1rzol9Dl0%3D" + $vhdDownload = "https://jumpstartprodsg.blob.core.windows.net/scenarios/prod/JSSQL12Base.vhdx" Write-Host "Fetching VM" @@ -278,7 +278,7 @@ Start-Transcript -Path $logFilePath -Force -ErrorAction SilentlyContinue JSWin2K12Base = @{ imageName = 'JSWin2K12Base' vmvhdPath = "$Env:ESUVMDir\JSWin2K12Base.vhdx" - vhdDownload = "https://jsvhds.blob.core.windows.net/scenarios/prod/JSWin2K12Base.vhdx?sp=r&st=2023-09-11T08:05:53Z&se=2025-11-08T17:05:53Z&spr=https&sv=2022-11-02&sr=b&sig=zoVpd9AMzsTRRE0a7eLJYeFURexY4R9VSzOGKfLOx%2FQ%3D" + vhdDownload = "https://jumpstartprodsg.blob.core.windows.net/scenarios/prod/JSWin2K12Base.vhdx" vmName = "JSWin2K12Base" ip = "10.10.1.100" type = "Windows" @@ -286,7 +286,7 @@ Start-Transcript -Path $logFilePath -Force -ErrorAction SilentlyContinue JSSQL12Base = @{ imageName = "JSSQL12Base" vmvhdPath = "$Env:ESUVMDir\JSSQL12Base.vhdx" - vhdDownload = "https://jsvhds.blob.core.windows.net/scenarios/prod/JSSQL12Base.vhdx?sp=r&st=2023-09-27T06:57:38Z&se=2027-09-11T14:57:38Z&spr=https&sv=2022-11-02&sr=b&sig=BXtEL%2B7RdLairRHXd3TA6n5q%2FNktjItvcU1rzol9Dl0%3D" + vhdDownload = "https://jumpstartprodsg.blob.core.windows.net/scenarios/prod/JSSQL12Base.vhdx" vmName = "JSSQL12Base" ip = "10.10.1.101" type = "SQL" diff --git a/azure_arc_servers_jumpstart/privatelink/artifacts/installArcAgent.ps1 b/azure_arc_servers_jumpstart/privatelink/artifacts/installArcAgent.ps1 index 56926df67f..4638cd65ca 100644 --- a/azure_arc_servers_jumpstart/privatelink/artifacts/installArcAgent.ps1 +++ b/azure_arc_servers_jumpstart/privatelink/artifacts/installArcAgent.ps1 @@ -3,7 +3,7 @@ Start-Transcript -Path C:\Temp\ArcInstallScript.log # Azure Login -az login --service-principal -u $Env:appId -p=$Env:password --tenant $Env:tenantId +az login --service-principal -u $Env:appId -p $Env:password --tenant $Env:tenantId az account set -s $Env:SubscriptionId # Configure hosts file for Private link endpoints resolution @@ -62,4 +62,4 @@ msiexec /i AzureConnectedMachineAgent.msi /l*v installationlog.txt /qn | Out-Str # Remove schedule task Unregister-ScheduledTask -TaskName "LogonScript" -Confirm:$False -Stop-Process -Name powershell -Force \ No newline at end of file +Stop-Process -Name powershell -Force diff --git a/azure_arc_servers_jumpstart/proxy/ARM/cloudDeploy.json b/azure_arc_servers_jumpstart/proxy/ARM/cloudDeploy.json index ac619a991f..30b9cf9090 100644 --- a/azure_arc_servers_jumpstart/proxy/ARM/cloudDeploy.json +++ b/azure_arc_servers_jumpstart/proxy/ARM/cloudDeploy.json @@ -327,7 +327,7 @@ "name": "[if(not(parameters('deployBastion')),'Basic','Standard')]" }, "properties": { - "publicIpAllocationMethod": "Dynamic", + "publicIpAllocationMethod": "Static", "publicIPAddressVersion": "IPv4", "dnsSettings": { "domainNameLabel": "[parameters('proxydnsLabelPrefix')]" @@ -344,7 +344,7 @@ "name": "[if(not(parameters('deployBastion')),'Basic','Standard')]" }, "properties": { - "publicIpAllocationMethod": "Dynamic", + "publicIpAllocationMethod": "Static", "publicIPAddressVersion": "IPv4", "dnsSettings": { "domainNameLabel": "[parameters('dnsLabelPrefix')]" diff --git a/azure_arc_servers_jumpstart/proxy/artifacts/install_arc_agent_proxy.sh b/azure_arc_servers_jumpstart/proxy/artifacts/install_arc_agent_proxy.sh index 5cd9ac2ce7..3f1e309d11 100644 --- a/azure_arc_servers_jumpstart/proxy/artifacts/install_arc_agent_proxy.sh +++ b/azure_arc_servers_jumpstart/proxy/artifacts/install_arc_agent_proxy.sh @@ -10,7 +10,8 @@ LOCATION=$7 VMNAME=$8 URL=$9 PORT=3128 -PASSWORD=$10 +PASSWORD=${10} + touch /home/$USER/.bash_profile chmod +x /home/$USER/.bash_profile @@ -31,6 +32,8 @@ export PASSWORD=$PASSWORD export HTTP_PROXY="http://$URL:$PORT" export HTTPS_PROXY="http://$URL:$PORT" +export http_proxy="http://$URL:$PORT" +export https_proxy="http://$URL:$PORT" export FTP_PROXY="http://$URL:$PORT" export DNS_PROXY="http://$URL:$PORT" export RSYNC_PROXY="http://$URL:$PORT" @@ -38,22 +41,23 @@ export RSYNC_PROXY="http://$URL:$PORT" # Set up proxy echo "export HTTP_PROXY="http://$URL:$PORT"" | sudo tee -a /etc/profile.d/proxy.sh -echo "export HTTPS_PROXY=$HTTP_PROXY" | sudo tee -a /etc/profile.d/proxy.sh -echo "export FTP_PROXY=$HTTP_PROXY" | sudo tee -a /etc/profile.d/proxy.sh -echo "export DNS_PROXY=$HTTP_PROXY" | sudo tee -a /etc/profile.d/proxy.sh -echo "export RSYNC_PROXY=$HTTP_PROXY" | sudo tee -a /etc/profile.d/proxy.sh - +echo "export HTTPS_PROXY="http://$URL:$PORT"" | sudo tee -a /etc/profile.d/proxy.sh +echo "export FTP_PROXY="http://$URL:$PORT"" | sudo tee -a /etc/profile.d/proxy.sh +echo "export DNS_PROXY="http://$URL:$PORT""| sudo tee -a /etc/profile.d/proxy.sh +echo "export RSYNC_PROXY="http://$URL:$PORT"" | sudo tee -a /etc/profile.d/proxy.sh # Set up certificate sudo touch /etc/apt/apt.conf echo "Acquire::http::proxy \"http://$URL:$PORT\";" | sudo tee /etc/apt/apt.conf > /dev/null + + sudo apt-get update sudo apt-get install -y sshpass sudo echo $PASSWORD > /tmp/pass sudo sshpass -f '/tmp/pass' scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $USER@$URL:/tmp/squid-ca-cert-key.pem . -rm -f /tmp/pass sudo mv ./squid-ca-cert-key.pem /usr/local/share/ca-certificates/squid-ca-cert-key.crt sudo update-ca-certificates +sudo rm -f /tmp/pass ## Configure Ubuntu to allow Azure Arc Connected Machine Agent Installation @@ -67,18 +71,22 @@ echo "Configuring Firewall" sudo ufw --force enable sudo ufw deny out from any to 169.254.169.254 sudo ufw default allow incoming + + sudo apt-get update + echo "Reconfiguring Hostname" sudo hostname $VMNAME sudo -E /bin/sh -c 'echo $VMNAME > /etc/hostname' # Download the installation package -wget https://aka.ms/azcmagent -O ~/install_linux_azcmagent.sh --no-check-certificate +wget -e use_proxy=yes -e https_proxy=$URL:$PORT https://aka.ms/azcmagent -O ~/install_linux_azcmagent.sh --no-check-certificate # Install the hybrid agent sudo bash ~/install_linux_azcmagent.sh --proxy "http://$URL:$PORT" +sudo azcmagent config set proxy.url "http://$URL:$PORT" # Run connect command sudo azcmagent connect \ @@ -91,7 +99,6 @@ sudo azcmagent connect \ --tags "Project=jumpstart_azure_arc_servers" \ --correlation-id "d009f5dd-dba8-4ac7-bac9-b54ef3a6671a" -sudo azcmagent config set proxy.url "http://$URL:$PORT" sudo rm -f /home/$USER/.bash_profile EOT diff --git a/azure_arc_servers_jumpstart/proxy/azuredeploy.example.parameters.json b/azure_arc_servers_jumpstart/proxy/azuredeploy.example.parameters.json index 796eaebac1..76e618d517 100644 --- a/azure_arc_servers_jumpstart/proxy/azuredeploy.example.parameters.json +++ b/azure_arc_servers_jumpstart/proxy/azuredeploy.example.parameters.json @@ -15,7 +15,7 @@ "value": "arcdemo" }, "adminPassword": { - "value": "ArcPassword123!!" + "value": "ArcPassword123" }, "dnsLabelPrefix": { "value": "arcproxyclient123" 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..cb91599c92 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 @@ -7,8 +7,7 @@ $Env:ToolsDir = "C:\Tools" $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" +$sourceFolder = "https://jumpstartprodsg.blob.core.windows.net/scenarios/prod" $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..43a45fdc87 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 @@ -402,7 +402,7 @@ $maxRetries = 5 $aioStatus = "notDeployed" do { - az iot ops init --cluster $arcClusterName -g $resourceGroup --kv-id $keyVaultId --sp-app-id $spnClientID --sp-secret $spnClientSecret --mq-service-type loadBalancer --mq-insecure true --simulate-plc true --only-show-errors + az iot ops init --cluster $arcClusterName -g $resourceGroup --kv-id $keyVaultId --sp-app-id $spnClientID --sp-secret $spnClientSecret --broker-service-type loadBalancer --add-insecure-listener true --simulate-plc true --only-show-errors if ($? -eq $false) { $aioStatus = "notDeployed" Write-Host "`n" @@ -414,6 +414,7 @@ do { } } until ($aioStatus -eq "deployed" -or $retryCount -eq $maxRetries) +<# $retryCount = 0 $maxRetries = 5 @@ -422,7 +423,7 @@ do { $output = $output | ConvertFrom-Json $mqServiceStatus = ($output.postDeployment | Where-Object { $_.name -eq "evalBrokerListeners" }).status if ($mqServiceStatus -ne "Success") { - az iot ops init --cluster $arcClusterName -g $resourceGroup --kv-id $keyVaultId --sp-app-id $spnClientID --sp-object-id $spnObjectId --sp-secret $spnClientSecret --mq-service-type loadBalancer --mq-insecure true --simulate-plc true --only-show-errors + az iot ops init --cluster $arcClusterName -g $resourceGroup --kv-id $keyVaultId --sp-app-id $spnClientID --sp-object-id $spnObjectId --sp-secret $spnClientSecret --broker-service-type loadBalancer --add-insecure-listener true --simulate-plc true --only-show-errors $retryCount++ } } until ($mqServiceStatus -eq "Success" -or $retryCount -eq $maxRetries) @@ -431,20 +432,23 @@ if ($retryCount -eq $maxRetries) { Write-Host "[$(Get-Date -Format t)] ERROR: AIO deployment failed. Exiting..." -ForegroundColor White -BackgroundColor Red exit 1 # Exit the script } +#> Write-Host "[$(Get-Date -Format t)] INFO: Started Event Grid role assignment process" -ForegroundColor DarkGray -$extensionPrincipalId = (az k8s-extension show --cluster-name $arcClusterName --name "mq" --resource-group $resourceGroup --cluster-type "connectedClusters" --output json | ConvertFrom-Json).identity.principalId +$extensionPrincipalId = (az k8s-extension list --cluster-name $arcClusterName --resource-group $resourceGroup --cluster-type "connectedClusters" --query "[?extensionType=='microsoft.iotoperations']" --output json | ConvertFrom-Json).identity.principalId $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..1b66e752b9 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 @@ -1,34 +1,40 @@ -apiVersion: mq.iotoperations.azure.com/v1beta1 -kind: MqttBridgeTopicMap +apiVersion: connectivity.iotoperations.azure.com/v1beta1 +kind: DataflowEndpoint +metadata: + name: eventgrid +spec: + endpointType: mqtt + authentication: + method: systemAssignedManagedIdentity + systemAssignedManagedIdentitySettings: + audience: https://eventgrid.azure.net + mqttSettings: + host: eventGridPlaceholder:8883 + tls: + mode: Enabled +--- +apiVersion: connectivity.iotoperations.azure.com/v1beta1 +kind: Dataflow metadata: name: my-topic-map - namespace: azure-iot-operations spec: - mqttBridgeConnectorRef: my-mqtt-bridge - routes: - - direction: local-to-remote - name: route-to-eventgrid - qos: 1 - source: "topic/#" + profileRef: my-dataflow-profile + operations: + - operationType: source + name: source1 + sourceSettings: + endpointRef: mq + dataSources: + - "topic/#" + - operationType: destination + name: destination1 + destinationSettings: + endpointRef: eventgrid + dataDestination: factory-gateway- --- -apiVersion: mq.iotoperations.azure.com/v1beta1 -kind: MqttBridgeConnector +apiVersion: connectivity.iotoperations.azure.com/v1beta1 +kind: DataflowProfile metadata: - name: my-mqtt-bridge - namespace: azure-iot-operations + name: my-dataflow-profile spec: - image: - repository: e4kpreview.azurecr.io/mqttbridge - tag: 0.1.0-preview-rc6 - pullPolicy: IfNotPresent - protocol: v5 - bridgeInstances: 1 - clientIdPrefix: factory-gateway- - logLevel: debug - remoteBrokerConnection: - endpoint: eventGridPlaceholder:8883 - tls: - tlsEnabled: true - authentication: - systemAssignedManagedIdentity: - audience: https://eventgrid.azure.net + instanceCount: 1 \ No newline at end of file diff --git a/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/main.azd.bicep b/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/main.azd.bicep index 7dad4338c8..0e1b7f825c 100644 --- a/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/main.azd.bicep +++ b/azure_edge_iot_ops_jumpstart/aio_manufacturing/bicep/main.azd.bicep @@ -31,19 +31,19 @@ param bastionHostName string = 'AIO-Demo-Bastion' param vmSize string = 'Standard_D8s_v3' @description('Unique SPN app ID') -param spnClientId string +param spnClientId string = '' @description('Unique SPN object ID') -param spnObjectId string +param spnObjectId string = '' @description('Unique SPN password') @minLength(12) @maxLength(123) @secure() -param spnClientSecret string +param spnClientSecret string = newGuid() @description('Unique SPN tenant ID') -param spnTenantId string +param spnTenantId string = '' @description('Azure subscription ID') param subscriptionId string = subscription().subscriptionId @@ -93,7 +93,7 @@ param eventGridNamespaceName string = 'aioeventgridns${namingGuid}' param adxClusterName string = 'aioadx${namingGuid}' @description('The custom location RPO ID') -param customLocationRPOID string +param customLocationRPOID string = '' @description('The name of the Azure Key Vault') param akvName string = 'aioakv${namingGuid}' 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/artifacts/L1Files/ScalableCluster.json b/azure_jumpstart_ag/artifacts/L1Files/ScalableCluster.json index b9c6cdf14b..d85a6831d3 100644 --- a/azure_jumpstart_ag/artifacts/L1Files/ScalableCluster.json +++ b/azure_jumpstart_ag/artifacts/L1Files/ScalableCluster.json @@ -4,7 +4,7 @@ "DeploymentType": "ScalableCluster", "Init": { "ServiceIPRangeStart": "ServiceIPRangeStart-null", - "ServiceIPRangeSize": 1000 + "ServiceIPRangeSize": 15 }, "Arc": { "ClusterName": "ClusterName-null", @@ -37,9 +37,10 @@ "Mtu": 0 }, "LinuxNode": { - "CpuCount": 4, + "CpuCount": 6, "MemoryInMB": 24576, "DataSizeInGB": 80, + "LogSizeInGB": 5, "Ip4Address": "Ip4Address-null", "TimeoutSeconds": 600, "TpmPassthrough": false diff --git a/azure_jumpstart_ag/artifacts/L1Files/config.json b/azure_jumpstart_ag/artifacts/L1Files/config.json new file mode 100644 index 0000000000..3f9a6633d6 --- /dev/null +++ b/azure_jumpstart_ag/artifacts/L1Files/config.json @@ -0,0 +1,6 @@ +{ + "hydra.highAvailability.disk.storageClass": "default", + "hydra.acstorController.enabled": false, + "hydra.highAvailability.disk.storageClass": "local-path", + "hydra.cachedStorageSize": "20Gi" +} \ No newline at end of file diff --git a/azure_jumpstart_ag/artifacts/PowerShell/AgConfig-manufacturing.psd1 b/azure_jumpstart_ag/artifacts/PowerShell/AgConfig-manufacturing.psd1 new file mode 100644 index 0000000000..595265ebb8 --- /dev/null +++ b/azure_jumpstart_ag/artifacts/PowerShell/AgConfig-manufacturing.psd1 @@ -0,0 +1,232 @@ +@{ + # This is the PowerShell datafile used to provide configuration information for the Agora environment. Product keys and password are not encrypted and will be available on host during installation. + + # Directory paths + AgDirectories = @{ + AgDir = "C:\Ag" + AgPowerShellDir = "C:\Ag\PowerShell" + AgLogsDir = "C:\Ag\Logs" + AgVMDir = "C:\Ag\Virtual Machines" + AgIconDir = "C:\Ag\Icons" + AgToolsDir = "C:\Tools" + AgTempDir = "C:\Temp" + AgVHDXDir = "V:\VMs" + AgConfigMapDir = "C:\Ag\ConfigMaps" + AgL1Files = "C:\Ag\L1Files" + AgAppsRepo = "C:\Ag\AppsRepo" + AgMonitoringDir = "C:\Ag\Monitoring" + AgAdxDashboards = "C:\Ag\AdxDashboards" + AgDataEmulator = "C:\Ag\DataEmulator" + } + + # Required URLs + URLs = @{ + chocoInstallScript = 'https://chocolatey.org/install.ps1' + wslUbuntu = 'https://aka.ms/wslubuntu' + wslStoreStorage = 'https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi' + docker = 'https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe' + githubAPI = 'https://api.github.com' + grafana = 'https://api.github.com/repos/grafana/grafana/releases/latest' + azurePortal = 'https://portal.azure.com' + aksEEk3s = 'https://aka.ms/aks-edge/k3s-msi' + nginx = 'https://kubernetes.github.io/ingress-nginx' + prometheus = 'https://prometheus-community.github.io/helm-charts' + vcLibs = 'https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx' + windowsTerminal = 'https://api.github.com/repos/microsoft/terminal/releases/latest' + aksEEReleases = 'https://api.github.com/repos/Azure/AKS-Edge/releases' + mqttExplorerReleases = 'https://api.github.com/repos/thomasnordquist/MQTT-Explorer/releases/latest' + } + + # Azure required registered resource providers + AzureProviders = @( + "Microsoft.Kubernetes", + "Microsoft.KubernetesConfiguration", + "Microsoft.ExtendedLocation", + "Microsoft.HybridCompute", + "Microsoft.GuestConfiguration", + "Microsoft.HybridConnectivity", + "Microsoft.DeviceRegistry", + "Microsoft.EventGrid" + ) + + # Az CLI required extensions + AzCLIExtensions = @( + 'k8s-extension', + 'k8s-configuration', + 'eventgrid', + 'customlocation', + 'kusto', + 'storage-preview' + 'azure-iot-ops' + ) + + # PowerShell modules + PowerShellModules = @( + 'Az.ConnectedKubernetes', + 'Az.KubernetesConfiguration', + 'Az.Kusto', + 'Az.EventGrid', + 'Az.Storage', + 'Az.EventHub' + ) + + # Chocolatey packages list + ChocolateyPackagesList = @( + 'az.powershell', + 'bicep', + 'kubernetes-cli', + 'vcredist140', + 'microsoft-edge', + 'azcopy10', + 'vscode', + 'git', + '7zip', + 'kubectx', + 'putty.install', + 'kubernetes-helm', + 'dotnet-sdk', + 'zoomit', + 'openssl.light', + 'mqtt-explorer', + 'gh', + 'python' + ) + + # Pip packages list + PipPackagesList = @( + 'paho-mqtt' + ) + + # VSCode extensions + VSCodeExtensions = @( + 'ms-vscode-remote.remote-containers', + 'ms-vscode-remote.remote-wsl', + 'ms-vscode.powershell', + 'redhat.vscode-yaml', + 'ZainChen.json', + 'esbenp.prettier-vscode', + 'ms-kubernetes-tools.vscode-kubernetes-tools', + 'mindaro.mindaro', + 'github.vscode-pull-request-github' + ) + + # Git branches + GitBranches = @( + 'production', + 'staging', + 'canary' , + 'main' + ) + + # VHDX blob url + ProdVHDBlobURL = 'https://jumpstartprodsg.blob.core.windows.net/agora/base/prod-w11iot/AGBase.vhdx' + PreProdVHDBlobURL = 'https://jumpstartprodsg.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 + L1VMMemory = 32GB # This value controls the amount of RAM for each AKS Edge Essentials host virtual machine + L1VMNumVCPU = 8 # This value controls the number of vCPUs to assign to each AKS Edge Essentials host virtual machine. + InternalSwitch = "InternalSwitch" # This value controls the Hyper-V internal switch name used by L0 Azure virtual machine. + L1Username = "Administrator" # This value controls the Admin credential username for the L1 Hyper-V virtual machines that run on the Agora-Client. + L1Password = 'Agora123!!' # This value controls the Admin credential password for the L1 Hyper-V virtual machines that run on the Agora-Client. + L1DefaultGateway = "172.20.1.1" # This value controls the default gateway IP address used by each L1 Hyper-V virtual machines that run on the Agora-Client. + L1SwitchName = "AKS-Int" # This value controls the Hyper-V internal switch name used by each L1 Hyper-V virtual machines that run on the Agora-Client. + L1NatSubnetPrefix = "172.20.1.0/24" # This value controls the network subnet used by each L1 Hyper-V virtual machines that run on the Agora-Client. + + # NAT Configuration + natHostSubnet = "192.168.128.0/24" + natHostVMSwitchName = "InternalNAT" + natConfigure = $true + natSubnet = "192.168.46.0/24" # This value is the subnet is the NAT router will use to route to AzSMGMT to access the Internet. It can be any /24 subnet and is only used for routing. + natDNS = "%staging-natDNS%" # Do not change - can be configured by passing the optional natDNS parameter to the ARM deployment. + + # AKS Edge Essentials variables + SiteConfig = @{ + Detroit = @{ + ArcClusterName = "Ag-ArcK8s-Detroit" + NetIPAddress = "172.20.1.2" + DefaultGateway = "172.20.1.1" + PrefixLength = "24" + DNSClientServerAddress = "168.63.129.16" + ServiceIPRangeStart = "172.20.1.31" + ServiceIPRangeSize = "10" + ControlPlaneEndpointIp = "172.20.1.21" + LinuxNodeIp4Address = "172.20.1.11" + Subnet = "172.20.1.0/24" + FriendlyName = "Detroit" + IsProduction = $true + Type = "AKSEE" + Branch = "main" + HelmSetValue = "alertmanager.enabled=false,grafana.enabled=false,prometheus.service.type=LoadBalancer" + HelmService = "service/prometheus-kube-prometheus-prometheus" + GrafanaDataSource = "detroit" + HelmValuesFile = "prometheus-additional-scrape-config.yaml" + clusterLogSize = "1024" + AKSEEReleaseUseLatest = $true # If set to true, the latest AKSEE release will be used. If set to false, the n-1 version will be used + } + Monterrey = @{ + ArcClusterName = "Ag-ArcK8s-Monterrey" + NetIPAddress = "172.20.1.3" + DefaultGateway = "172.20.1.1" + PrefixLength = "24" + DNSClientServerAddress = "168.63.129.16" + ServiceIPRangeStart = "172.20.1.71" + ServiceIPRangeSize = "10" + ControlPlaneEndpointIp = "172.20.1.61" + LinuxNodeIp4Address = "172.20.1.51" + Subnet = "172.20.1.0/24" + FriendlyName = "Monterrey" + IsProduction = $true + Type = "AKSEE" + Branch = "main" + HelmSetValue = "alertmanager.enabled=false,grafana.enabled=false,prometheus.service.type=LoadBalancer" + HelmService = "service/prometheus-kube-prometheus-prometheus" + GrafanaDataSource = "monterrey" + HelmValuesFile = "prometheus-additional-scrape-config.yaml" + clusterLogSize = "1024" + AKSEEReleaseUseLatest = $true # If set to true, the latest AKSEE release will be used. If set to false, the n-1 version will be used + } + } + + # Universal resource tag and resource types + TagName = 'Project' + TagValue = 'Jumpstart_Agora' + ArcServerResourceType = 'Microsoft.HybridCompute/machines' + ArcK8sResourceType = 'Microsoft.Kubernetes/connectedClusters' + AksResourceType = 'Microsoft.ContainerService/managedClusters' + + + # Observability variables + Monitoring = @{ + AdminUser = "admin" + User = "Contoso Operator" + Email = "operator@contoso.com" + Namespace = "observability" + ProdURL = "http://localhost:3000" + Dashboards = @{ + "grafana.com" = @() # Dashboards from https://grafana.com/grafana/dashboards + "custom" = @('node-exporter-full','cluster-global') # Dashboards from https://github.com/microsoft/azure_arc/tree/main/azure_jumpstart_ag/artifacts/monitoring + } + } + + Namespaces = @( + "observability" + "images-cache" + ) + + AppConfig = @{ + inferencing_deployment = @{ + GitOpsConfigName = "contoso-motors" + KustomizationName = "contoso-motors" + KustomizationPath="./contoso_manufacturing/operations" + Namespace = "contoso-motors" + Order = 1 + } + } + + # Microsoft Edge startup settings variables + EdgeSettingRegistryPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Edge' + EdgeSettingValueTrue = '00000001' + EdgeSettingValueFalse = '00000000' + +} \ No newline at end of file diff --git a/azure_jumpstart_ag/artifacts/PowerShell/AgConfig.psd1 b/azure_jumpstart_ag/artifacts/PowerShell/AgConfig-retail.psd1 similarity index 96% rename from azure_jumpstart_ag/artifacts/PowerShell/AgConfig.psd1 rename to azure_jumpstart_ag/artifacts/PowerShell/AgConfig-retail.psd1 index 666d3847f3..e877738342 100644 --- a/azure_jumpstart_ag/artifacts/PowerShell/AgConfig.psd1 +++ b/azure_jumpstart_ag/artifacts/PowerShell/AgConfig-retail.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://jumpstartprodsg.blob.core.windows.net/agora/base/prod-w11iot/AGBase.vhdx' + PreProdVHDBlobURL = 'https://jumpstartprodsg.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 @@ -231,7 +231,7 @@ ProdURL = "http://localhost:3000" Dashboards = @{ "grafana.com" = @() # Dashboards from https://grafana.com/grafana/dashboards - "custom" = @('freezer-monitoring','node-exporter-full','cluster-global') # Dashboards from https://github.com/microsoft/azure_arc/tree/main/azure_jumpstart_ag/artifacts/monitoring + "custom" = @('freezer-monitoring','node-exporter-full','cluster-global') # Dashboards from https://github.com/microsoft/azure_arc/tree/main/azure_jumpstart_ag/retail/artifacts/monitoring } } diff --git a/azure_jumpstart_ag/artifacts/PowerShell/AgLogonScript.ps1 b/azure_jumpstart_ag/artifacts/PowerShell/AgLogonScript.ps1 index 1e29e49963..8bd4dbc2b7 100644 --- a/azure_jumpstart_ag/artifacts/PowerShell/AgLogonScript.ps1 +++ b/azure_jumpstart_ag/artifacts/PowerShell/AgLogonScript.ps1 @@ -6,34 +6,57 @@ Set-PSDebug -Strict ##################################################################### # Initialize the environment ##################################################################### -$AgConfig = Import-PowerShellDataFile -Path $Env:AgConfigPath -$AgToolsDir = $AgConfig.AgDirectories["AgToolsDir"] -$AgIconsDir = $AgConfig.AgDirectories["AgIconDir"] -$AgAppsRepo = $AgConfig.AgDirectories["AgAppsRepo"] -$configMapDir = $agConfig.AgDirectories["AgConfigMapDir"] -$websiteUrls = $AgConfig.URLs -$githubAccount = $Env:githubAccount -$githubBranch = $Env:githubBranch -$githubUser = $Env:githubUser -$githubPat = $Env:GITHUB_TOKEN -$resourceGroup = $Env:resourceGroup -$azureLocation = $Env:azureLocation -$spnClientId = $Env:spnClientId -$spnClientSecret = $Env:spnClientSecret -$spnTenantId = $Env:spnTenantId -$adminUsername = $Env:adminUsername -$acrName = $Env:acrName.ToLower() -$cosmosDBName = $Env:cosmosDBName -$cosmosDBEndpoint = $Env:cosmosDBEndpoint -$templateBaseUrl = $Env:templateBaseUrl -$appClonedRepo = "https://github.com/$githubUser/jumpstart-agora-apps" -$appUpstreamRepo = "https://github.com/microsoft/jumpstart-agora-apps" -$adxClusterName = $Env:adxClusterName -$namingGuid = $Env:namingGuid -$appsRepo = "jumpstart-agora-apps" -$adminPassword = $Env:adminPassword -$gitHubAPIBaseUri = $websiteUrls["githubAPI"] -$workflowStatus = "" +$global:AgConfig = Import-PowerShellDataFile -Path $Env:AgConfigPath +$global:AgToolsDir = $AgConfig.AgDirectories["AgToolsDir"] +$global:AgIconsDir = $AgConfig.AgDirectories["AgIconDir"] +$global:AgAppsRepo = $AgConfig.AgDirectories["AgAppsRepo"] +$global:configMapDir = $agConfig.AgDirectories["AgConfigMapDir"] +$global:AgDeploymentFolder = $AgConfig.AgDirectories["AgL1Files"] +$global:AgPowerShellDir = $AgConfig.AgDirectories["AgPowerShellDir"] +$global:industry = $Env:industry +$global:websiteUrls = $AgConfig.URLs +$global:githubAccount = $Env:githubAccount +$global:githubBranch = $Env:githubBranch +$global:resourceGroup = $Env:resourceGroup +$global:azureLocation = $Env:azureLocation +$global:spnClientId = $Env:spnClientId +$global:spnClientSecret = $Env:spnClientSecret +$global:spnTenantId = $Env:spnTenantId +$global:subscriptionId = $Env:subscriptionId +$global:adminUsername = $Env:adminUsername +$global:templateBaseUrl = $Env:templateBaseUrl +$global:adxClusterName = $Env:adxClusterName +$global:namingGuid = $Env:namingGuid +$global:adminPassword = $Env:adminPassword +$global:customLocationRPOID = $Env:customLocationRPOID +$global:appUpstreamRepo = "https://github.com/microsoft/jumpstart-agora-apps" +$global:appsRepo = "jumpstart-agora-apps" +$global:AKSEEPinnedSchemaVersion = $Env:AKSEEPinnedSchemaVersion +if ($industry -eq "retail") { + $global:githubUser = $Env:githubUser + $global:githubPat = $Env:GITHUB_TOKEN + $global:acrName = $Env:acrName.ToLower() + $global:cosmosDBName = $Env:cosmosDBName + $global:cosmosDBEndpoint = $Env:cosmosDBEndpoint + $global:gitHubAPIBaseUri = $websiteUrls["githubAPI"] + $global:workflowStatus = "" + $global:appClonedRepo = "https://github.com/$githubUser/jumpstart-agora-apps" +}elseif ($industry -eq "manufacturing") { + $global:aioNamespace = "azure-iot-operations" + $global:mqListenerService = "aio-mq-dmqtt-frontend" + $global:mqttExplorerReleasesUrl = $websiteUrls["mqttExplorerReleases"] + $global:stagingStorageAccountName = $Env:stagingStorageAccountName + $global:aioStorageAccountName = $Env:aioStorageAccountName + $global:spnObjectId = $Env:spnObjectId + $global:stcontainerName = $Env:stcontainerName +} + +##################################################################### +# Importing fuctions +##################################################################### +Import-Module "$AgPowerShellDir\common.psm1" -Force -DisableNameChecking +Import-Module "$AgPowerShellDir\retail.psm1" -Force -DisableNameChecking +Import-Module "$AgPowerShellDir\manufacturing.psm1" -Force -DisableNameChecking Start-Transcript -Path ($AgConfig.AgDirectories["AgLogsDir"] + "\AgLogonScript.log") Write-Header "Executing Jumpstart Agora automation scripts" @@ -45,1770 +68,149 @@ Set-NetFirewallProfile -Profile Domain, Public, Private -Enabled False # Force TLS 1.2 for connections to prevent TLS/SSL errors [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$global:password = ConvertTo-SecureString $AgConfig.L1Password -AsPlainText -Force +$global:Credentials = New-Object System.Management.Automation.PSCredential($AgConfig.L1Username, $password) ##################################################################### # Setup Azure CLI ##################################################################### Write-Host "[$(Get-Date -Format t)] INFO: Configuring Azure CLI (Step 1/17)" -ForegroundColor DarkGreen -$cliDir = New-Item -Path ($AgConfig.AgDirectories["AgLogsDir"] + "\.cli\") -Name ".Ag" -ItemType Directory - -if (-not $($cliDir.Parent.Attributes.HasFlag([System.IO.FileAttributes]::Hidden))) { - $folder = Get-Item $cliDir.Parent.FullName -ErrorAction SilentlyContinue - $folder.Attributes += [System.IO.FileAttributes]::Hidden -} - -$Env:AZURE_CONFIG_DIR = $cliDir.FullName - Write-Host "[$(Get-Date -Format t)] INFO: Logging into Az CLI using the service principal and secret provided at deployment" -ForegroundColor Gray az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\AzCLI.log") - -# Making extension install dynamic -if ($AgConfig.AzCLIExtensions.Count -ne 0) { - Write-Host "[$(Get-Date -Format t)] INFO: Installing Azure CLI extensions: " ($AgConfig.AzCLIExtensions -join ', ') -ForegroundColor Gray - az config set extension.use_dynamic_install=yes_without_prompt --only-show-errors - # Installing Azure CLI extensions - foreach ($extension in $AgConfig.AzCLIExtensions) { - az extension add --name $extension --system --only-show-errors - } -} - -Write-Host "[$(Get-Date -Format t)] INFO: Az CLI configuration complete!" -ForegroundColor Green -Write-Host +az account set -s $subscriptionId +Deploy-AzCLI ##################################################################### # Setup Azure PowerShell and register providers ##################################################################### Write-Host "[$(Get-Date -Format t)] INFO: Configuring Azure PowerShell (Step 2/17)" -ForegroundColor DarkGreen -$azurePassword = ConvertTo-SecureString $Env:spnClientSecret -AsPlainText -Force -$psCred = New-Object System.Management.Automation.PSCredential($Env:spnClientID , $azurePassword) -Connect-AzAccount -Credential $psCred -TenantId $Env:spnTenantId -ServicePrincipal | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\AzPowerShell.log") -$subscriptionId = (Get-AzSubscription).Id - -# Install PowerShell modules -if ($AgConfig.PowerShellModules.Count -ne 0) { - Write-Host "[$(Get-Date -Format t)] INFO: Installing PowerShell modules: " ($AgConfig.PowerShellModules -join ', ') -ForegroundColor Gray - foreach ($module in $AgConfig.PowerShellModules) { - Install-Module -Name $module -Force | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\AzPowerShell.log") - } -} - -# Register Azure providers -if ($AgConfig.AzureProviders.Count -ne 0) { - Write-Host "[$(Get-Date -Format t)] INFO: Registering Azure providers in the current subscription: " ($AgConfig.AzureProviders -join ', ') -ForegroundColor Gray - foreach ($provider in $AgConfig.AzureProviders) { - Register-AzResourceProvider -ProviderNamespace $provider | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\AzPowerShell.log") - } -} -Write-Host "[$(Get-Date -Format t)] INFO: Azure PowerShell configuration and resource provider registration complete!" -ForegroundColor Green -Write-Host +Deploy-AzPowerShell ############################################################# # Install Windows Terminal, WSL2, and Ubuntu ############################################################# Write-Host "[$(Get-Date -Format t)] INFO: Installing dev tools (Step 3/17)" -ForegroundColor DarkGreen +Deploy-WindowsTools -$DevToolsInstallationJob = Invoke-Command -ScriptBlock { - -$AgConfig = $using:AgConfig -$websiteUrls = $using:websiteUrls -$AgToolsDir = $using:AgToolsDir -$adminUsername = $using:adminUsername - - -If ($PSVersionTable.PSVersion.Major -ge 7) { Write-Error "This script needs be run by version of PowerShell prior to 7.0" } -$downloadDir = "C:\WinTerminal" -$frameworkPkgPath = "$downloadDir\Microsoft.VCLibs.x64.14.00.Desktop.appx" -$WindowsTerminalKitPath = "$downloadDir\Microsoft.WindowsTerminal.PreinstallKit.zip" -$windowsTerminalPath = "$downloadDir\WindowsTerminal" -$filenamePattern = "*PreinstallKit.zip" -$terminalDownloadUri = ((Invoke-RestMethod -Method GET -Uri $websiteUrls["windowsTerminal"]).assets | Where-Object name -like $filenamePattern ).browser_download_url | Select-Object -First 1 - -# Download C++ Runtime framework packages for Desktop Bridge and Windows Terminal latest release -Write-Host "[$(Get-Date -Format t)] INFO: Downloading binaries." -ForegroundColor Gray - -$ProgressPreference = 'SilentlyContinue' - -Invoke-WebRequest -Uri $websiteUrls["vcLibs"] -OutFile ( New-Item -Path $frameworkPkgPath -Force ) | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") -Invoke-WebRequest -Uri $terminalDownloadUri -OutFile ( New-Item -Path $windowsTerminalKitPath -Force ) | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") - -$ProgressPreference = 'Continue' - -# Extract Windows Terminal PreinstallKit -Write-Host "[$(Get-Date -Format t)] INFO: Expanding Windows Terminal PreinstallKit." -ForegroundColor Gray -Expand-Archive $WindowsTerminalKitPath $windowsTerminalPath | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") - -# Install WSL latest kernel update -Write-Host "[$(Get-Date -Format t)] INFO: Installing WSL." -ForegroundColor Gray -msiexec /i "$AgToolsDir\wsl_update_x64.msi" /qn | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") - -# Install C++ Runtime framework packages for Desktop Bridge and Windows Terminal latest release -Write-Host "[$(Get-Date -Format t)] INFO: Installing Windows Terminal" -ForegroundColor Gray -Add-AppxPackage -Path $frameworkPkgPath | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") - -# Install the Windows Terminal prereqs -foreach ($file in Get-ChildItem $windowsTerminalPath -Filter *x64*.appx) { - Add-AppxPackage -Path $file.FullName | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") -} - -# Install Windows Terminal -foreach ($file in Get-ChildItem $windowsTerminalPath -Filter *.msixbundle) { - Add-AppxPackage -Path $file.FullName | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") -} - -# Configure Windows Terminal -Set-Location $Env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminal*\LocalState - -# Launch Windows Terminal for default settings.json to be created -$action = New-ScheduledTaskAction -Execute $((Get-Command wt.exe).Source) -$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddSeconds(1) -$null = Register-ScheduledTask -Action $action -Trigger $trigger -TaskName WindowsTerminalInit - -# Give process time to initiate and create settings file -Start-Sleep 10 - -# Stop Windows Terminal process -Get-Process WindowsTerminal | Stop-Process - -Unregister-ScheduledTask -TaskName WindowsTerminalInit -Confirm:$false - -$settings = Get-Content .\settings.json | ConvertFrom-Json -$settings.profiles.defaults.elevate - -# Configure the default profile setting "Run this profile as Administrator" to "true" -$settings.profiles.defaults | Add-Member -Name elevate -MemberType NoteProperty -Value $true -Force - -$settings | ConvertTo-Json -Depth 8 | Set-Content .\settings.json - -# Install Ubuntu -Write-Host "[$(Get-Date -Format t)] INFO: Installing Ubuntu" -ForegroundColor Gray -Add-AppxPackage -Path "$AgToolsDir\Ubuntu.appx" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") - -# Setting WSL environment variables -$userenv = [System.Environment]::GetEnvironmentVariable("Path", "User") -[System.Environment]::SetEnvironmentVariable("PATH", $userenv + ";C:\Users\$adminUsername\Ubuntu", "User") - -# Initializing the wsl ubuntu app without requiring user input -$ubuntu_path = "c:/users/$adminUsername/AppData/Local/Microsoft/WindowsApps/ubuntu" -Invoke-Expression -Command "$ubuntu_path install --root" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") - -# Create Windows Terminal shortcut -$WshShell = New-Object -comObject WScript.Shell -$WinTerminalPath = (Get-ChildItem "C:\Program Files\WindowsApps" -Recurse | Where-Object { $_.name -eq "wt.exe" }).FullName -$Shortcut = $WshShell.CreateShortcut("$Env:USERPROFILE\Desktop\Windows Terminal.lnk") -$Shortcut.TargetPath = $WinTerminalPath -$shortcut.WindowStyle = 3 -$shortcut.Save() - -############################################################# -# Install VSCode extensions -############################################################# -Write-Host "[$(Get-Date -Format t)] INFO: Installing VSCode extensions: " + ($AgConfig.VSCodeExtensions -join ', ') -ForegroundColor Gray -# Install VSCode extensions -foreach ($extension in $AgConfig.VSCodeExtensions) { - code --install-extension $extension 2>&1 | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") -} - -############################################################# -# Install Docker Desktop -############################################################# -Write-Host "[$(Get-Date -Format t)] INFO: Installing Docker Desktop." -ForegroundColor DarkGreen -# Download and Install Docker Desktop -$arguments = 'install --quiet --accept-license' -Start-Process "$AgToolsDir\DockerDesktopInstaller.exe" -Wait -ArgumentList $arguments -Get-ChildItem "$Env:USERPROFILE\Desktop\Docker Desktop.lnk" | Remove-Item -Confirm:$false -Copy-Item "$AgToolsDir\settings.json" -Destination "$Env:USERPROFILE\AppData\Roaming\Docker\settings.json" -Force -Start-Process "C:\Program Files\Docker\Docker\Docker Desktop.exe" -Start-Sleep -Seconds 15 -Get-Process | Where-Object { $_.name -like "Docker Desktop" } | Stop-Process -Force -# Cleanup -Remove-Item $downloadDir -Recurse -Force - -} -JobName step3 -ThrottleLimit 16 -AsJob -ComputerName . - -Write-Host "[$(Get-Date -Format t)] INFO: Dev Tools installation initiated in background job." -ForegroundColor Green - -$DevToolsInstallationJob - -Write-Host ##################################################################### # Configure Jumpstart Agora Apps repository ##################################################################### -Write-Host "INFO: Forking and preparing Apps repository locally (Step 4/17)" -ForegroundColor DarkGreen -Set-Location $AgAppsRepo -Write-Host "INFO: Checking if the $appsRepo repository is forked" -ForegroundColor Gray -$retryCount = 0 -$maxRetries = 5 -do { - $forkExists = $false - try { - $response = Invoke-RestMethod -Uri "$gitHubAPIBaseUri/repos/$githubUser/$appsRepo" - if ($response) { - write-host "INFO: Fork exists....Proceeding" -ForegroundColor Gray - $forkExists = $true - } - } - catch { - if ($retryCount -lt $maxRetries) { - Write-Host "ERROR: $githubUser/$appsRepo Fork doesn't exist, please fork https://github.com/microsoft/jumpstart-agora-apps to proceed (attempt $retryCount/$maxRetries) . . . waiting 60 seconds" -ForegroundColor Red - $retryCount++ - $forkExists = $false - start-sleep -Seconds 60 - } - else { - Write-Host "[$(Get-Date -Format t)] ERROR: Retry limit reached, $githubUser/$appsRepo Fork doesn't exist. Exiting." -ForegroundColor Red - exit - } - } -} until ($forkExists -eq $true) - -Write-Host "INFO: Checking if the GitHub access token is valid." -ForegroundColor Gray -do { - $response = gh auth status 2>&1 - if ($response -match "authentication failed") { - write-host "ERROR: The GitHub Personal access token is not valid" -ForegroundColor Red - Write-Host "INFO: Please try to re-generate the personal access token and provide it here (https://aka.ms/AgoraPreReqs): " - do { - $githubPAT = Read-Host "GitHub personal access token" - } while ($githubPAT -eq "") - } -} until ( - $response -notmatch "authentication failed" -) - -Write-Host "INFO: The GitHub Personal access token is valid. Proceeding." -ForegroundColor DarkGreen -$Env:GITHUB_TOKEN = $githubPAT.Trim() -[System.Environment]::SetEnvironmentVariable('GITHUB_TOKEN', $githubPAT.Trim(), [System.EnvironmentVariableTarget]::Machine) - -Write-Host "INFO: Checking if the personal access token is assigned on the $githubUser/$appsRepo Fork" -ForegroundColor Gray -$headers = @{ - Authorization = "token $githubPat" - "Content-Type" = "application/json" +if ($industry -eq "retail") { + Write-Host "INFO: Forking and preparing Apps repository locally (Step 4/17)" -ForegroundColor DarkGreen + SetupRetailRepo } -$retryCount = 0 -$maxRetries = 5 -$uri = "$gitHubAPIBaseUri/repos/$githubUser/$appsRepo/actions/secrets" -do { - try { - $response=Invoke-RestMethod -Uri $uri -Method Get -Headers $headers - Write-Host "INFO: Personal access token is assigned on $githubUser/$appsRepo fork" -ForegroundColor DarkGreen - $PatAssigned = $true - } - catch { - if ($retryCount -lt $maxRetries) { - Write-Host "ERROR: Personal access token is not assigned on $githubUser/$appsRepo fork. Please assign the personal access token to your fork (https://aka.ms/AgoraPreReqs) (attempt $retryCount/$maxRetries).....waiting 60 seconds" -ForegroundColor Red - $PatAssigned = $false - $retryCount++ - start-sleep -Seconds 60 - } - else{ - Write-Host "[$(Get-Date -Format t)] ERROR: Retry limit reached, the personal access token is not assigned to $githubUser/$appsRepo. Exiting." -ForegroundColor Red - exit - } - } -} until ($PatAssigned -eq $true) - - -Write-Host "INFO: Cloning the GitHub repository locally" -ForegroundColor Gray -git clone "https://$githubPat@github.com/$githubUser/$appsRepo.git" "$AgAppsRepo\$appsRepo" -Set-Location "$AgAppsRepo\$appsRepo" - -Write-Host "INFO: Verifying 'Administration' permissions" -ForegroundColor Gray -$retryCount = 0 -$maxRetries = 5 - -$body = @{ - required_status_checks = $null - enforce_admins = $false - required_pull_request_reviews = @{ - required_approving_review_count = 0 - } - dismiss_stale_reviews = $true - restrictions = $null -} | ConvertTo-Json - -do { - try { - $response = Invoke-WebRequest -Uri "$gitHubAPIBaseUri/repos/$githubUser/$appsRepo/branches/main/protection" -Method Put -Headers $headers -Body $body -ContentType "application/json" - } - catch { - if ($retryCount -lt $maxRetries) { - Write-Host "ERROR: The GitHub Personal access token doesn't seem to have 'Administration' write permissions, please assign the right permissions (https://aka.ms/AgoraPreReqs) (attempt $retryCount/$maxRetries)...waiting 60 seconds" -ForegroundColor Red - $retryCount++ - start-sleep -Seconds 60 - } - else { - Write-Host "[$(Get-Date -Format t)] ERROR: Retry limit reached, the personal access token doesn't have 'Administration' write permissions assigned. Exiting." -ForegroundColor Red - exit - } - } -} until ($response) -Write-Host "INFO: 'Administration' write permissions verified" -ForegroundColor DarkGreen - - -Write-Host "INFO: Checking if there are existing branch protection policies" -ForegroundColor Gray -$protectedBranches = Invoke-RestMethod -Uri "$gitHubAPIBaseUri/repos/$githubUser/$appsRepo/branches?protected=true" -Method GET -Headers $headers -foreach ($branch in $protectedBranches) { - $branchName = $branch.name - $deleteProtectionUrl = "$gitHubAPIBaseUri/repos/$githubUser/$appsRepo/branches/$branchName/protection" - Invoke-RestMethod -Uri $deleteProtectionUrl -Headers $headers -Method Delete - Write-Host "INFO: Deleted protection policy for branch: $branchName" -ForegroundColor Gray -} - -Write-Host "INFO: Pulling latests changes to GitHub repository" -ForegroundColor Gray -git config --global user.email "dev@agora.com" -git config --global user.name "Agora Dev" -git remote add upstream "$appUpstreamRepo.git" -git fetch upstream -git checkout main -git reset --hard upstream/main -git push origin main -f -git pull -git remote remove upstream -git remote add upstream "$appClonedRepo.git" - -Write-Host "INFO: Creating GitHub workflows" -ForegroundColor Gray -New-Item -ItemType Directory ".github/workflows" -Force -$githubApiUrl = "$gitHubAPIBaseUri/repos/$githubAccount/azure_arc/contents/azure_jumpstart_ag/artifacts/workflows?ref=$githubBranch" -$response = Invoke-RestMethod -Uri $githubApiUrl -$fileUrls = $response | Where-Object { $_.type -eq "file" } | Select-Object -ExpandProperty download_url -$fileUrls | ForEach-Object { - $fileName = $_.Substring($_.LastIndexOf("/") + 1) - $outputFile = Join-Path "$AgAppsRepo\$appsRepo\.github\workflows" $fileName - Invoke-RestMethod -Uri $_ -OutFile $outputFile -} -git add . -git commit -m "Pushing GitHub Actions to apps fork" -git push -Start-Sleep -Seconds 20 - -Write-Host "INFO: Verifying 'Secrets' permissions" -ForegroundColor Gray -$retryCount = 0 -$maxRetries = 5 -do { - $response = gh secret set "test" -b "test" 2>&1 - if ($response -match "error") { - if ($retryCount -eq $maxRetries) { - Write-Host "[$(Get-Date -Format t)] ERROR: Retry limit reached, the personal access token doesn't have 'Secrets' write permissions assigned. Exiting." -ForegroundColor Red - exit - } - else { - $retryCount++ - write-host "ERROR: The GitHub Personal access token doesn't seem to have 'Secrets' write permissions, please assign the right permissions (https://aka.ms/AgoraPreReqs) (attempt $retryCount/$maxRetries)...waiting 60 seconds" -ForegroundColor Red - Start-Sleep -Seconds 60 - } - } -} while ($response -match "error" -or $retryCount -ge $maxRetries) -gh secret delete test -Write-Host "INFO: 'Secrets' write permissions verified" -ForegroundColor DarkGreen - -Write-Host "INFO: Verifying 'Actions' permissions" -ForegroundColor Gray -$retryCount = 0 -$maxRetries = 5 -do { - $response = gh workflow enable update-files.yml 2>&1 - if ($response -match "failed") { - if ($retryCount -eq $maxRetries) { - Write-Host "[$(Get-Date -Format t)] ERROR: Retry limit reached, the personal access token doesn't have 'Actions' write permissions assigned. Exiting." -ForegroundColor Red - exit - } - else { - $retryCount++ - write-host "ERROR: The GitHub Personal access token doesn't seem to have 'Actions' write permissions, please assign the right permissions (https://aka.ms/AgoraPreReqs) (attempt $retryCount/$maxRetries)...waiting 60 seconds" -ForegroundColor Red - Start-Sleep -Seconds 60 - } - } -} while ($response -match "failed" -or $retryCount -ge $maxRetries) -Write-Host "INFO: 'Actions' write permissions verified" -ForegroundColor DarkGreen - -write-host "INFO: Creating GitHub secrets" -ForegroundColor Gray -Write-Host "INFO: Getting Cosmos DB access key" -ForegroundColor Gray -Write-Host "INFO: Adding GitHub secrets to apps fork" -ForegroundColor Gray -gh api -X PUT "/repos/$githubUser/$appsRepo/actions/permissions/workflow" -F can_approve_pull_request_reviews=true -gh repo set-default "$githubUser/$appsRepo" -gh secret set "SPN_CLIENT_ID" -b $spnClientID -gh secret set "SPN_CLIENT_SECRET" -b $spnClientSecret -gh secret set "ACR_NAME" -b $acrName -gh secret set "PAT_GITHUB" -b $githubPat -gh secret set "COSMOS_DB_ENDPOINT" -b $cosmosDBEndpoint -gh secret set "SPN_TENANT_ID" -b $spnTenantId - -Write-Host "INFO: Updating ACR name and Cosmos DB endpoint in all branches" -ForegroundColor Gray -gh workflow run update-files.yml -while ($workflowStatus.status -ne "completed") { - Write-Host "INFO: Waiting for update-files workflow to complete" -ForegroundColor Gray - Start-Sleep -Seconds 10 - $workflowStatus = (gh run list --workflow=update-files.yml --json status) | ConvertFrom-Json -} -Write-Host "INFO: Starting Contoso supermarket pos application v1.0 image build" -ForegroundColor Gray -gh workflow run pos-app-initial-images-build.yml - -Write-Host "INFO: Creating GitHub branches to $appsRepo fork" -ForegroundColor Gray -$branches = $AgConfig.GitBranches -foreach ($branch in $branches) { - try { - $response = Invoke-RestMethod -Uri "$gitHubAPIBaseUri/repos/$githubUser/$appsRepo/branches/$branch" - if ($response) { - if ($branch -ne "main") { - Write-Host "INFO: branch $branch already exists! Deleting and recreating the branch" -ForegroundColor Gray - git push origin --delete $branch - git branch -d $branch - git fetch origin - git checkout main - git pull origin main - git checkout -b $branch main - git pull origin main - git push --set-upstream origin $branch - } - } - } - catch { - Write-Host "INFO: Creating $branch branch" -ForegroundColor Gray - git fetch origin - git checkout main - git pull origin main - git checkout -b $branch main - git pull origin main - git push --set-upstream origin $branch - } -} -Write-Host "INFO: Cleaning up any other branches" -ForegroundColor Gray -$existingBranches = gh api "repos/$githubUser/$appsRepo/branches" | ConvertFrom-Json -$branches = $AgConfig.GitBranches -foreach ($branch in $existingBranches) { - if ($branches -notcontains $branch.name){ - $branchToDelete = $branch.name - git push origin --delete $branchToDelete - } -} - -Write-Host "INFO: Switching to main branch" -ForegroundColor Gray -git checkout main -Write-Host "INFO: Adding branch protection policies for all branches" -ForegroundColor Gray -foreach ($branch in $branches) { - Write-Host "INFO: Adding branch protection policies for $branch branch" -ForegroundColor Gray - $headers = @{ - "Authorization" = "Bearer $githubPat" - "Accept" = "application/vnd.github+json" - } - $body = @{ - required_status_checks = $null - enforce_admins = $false - required_pull_request_reviews = @{ - required_approving_review_count = 0 - } - dismiss_stale_reviews = $true - restrictions = $null - } | ConvertTo-Json - - Invoke-WebRequest -Uri "$gitHubAPIBaseUri/repos/$githubUser/$appsRepo/branches/$branch/protection" -Method Put -Headers $headers -Body $body -ContentType "application/json" -} -Write-Host "INFO: GitHub repo configuration complete!" -ForegroundColor Green -Write-Host ##################################################################### # Azure IoT Hub resources preparation ##################################################################### -Write-Host "[$(Get-Date -Format t)] INFO: Creating Azure IoT resources (Step 5/17)" -ForegroundColor DarkGreen -if ($githubUser -ne "microsoft") { - $iotHubHostName = $Env:iotHubHostName - $iotHubName = $iotHubHostName.replace(".azure-devices.net", "") - $sites = $AgConfig.SiteConfig.Values - Write-Host "[$(Get-Date -Format t)] INFO: Create an Azure IoT device for each site" -ForegroundColor Gray - foreach ($site in $sites) { - foreach ($device in $site.IoTDevices) { - $deviceId = "$device-$($site.FriendlyName)" - Add-AzIotHubDevice -ResourceGroupName $resourceGroup -IotHubName $iotHubName -DeviceId $deviceId -EdgeEnabled | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\IoT.log") - } - } - Write-Host "[$(Get-Date -Format t)] INFO: Azure IoT Hub configuration complete!" -ForegroundColor Green - Write-Host -} -else { - Write-Host "[$(Get-Date -Format t)] ERROR: You have to fork the jumpstart-agora-apps repository!" -ForegroundColor Red -} - -### BELOW IS AN ALTERNATIVE APPROACH TO IMPORT DASHBOARD USING README INSTRUCTIONS -$adxDashBoardsDir = $AgConfig.AgDirectories["AgAdxDashboards"] -$dataEmulatorDir = $AgConfig.AgDirectories["AgDataEmulator"] -$kustoCluster = Get-AzKustoCluster -ResourceGroupName $resourceGroup -Name $adxClusterName -if ($null -ne $kustoCluster) { - $adxEndPoint = $kustoCluster.Uri - if ($null -ne $adxEndPoint -and $adxEndPoint -ne "") { - $ordersDashboardBody = (Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/adx_dashboards/adx-dashboard-orders-payload.json").Content -replace '{{ADX_CLUSTER_URI}}', $adxEndPoint -replace '{{ADX_CLUSTER_NAME}}', $adxClusterName - Set-Content -Path "$adxDashBoardsDir\adx-dashboard-orders-payload.json" -Value $ordersDashboardBody -Force -ErrorAction Ignore - $iotSensorsDashboardBody = (Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/adx_dashboards/adx-dashboard-iotsensor-payload.json") -replace '{{ADX_CLUSTER_URI}}', $adxEndPoint -replace '{{ADX_CLUSTER_NAME}}', $adxClusterName - Set-Content -Path "$adxDashBoardsDir\adx-dashboard-iotsensor-payload.json" -Value $iotSensorsDashboardBody -Force -ErrorAction Ignore - } - else { - Write-Host "[$(Get-Date -Format t)] ERROR: Unable to find Azure Data Explorer endpoint from the cluster resource in the resource group." - } -} - -# Download DataEmulator.zip into Agora folder and unzip -$emulatorPath = "$dataEmulatorDir\DataEmulator.zip" -Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/data_emulator/DataEmulator.zip" -OutFile $emulatorPath - -# Unzip DataEmulator.zip to copy DataEmulator exe and config file to generate sample data for dashboards -if (Test-Path -Path $emulatorPath) { - Expand-Archive -Path "$emulatorPath" -DestinationPath "$dataEmulatorDir" -ErrorAction SilentlyContinue -Force -} - -# Download products.json and stores.json file to use in Data Emulator -$productsJsonPath = "$dataEmulatorDir\products.json" -Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/data_emulator/products.json" -OutFile $productsJsonPath -if (!(Test-Path -Path $productsJsonPath)) { - Write-Host "Unabled to download products.json file. Please download manually from GitHub into the data_emulator folder." +if ($industry -eq "retail") { + Write-Host "[$(Get-Date -Format t)] INFO: Creating Azure IoT resources (Step 5/17)" -ForegroundColor DarkGreen + Deploy-AzureIoTHub } -$storesJsonPath = "$dataEmulatorDir\stores.json" -Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/data_emulator/stores.json" -OutFile $storesJsonPath -if (!(Test-Path -Path $storesJsonPath)) { - Write-Host "Unabled to download stores.json file. Please download manually from GitHub into the data_emulator folder." -} - -# Download icon file -$iconPath = "$AgIconsDir\emulator.ico" -Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/icons/emulator.ico" -OutFile $iconPath -if (!(Test-Path -Path $iconPath)) { - Write-Host "Unabled to download emulator.ico file. Please download manually from GitHub into the icons folder." -} - -# Create desktop shortcut -$shortcutLocation = "$Env:Public\Desktop\Data Emulator.lnk" -$wScriptShell = New-Object -ComObject WScript.Shell -$shortcut = $wScriptShell.CreateShortcut($shortcutLocation) -$shortcut.TargetPath = "$dataEmulatorDir\DataEmulator.exe" -$shortcut.IconLocation = "$iconPath, 0" -$shortcut.WindowStyle = 7 -$shortcut.Save() - ##################################################################### # Configure L1 virtualization infrastructure ##################################################################### Write-Host "[$(Get-Date -Format t)] INFO: Configuring L1 virtualization infrastructure (Step 6/17)" -ForegroundColor DarkGreen -$password = ConvertTo-SecureString $AgConfig.L1Password -AsPlainText -Force -$Credentials = New-Object System.Management.Automation.PSCredential($AgConfig.L1Username, $password) - -# Turn the .kube folder to a shared folder where all Kubernetes kubeconfig files will be copied to -$kubeFolder = "$Env:USERPROFILE\.kube" -New-Item -ItemType Directory $kubeFolder -Force | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") -New-SmbShare -Name "kube" -Path "$Env:USERPROFILE\.kube" -FullAccess "Everyone" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") - -# Enable Enhanced Session Mode on Host -Write-Host "[$(Get-Date -Format t)] INFO: Enabling Enhanced Session Mode on Hyper-V host" -ForegroundColor Gray -Set-VMHost -EnableEnhancedSessionMode $true | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") - -# Create Internal Hyper-V switch for the L1 nested virtual machines -New-VMSwitch -Name $AgConfig.L1SwitchName -SwitchType Internal | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") -$ifIndex = (Get-NetAdapter -Name ("vEthernet (" + $AgConfig.L1SwitchName + ")")).ifIndex -New-NetIPAddress -IPAddress $AgConfig.L1DefaultGateway -PrefixLength 24 -InterfaceIndex $ifIndex | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") -New-NetNat -Name $AgConfig.L1SwitchName -InternalIPInterfaceAddressPrefix $AgConfig.L1NatSubnetPrefix | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") - -##################################################################### -# Deploying the nested L1 virtual machines -##################################################################### -Write-Host "[$(Get-Date -Format t)] INFO: Fetching Windows 11 IoT Enterprise VM image from Azure storage. This may take a few minutes." -ForegroundColor Yellow -# azcopy cp $AgConfig.PreProdVHDBlobURL $AgConfig.AgDirectories["AgVHDXDir"] --recursive=true --check-length=false --log-level=ERROR | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") -azcopy cp $AgConfig.ProdVHDBlobURL $AgConfig.AgDirectories["AgVHDXDir"] --recursive=true --check-length=false --log-level=ERROR | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") - -# Create three virtual machines from the base VHDX image -$vhdxPath = Get-ChildItem $AgConfig.AgDirectories["AgVHDXDir"] -Filter *.vhdx | Select-Object -ExpandProperty FullName -foreach ($site in $AgConfig.SiteConfig.GetEnumerator()) { - if ($site.Value.Type -eq "AKSEE") { - # Create disks for each site host - Write-Host "[$(Get-Date -Format t)] INFO: Creating $($site.Name) disk." -ForegroundColor Gray - $destVhdxPath = "$($AgConfig.AgDirectories["AgVHDXDir"])\$($site.Name)Disk.vhdx" - $destPath = $AgConfig.AgDirectories["AgVHDXDir"] - New-VHD -ParentPath $vhdxPath -Path $destVhdxPath -Differencing | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") - - # Create a new virtual machine and attach the existing virtual hard disk - Write-Host "[$(Get-Date -Format t)] INFO: Creating and configuring $($site.Name) virtual machine." -ForegroundColor Gray - - New-VM -Name $site.Name ` - -Path $destPath ` - -MemoryStartupBytes $AgConfig.L1VMMemory ` - -BootDevice VHD ` - -VHDPath $destVhdxPath ` - -Generation 2 ` - -Switch $AgConfig.L1SwitchName | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") - - # Set up the virtual machine before coping all AKS Edge Essentials automation files - Set-VMProcessor -VMName $site.Name ` - -Count $AgConfig.L1VMNumVCPU ` - -ExposeVirtualizationExtensions $true | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") - - Get-VMNetworkAdapter -VMName $site.Name | Set-VMNetworkAdapter -MacAddressSpoofing On | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") - Enable-VMIntegrationService -VMName $site.Name -Name "Guest Service Interface" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") - - # Start the virtual machine - Start-VM -Name $site.Name | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") - } -} - -Start-Sleep -Seconds 20 -# Create an array with VM names -$VMnames = (Get-VM).Name - -$sourcePath = "$PsHome\Profile.ps1" -$destinationPath = "C:\Deployment\Profile.ps1" -$maxRetries = 3 - -foreach ($VM in $VMNames) { - $retryCount = 0 - $copySucceeded = $false - - while (-not $copySucceeded -and $retryCount -lt $maxRetries) { - try { - Copy-VMFile $VM -SourcePath $sourcePath -DestinationPath $destinationPath -CreateFullPath -FileSource Host -Force -ErrorAction Stop - $copySucceeded = $true - Write-Host "File copied to $VM successfully." - } catch { - $retryCount++ - Write-Host "Attempt $retryCount : File copy to $VM failed. Retrying..." - Start-Sleep -Seconds 30 # Wait for 30 seconds before retrying - } - } - - if (-not $copySucceeded) { - Write-Host "File copy to $VM failed after $maxRetries attempts." - } -} - -######################################################################## -# Prepare L1 nested virtual machines for AKS Edge Essentials bootstrap -######################################################################## -foreach ($site in $AgConfig.SiteConfig.GetEnumerator()) { - if ($site.Value.Type -eq "AKSEE") { - Write-Host "[$(Get-Date -Format t)] INFO: Renaming computer name of $($site.Name)" -ForegroundColor Gray - $ErrorActionPreference = "SilentlyContinue" - Invoke-Command -VMName $site.Name -Credential $Credentials -ScriptBlock { - $site = $using:site - (gwmi win32_computersystem).Rename($site.Name) - } | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") - $ErrorActionPreference = "Continue" - Stop-VM -Name $site.Name -Force -Confirm:$false - Start-VM -Name $site.Name - } -} - -foreach ($VM in $VMNames) { - $VMStatus = Get-VMIntegrationService -VMName $VM -Name Heartbeat - while ($VMStatus.PrimaryStatusDescription -ne "OK") { - $VMStatus = Get-VMIntegrationService -VMName $VM -Name Heartbeat - write-host "[$(Get-Date -Format t)] INFO: Waiting for $VM to finish booting." -ForegroundColor Gray - Start-Sleep -Seconds 5 - } -} - -Write-Host "[$(Get-Date -Format t)] INFO: Fetching the latest two AKS Edge Essentials releases." -ForegroundColor Gray -$latestReleaseTag = (Invoke-WebRequest $websiteUrls["aksEEReleases"] | ConvertFrom-Json)[0].tag_name -$beforeLatestReleaseTag = (Invoke-WebRequest $websiteUrls["aksEEReleases"] | ConvertFrom-Json)[1].tag_name -$AKSEEReleasesTags = ($latestReleaseTag,$beforeLatestReleaseTag) -$AKSEESchemaVersions = @() - -for ($i = 0; $i -lt $AKSEEReleasesTags.Count; $i++) { - $releaseTag = (Invoke-WebRequest $websiteUrls["aksEEReleases"] | ConvertFrom-Json)[$i].tag_name - $AKSEEReleaseDownloadUrl = "https://github.com/Azure/AKS-Edge/archive/refs/tags/$releaseTag.zip" - $output = Join-Path $AgToolsDir "$releaseTag.zip" - Invoke-WebRequest $AKSEEReleaseDownloadUrl -OutFile $output - Expand-Archive $output -DestinationPath $AgToolsDir -Force - $AKSEEReleaseConfigFilePath = "$AgToolsDir\AKS-Edge-$releaseTag\tools\aksedge-config.json" - $jsonContent = Get-Content -Raw -Path $AKSEEReleaseConfigFilePath | ConvertFrom-Json - $schemaVersion = $jsonContent.SchemaVersion - $AKSEESchemaVersions += $schemaVersion - # Clean up the downloaded release files - Remove-Item -Path $output -Force - Remove-Item -Path "$AgToolsDir\AKS-Edge-$releaseTag" -Force -Recurse -} - -Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { - $hostname = hostname - $ProgressPreference = "SilentlyContinue" - ########################################### - # Preparing environment folders structure - ########################################### - Write-Host "[$(Get-Date -Format t)] INFO: Preparing folder structure on $hostname." -ForegroundColor Gray - $deploymentFolder = "C:\Deployment" # Deployment folder is already pre-created in the VHD image - $logsFolder = "$deploymentFolder\Logs" - $kubeFolder = "$Env:USERPROFILE\.kube" - - # Set up an array of folders - $folders = @($logsFolder, $kubeFolder) - - # Loop through each folder and create it - foreach ($Folder in $folders) { - New-Item -ItemType Directory $Folder -Force - } -} | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") - -$subscriptionId = (Get-AzSubscription).Id -Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { - # Start logging - $hostname = hostname - $ProgressPreference = "SilentlyContinue" - $deploymentFolder = "C:\Deployment" # Deployment folder is already pre-created in the VHD image - $logsFolder = "$deploymentFolder\Logs" - Start-Transcript -Path $logsFolder\AKSEEBootstrap.log - $AgConfig = $using:AgConfig - $AgToolsDir = $using:AgToolsDir - $websiteUrls = $using:websiteUrls - - ########################################## - # Deploying AKS Edge Essentials clusters - ########################################## - $deploymentFolder = "C:\Deployment" # Deployment folder is already pre-created in the VHD image - $logsFolder = "$deploymentFolder\Logs" - - # Assigning network adapter IP address - $NetIPAddress = $AgConfig.SiteConfig[$Env:COMPUTERNAME].NetIPAddress - $DefaultGateway = $AgConfig.SiteConfig[$Env:COMPUTERNAME].DefaultGateway - $PrefixLength = $AgConfig.SiteConfig[$Env:COMPUTERNAME].PrefixLength - $DNSClientServerAddress = $AgConfig.SiteConfig[$Env:COMPUTERNAME].DNSClientServerAddress - Write-Host "[$(Get-Date -Format t)] INFO: Configuring networking interface on $hostname with IP address $NetIPAddress." -ForegroundColor Gray - $AdapterName = (Get-NetAdapter -Name Ethernet*).Name - $ifIndex = (Get-NetAdapter -Name $AdapterName).ifIndex - New-NetIPAddress -IPAddress $NetIPAddress -DefaultGateway $DefaultGateway -PrefixLength $PrefixLength -InterfaceIndex $ifIndex | Out-Null - Set-DNSClientServerAddress -InterfaceIndex $ifIndex -ServerAddresses $DNSClientServerAddress | Out-Null - - ########################################### - # Validating internet connectivity - ########################################### - $timeElapsed = 0 - do { - Write-Host "[$(Get-Date -Format t)] INFO: Waiting for internet connection to be healthy on $hostname." -ForegroundColor Gray - Start-Sleep -Seconds 5 - $timeElapsed = $timeElapsed + 10 - } until ((Test-Connection bing.com -Count 1 -ErrorAction SilentlyContinue) -or ($timeElapsed -eq 60)) - - # Fetching latest AKS Edge Essentials msi file - Write-Host "[$(Get-Date -Format t)] INFO: Fetching latest AKS Edge Essentials install file on $hostname." -ForegroundColor Gray - Invoke-WebRequest $websiteUrls["aksEEk3s"] -OutFile $deploymentFolder\AKSEEK3s.msi - - # Fetching required GitHub artifacts from Jumpstart repository - Write-Host "[$(Get-Date -Format t)] INFO: Fetching GitHub artifacts" -ForegroundColor Gray - $repoName = "azure_arc" # While testing, change to your GitHub fork's repository name - $githubApiUrl = "https://api.github.com/repos/$using:githubAccount/$repoName/contents/azure_jumpstart_ag/artifacts/L1Files?ref=$using:githubBranch" - $response = Invoke-RestMethod -Uri $githubApiUrl - $fileUrls = $response | Where-Object { $_.type -eq "file" } | Select-Object -ExpandProperty download_url - $fileUrls | ForEach-Object { - $fileName = $_.Substring($_.LastIndexOf("/") + 1) - $outputFile = Join-Path $deploymentFolder $fileName - Invoke-RestMethod -Uri $_ -OutFile $outputFile - } - - ############################################################################### - # Setting up replacement parameters for AKS Edge Essentials config json file - ############################################################################### - Write-Host "[$(Get-Date -Format t)] INFO: Building AKS Edge Essentials config json file on $hostname." -ForegroundColor Gray - $AKSEEConfigFilePath = "$deploymentFolder\ScalableCluster.json" - $AdapterName = (Get-NetAdapter -Name Ethernet*).Name - $namingGuid = $using:namingGuid - $arcClusterName = $AgConfig.SiteConfig[$Env:COMPUTERNAME].ArcClusterName + "-$namingGuid" - - # Fetch schemaVersion release from the AgConfig file - $AKSEESchemaVersionUseLatest = $AgConfig.SiteConfig[$Env:COMPUTERNAME].AKSEEReleaseUseLatest - if($AKSEESchemaVersionUseLatest){ - $SchemaVersion = $using:AKSEESchemaVersions[0] - } - else { - $SchemaVersion = $using:AKSEESchemaVersions[1] - } - - $replacementParams = @{ - "SchemaVersion-null" = $SchemaVersion - "ServiceIPRangeStart-null" = $AgConfig.SiteConfig[$Env:COMPUTERNAME].ServiceIPRangeStart - "1000" = $AgConfig.SiteConfig[$Env:COMPUTERNAME].ServiceIPRangeSize - "ControlPlaneEndpointIp-null" = $AgConfig.SiteConfig[$Env:COMPUTERNAME].ControlPlaneEndpointIp - "Ip4GatewayAddress-null" = $AgConfig.SiteConfig[$Env:COMPUTERNAME].DefaultGateway - "2000" = $AgConfig.SiteConfig[$Env:COMPUTERNAME].PrefixLength - "DnsServer-null" = $AgConfig.SiteConfig[$Env:COMPUTERNAME].DNSClientServerAddress - "Ethernet-Null" = $AdapterName - "Ip4Address-null" = $AgConfig.SiteConfig[$Env:COMPUTERNAME].LinuxNodeIp4Address - "ClusterName-null" = $arcClusterName - "Location-null" = $using:azureLocation - "ResourceGroupName-null" = $using:resourceGroup - "SubscriptionId-null" = $using:subscriptionId - "TenantId-null" = $using:spnTenantId - "ClientId-null" = $using:spnClientId - "ClientSecret-null" = $using:spnClientSecret - } - - ################################################### - # Preparing AKS Edge Essentials config json file - ################################################### - $content = Get-Content $AKSEEConfigFilePath - foreach ($key in $replacementParams.Keys) { - $content = $content -replace $key, $replacementParams[$key] - } - Set-Content "$deploymentFolder\Config.json" -Value $content -} -Write-Host "[$(Get-Date -Format t)] INFO: Initial L1 virtualization infrastructure configuration complete." -ForegroundColor Green -Write-Host - -Write-Host "[$(Get-Date -Format t)] INFO: Installing AKS Edge Essentials (Step 7/17)" -ForegroundColor DarkGreen -foreach ($VMName in $VMNames) { - $Session = New-PSSession -VMName $VMName -Credential $Credentials - Write-Host "[$(Get-Date -Format t)] INFO: Rebooting $VMName." -ForegroundColor Gray - Invoke-Command -Session $Session -ScriptBlock { - $Action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -File C:\Deployment\AKSEEBootstrap.ps1" - $Trigger = New-ScheduledTaskTrigger -AtStartup - Register-ScheduledTask -TaskName "Startup Scan" -Action $Action -Trigger $Trigger -User $Env:USERNAME -Password 'Agora123!!' -RunLevel Highest | Out-Null - Restart-Computer -Force -Confirm:$false - } | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1AKSInfra.log") - Remove-PSSession $Session | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1AKSInfra.log") -} - -Write-Host "[$(Get-Date -Format t)] INFO: Sleeping for three (3) minutes to allow for AKS EE installs to complete." -ForegroundColor Gray -Start-Sleep -Seconds 180 # Give some time for the AKS EE installs to complete. This will take a few minutes. - -##################################################################### -# Monitor until the kubeconfig files are detected and copied over -##################################################################### -$elapsedTime = Measure-Command { - foreach ($VMName in $VMNames) { - $path = "C:\Users\Administrator\.kube\config-" + $VMName.ToLower() - $user = $AgConfig.L1Username - [securestring]$secStringPassword = ConvertTo-SecureString $AgConfig.L1Password -AsPlainText -Force - $credential = New-Object System.Management.Automation.PSCredential($user, $secStringPassword) - Start-Sleep 5 - while (!(Invoke-Command -VMName $VMName -Credential $credential -ScriptBlock { Test-Path $using:path })) { - Start-Sleep 30 - Write-Host "[$(Get-Date -Format t)] INFO: Waiting for AKS Edge Essentials kubeconfig to be available on $VMName." -ForegroundColor Gray - } - - Write-Host "[$(Get-Date -Format t)] INFO: $VMName's kubeconfig is ready - copying over config-$VMName" -ForegroundColor DarkGreen - $destinationPath = $Env:USERPROFILE + "\.kube\config-" + $VMName - $s = New-PSSession -VMName $VMName -Credential $credential - Copy-Item -FromSession $s -Path $path -Destination $destinationPath - $file = Get-Item $destinationPath - if ($file.Length -eq 0) { - Write-Host "[$(Get-Date -Format t)] ERROR: Kubeconfig on $VMName is corrupt. This error is unrecoverable. Exiting." -ForegroundColor White -BackgroundColor Red - exit 1 - } - } -} - -# Display the elapsed time in seconds it took for kubeconfig files to show up in folder -Write-Host "[$(Get-Date -Format t)] INFO: Waiting on kubeconfig files took $($elapsedTime.ToString("g"))." -ForegroundColor Gray - -##################################################################### -# Merging kubeconfig files on the L0 virtual machine -##################################################################### -Write-Host "[$(Get-Date -Format t)] INFO: All three kubeconfig files are present. Merging kubeconfig files for use with kubectx." -ForegroundColor Gray -$kubeconfigpath = "" -foreach ($VMName in $VMNames) { - $kubeconfigpath = $kubeconfigpath + "$Env:USERPROFILE\.kube\config-" + $VMName.ToLower() + ";" -} -$Env:KUBECONFIG = $kubeconfigpath -kubectl config view --merge --flatten > "$Env:USERPROFILE\.kube\config-raw" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1AKSInfra.log") -kubectl config get-clusters --kubeconfig="$Env:USERPROFILE\.kube\config-raw" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1AKSInfra.log") -Rename-Item -Path "$Env:USERPROFILE\.kube\config-raw" -NewName "$Env:USERPROFILE\.kube\config" -$Env:KUBECONFIG = "$Env:USERPROFILE\.kube\config" - -# Print a message indicating that the merge is complete -Write-Host "[$(Get-Date -Format t)] INFO: All three kubeconfig files merged successfully." -ForegroundColor Gray - -# Validate context switching using kubectx & kubectl -foreach ($cluster in $VMNames) { - Write-Host "[$(Get-Date -Format t)] INFO: Testing connectivity to kube api on $cluster cluster." -ForegroundColor Gray - kubectx $cluster.ToLower() - kubectl get nodes -o wide -} -Write-Host "[$(Get-Date -Format t)] INFO: AKS Edge Essentials installs are complete!" -ForegroundColor Green -Write-Host +Deploy-VirtualizationInfrastructure ##################################################################### # Setup Azure Container registry on cloud AKS staging environment ##################################################################### -az aks get-credentials --resource-group $Env:resourceGroup --name $Env:aksStagingClusterName --admin | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") -kubectx staging="$Env:aksStagingClusterName-admin" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") - -# Attach ACR to staging cluster -Write-Host "[$(Get-Date -Format t)] INFO: Attaching Azure Container Registry to AKS staging cluster." -ForegroundColor Gray -az aks update -n $Env:aksStagingClusterName -g $Env:resourceGroup --attach-acr $acrName | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") +if ($industry -eq "retail") { + Deploy-AzContainerRegistry +} ##################################################################### # Creating Kubernetes namespaces on clusters ##################################################################### Write-Host "[$(Get-Date -Format t)] INFO: Creating namespaces on clusters (Step 8/17)" -ForegroundColor DarkGreen -foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { - $clusterName = $cluster.Name.ToLower() - kubectx $clusterName | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") - foreach ($namespace in $AgConfig.Namespaces) { - Write-Host "[$(Get-Date -Format t)] INFO: Creating namespace $namespace on $clusterName" -ForegroundColor Gray - kubectl create namespace $namespace | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") - } -} +Deploy-ClusterNamespaces ##################################################################### # Setup Azure Container registry pull secret on clusters ##################################################################### Write-Host "[$(Get-Date -Format t)] INFO: Configuring secrets on clusters (Step 9/17)" -ForegroundColor DarkGreen -foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { - $clusterName = $cluster.Name.ToLower() - foreach ($namespace in $AgConfig.Namespaces) { - if ($namespace -eq "contoso-supermarket" -or $namespace -eq "images-cache"){ - Write-Host "[$(Get-Date -Format t)] INFO: Configuring Azure Container registry on $clusterName" - kubectx $clusterName | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") - kubectl create secret docker-registry acr-secret ` - --namespace $namespace ` - --docker-server="$acrName.azurecr.io" ` - --docker-username="$Env:spnClientId" ` - --docker-password="$Env:spnClientSecret" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") - } - } -} - -##################################################################### -# Create secrets for GitHub actions -##################################################################### -Write-Host "[$(Get-Date -Format t)] INFO: Creating Kubernetes secrets" -ForegroundColor Gray -$cosmosDBKey = $(az cosmosdb keys list --name $cosmosDBName --resource-group $resourceGroup --query primaryMasterKey --output tsv) -foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { - $clusterName = $cluster.Name.ToLower() - Write-Host "[$(Get-Date -Format t)] INFO: Creating Kubernetes secrets on $clusterName" -ForegroundColor Gray - foreach ($namespace in $AgConfig.Namespaces) { - if ($namespace -eq "contoso-supermarket" -or $namespace -eq "images-cache"){ - kubectx $cluster.Name.ToLower() | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") - kubectl create secret generic postgrespw --from-literal=POSTGRES_PASSWORD='Agora123!!' --namespace $namespace | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") - kubectl create secret generic cosmoskey --from-literal=COSMOS_KEY=$cosmosDBKey --namespace $namespace | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") - kubectl create secret generic github-token --from-literal=token=$githubPat --namespace $namespace | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") - } - } -} -Write-Host "[$(Get-Date -Format t)] INFO: Cluster secrets configuration complete." -ForegroundColor Green -Write-Host +Deploy-ClusterSecrets ##################################################################### # Cache contoso-supermarket images on all clusters ##################################################################### -Write-Host "[$(Get-Date -Format t)] INFO: Caching contoso-supermarket images on all clusters" -ForegroundColor Gray -while ($workflowStatus.status -ne "completed") { - Write-Host "INFO: Waiting for pos-app-initial-images-build workflow to complete" -ForegroundColor Gray - Start-Sleep -Seconds 10 - $workflowStatus = (gh run list --workflow=pos-app-initial-images-build.yml --json status) | ConvertFrom-Json -} -foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { - $branch = $cluster.Name.ToLower() - $context = $cluster.Name.ToLower() - $applicationName = "contoso-supermarket" - $imageTag = "v1.0" - $imagePullSecret = "acr-secret" - $namespace = "images-cache" - if ($branch -eq "chicago") { - $branch = "canary" - } - if ($branch -eq "seattle") { - $branch = "production" - } - Save-K8sImage -applicationName $applicationName -imageName "contosoai" -imageTag $imageTag -namespace $namespace -imagePullSecret $imagePullSecret -branch $branch -acrName $acrName -context $context - Save-K8sImage -applicationName $applicationName -imageName "pos" -imageTag $imageTag -namespace $namespace -imagePullSecret $imagePullSecret -branch $branch -acrName $acrName -context $context - Save-K8sImage -applicationName $applicationName -imageName "pos-cloudsync" -imageTag $imageTag -namespace $namespace -imagePullSecret $imagePullSecret -branch $branch -acrName $acrName -context $context - Save-K8sImage -applicationName $applicationName -imageName "queue-monitoring-backend" -imageTag $imageTag -namespace $namespace -imagePullSecret $imagePullSecret -branch $branch -acrName $acrName -context $context - Save-K8sImage -applicationName $applicationName -imageName "queue-monitoring-frontend" -imageTag $imageTag -namespace $namespace -imagePullSecret $imagePullSecret -branch $branch -acrName $acrName -context $context -} +Deploy-K8sImagesCache ##################################################################### # Connect the AKS Edge Essentials clusters and hosts to Azure Arc ##################################################################### Write-Host "[$(Get-Date -Format t)] INFO: Connecting AKS Edge clusters to Azure with Azure Arc (Step 10/17)" -ForegroundColor DarkGreen - -# Running pre-checks to ensure that the aksedge ConfigMap is present on all clusters -$maxRetries = 5 -$retryInterval = 30 # seconds -$retryCount = 0 -foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { - $clusterName = $cluster.Name.ToLower() - if ($clusterName -ne "staging") { - while ($retryCount -lt $maxRetries) { - kubectx $clusterName - $configMap = kubectl get configmap -n aksedge aksedge - if ($null -eq $configMap) { - $retryCount++ - Write-Host "Retry ${retryCount}/${maxRetries}: aksedge ConfigMap not found on $clusterName. Retrying in $retryInterval seconds..." | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ArcConnectivity.log") - Start-Sleep -Seconds $retryInterval - } - else { - # ConfigMap found, continue with the rest of the script - Write-Host "aksedge ConfigMap found on $clusterName. Continuing with the script..." | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ArcConnectivity.log") - break # Exit the loop - } - } - - if ($retryCount -eq $maxRetries) { - Write-Host "[$(Get-Date -Format t)] ERROR: aksedge ConfigMap not found on $clusterName. Exiting..." -ForegroundColor White -BackgroundColor Red | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ArcConnectivity.log") - exit 1 # Exit the script - } - } -} - -foreach ($VM in $VMNames) { - $secret = $Env:spnClientSecret - $clientId = $Env:spnClientId - $tenantId = $Env:spnTenantId - $location = $Env:azureLocation - $resourceGroup = $Env:resourceGroup - - Invoke-Command -VMName $VM -Credential $Credentials -ScriptBlock { - # Install prerequisites - . C:\Deployment\Profile.ps1 - $hostname = hostname - $ProgressPreference = "SilentlyContinue" - Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force - Install-Module Az.Resources -Repository PSGallery -Force -AllowClobber -ErrorAction Stop - Install-Module Az.Accounts -Repository PSGallery -Force -AllowClobber -ErrorAction Stop - Install-Module Az.ConnectedKubernetes -Repository PSGallery -Force -AllowClobber -ErrorAction Stop - Install-Module Az.ConnectedMachine -Force -AllowClobber -ErrorAction Stop - - # Connect servers to Arc - $azurePassword = ConvertTo-SecureString $using:secret -AsPlainText -Force - $psCred = New-Object System.Management.Automation.PSCredential($using:clientId, $azurePassword) - Connect-AzAccount -Credential $psCred -TenantId $using:tenantId -ServicePrincipal - Write-Host "[$(Get-Date -Format t)] INFO: Arc-enabling $hostname server." -ForegroundColor Gray - Redo-Command -ScriptBlock { Connect-AzConnectedMachine -ResourceGroupName $using:resourceGroup -Name "Ag-$hostname-Host" -Location $using:location } - - # Connect clusters to Arc - $deploymentPath = "C:\Deployment\config.json" - Write-Host "[$(Get-Date -Format t)] INFO: Arc-enabling $hostname AKS Edge Essentials cluster." -ForegroundColor Gray - - kubectl get svc - - $retryCount = 5 # Number of times to retry the operation - $retryDelay = 30 # Delay in seconds between retries - - for ($retry = 1; $retry -le $retryCount; $retry++) { - $return = Connect-AksEdgeArc -JsonConfigFilePath $deploymentPath - if ($return -ne "OK") { - Write-Output "Failed to onboard AKS Edge Essentials cluster to Azure Arc. Retrying (Attempt $retry of $retryCount)..." - if ($retry -lt $retryCount) { - Start-Sleep -Seconds $retryDelay # Wait before retrying - } - else { - Write-Output "Exceeded maximum retry attempts. Exiting." - break # Exit the loop after the maximum number of retries - } - } else { - Write-Output "Successfully onboarded AKS Edge Essentials cluster to Azure Arc." - break # Exit the loop if the connection is successful - } - } - - - } 2>&1 | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ArcConnectivity.log") -} - -##################################################################### -# Tag Azure Arc resources -##################################################################### -$arcResourceTypes = $AgConfig.ArcServerResourceType, $AgConfig.ArcK8sResourceType -$Tag = @{$AgConfig.TagName = $AgConfig.TagValue } - -# Iterate over the Arc resources and tag it -foreach ($arcResourceType in $arcResourceTypes) { - $arcResources = Get-AzResource -ResourceType $arcResourceType -ResourceGroupName $Env:resourceGroup - foreach ($arcResource in $arcResources) { - Update-AzTag -ResourceId $arcResource.Id -Tag $Tag -Operation Merge | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ArcConnectivity.log") - } -} - -Write-Host "[$(Get-Date -Format t)] INFO: AKS Edge Essentials clusters and hosts have been registered with Azure Arc!" -ForegroundColor Green -Write-Host - +Deploy-AzArcK8s ##################################################################### # Installing flux extension on clusters ##################################################################### Write-Host "[$(Get-Date -Format t)] INFO: Installing flux extension on clusters (Step 11/17)" -ForegroundColor DarkGreen - -$resourceTypes = @($AgConfig.ArcK8sResourceType, $AgConfig.AksResourceType) -$resources = Get-AzResource -ResourceGroupName $Env:resourceGroup | Where-Object { $_.ResourceType -in $resourceTypes } - -$jobs = @() - -foreach ($resource in $resources) { - - $resourceName = $resource.Name - $resourceType = $resource.Type - - Write-Host "[$(Get-Date -Format t)] INFO: Installing flux extension on $resourceName" -ForegroundColor Gray - - $job = Start-Job -Name $resourceName -ScriptBlock { - param($resourceName, $resourceType) - - $retryCount = 10 - $retryDelaySeconds = 60 - - switch ($resourceType) - { - 'Microsoft.Kubernetes/connectedClusters' {$ClusterType = 'ConnectedClusters'} - 'Microsoft.ContainerService/managedClusters' {$ClusterType = 'ManagedClusters'} - } - - if($clusterType -eq 'ConnectedClusters'){ - # Check if cluster is connected to Azure Arc control plane - $ConnectivityStatus = (Get-AzConnectedKubernetes -ResourceGroupName $Env:resourceGroup -ClusterName $resourceName).ConnectivityStatus - - if (-not ($ConnectivityStatus -eq 'Connected')) { - - for ($attempt = 1; $attempt -le $retryCount; $attempt++) { - - - $ConnectivityStatus = (Get-AzConnectedKubernetes -ResourceGroupName $Env:resourceGroup -ClusterName $resourceName).ConnectivityStatus - - # Check the condition - if ($ConnectivityStatus -eq 'Connected') { - # Condition is true, break out of the loop - break - } - - # Wait for a specific duration before re-evaluating the condition - Start-Sleep -Seconds $retryDelaySeconds - - - if ($attempt -lt $retryCount) { - Write-Host "Retrying in $retryDelaySeconds seconds..." - Start-Sleep -Seconds $retryDelaySeconds - } - else { - $ProvisioningState = "Timed out after $($retryDelaySeconds * $retryCount) seconds while waiting for cluster to become connected to Azure Arc control plane. Current status: $ConnectivityStatus" - break # Max retry attempts reached, exit the loop - } - - } - } - } - - $extension = az k8s-extension list --cluster-name $resourceName --resource-group $Env:resourceGroup --cluster-type $ClusterType --output json | ConvertFrom-Json - $extension = $extension | Where-Object extensionType -eq 'microsoft.flux' - - if ($extension.ProvisioningState -ne 'Succeeded' -and ($ConnectivityStatus -eq 'Connected' -or $clusterType -eq "ManagedClusters")) { - - for ($attempt = 1; $attempt -le $retryCount; $attempt++) { - - try { - - if ($extension) { - - az k8s-extension delete --name "flux" --cluster-name $resourceName --resource-group $Env:resourceGroup --cluster-type $ClusterType --force --yes - - } - - az k8s-extension create --name "flux" --extension-type "microsoft.flux" --cluster-name $resourceName --resource-group $Env:resourceGroup --cluster-type $ClusterType --output json | ConvertFrom-Json -OutVariable extension - - break # Command succeeded, exit the loop - } - - catch { - Write-Warning "An error occurred: $($_.Exception.Message)" - - if ($attempt -lt $retryCount) { - Write-Host "Retrying in $retryDelaySeconds seconds..." - Start-Sleep -Seconds $retryDelaySeconds - } - else { - Write-Error "Failed to execute the command after $retryCount attempts." - $ProvisioningState = $($_.Exception.Message) - break # Max retry attempts reached, exit the loop - } - - } - - } - - } - - $ProvisioningState = $extension.ProvisioningState - - [PSCustomObject]@{ - ResourceName = $resourceName - ResourceType = $resourceType - ProvisioningState = $ProvisioningState - } - - } -ArgumentList $resourceName, $resourceType - - $jobs += $job -} - -# Wait for all jobs to complete -$FluxExtensionJobs = $jobs | Wait-Job | Receive-Job -Keep - -$jobs | Format-Table Name,PSBeginTime,PSEndTime -AutoSize - -# Clean up jobs -$jobs | Remove-Job - -# Abort if Flux-extension fails on any cluster -if ($FluxExtensionJobs | Where-Object ProvisioningState -ne 'Succeeded') { - - throw "One or more Flux-extension deployments failed - aborting" - -} +Deploy-ClusterFluxExtension ##################################################################### # Deploying nginx on AKS cluster ##################################################################### -Write-Host "[$(Get-Date -Format t)] INFO: Deploying nginx on AKS cluster (Step 12/17)" -ForegroundColor DarkGreen -kubectx $AgConfig.SiteConfig.Staging.FriendlyName.ToLower() | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Nginx.log") -helm repo add $AgConfig.nginx.RepoName $AgConfig.nginx.RepoURL | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Nginx.log") -helm repo update | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Nginx.log") - -helm install $AgConfig.nginx.ReleaseName $AgConfig.nginx.ChartName ` - --create-namespace ` - --namespace $AgConfig.nginx.Namespace ` - --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Nginx.log") +if ($industry -eq "retail") { + Write-Host "[$(Get-Date -Format t)] INFO: Deploying nginx on AKS cluster (Step 12/17)" -ForegroundColor DarkGreen + kubectx $AgConfig.SiteConfig.Staging.FriendlyName.ToLower() | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Nginx.log") + helm repo add $AgConfig.nginx.RepoName $AgConfig.nginx.RepoURL | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Nginx.log") + helm repo update | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Nginx.log") + helm install $AgConfig.nginx.ReleaseName $AgConfig.nginx.ChartName ` + --create-namespace ` + --namespace $AgConfig.nginx.Namespace ` + --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Nginx.log") +} ##################################################################### # Configuring applications on the clusters using GitOps ##################################################################### -Write-Host "[$(Get-Date -Format t)] INFO: Configuring GitOps (Step 13/17)" -ForegroundColor DarkGreen - -Write-Host "[$(Get-Date -Format t)] INFO: Cleaning up images-cache namespace on all clusters" -ForegroundColor Gray -# Cleaning up images-cache namespace on all clusters -foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { - Start-Job -Name images-cache-cleanup -ScriptBlock { - $cluster = $using:cluster - $clusterName = $cluster.Name.ToLower() - Write-Host "[$(Get-Date -Format t)] INFO: Deleting images-cache namespace on cluster $clusterName" -ForegroundColor Gray - kubectl delete namespace "images-cache" --context $clusterName - } +if ($industry -eq "retail") { + Write-Host "[$(Get-Date -Format t)] INFO: Configuring GitOps (Step 13/17)" -ForegroundColor DarkGreen + Deploy-RetailConfigs } -# TODO - this looks app-specific so should perhaps be moved to the app loop -while ($workflowStatus.status -ne "completed") { - Write-Host "INFO: Waiting for pos-app-initial-images-build workflow to complete" -ForegroundColor Gray - Start-Sleep -Seconds 10 - $workflowStatus = (gh run list --workflow=pos-app-initial-images-build.yml --json status) | ConvertFrom-Json +if ($industry -eq "manufacturing") { + Deploy-AIO + Deploy-ManufacturingConfigs + $mqttIpArray=Set-MQTTIpAddress + #Deploy-MQTTSimulator -mqttIpArray $mqttIpArray # this is now being done via helm + Deploy-MQTTExplorer -mqttIpArray $mqttIpArray } -foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { - Start-Job -Name gitops -ScriptBlock { - - Function Get-GitHubFiles ($githubApiUrl, $folderPath, [Switch]$excludeFolders) { - # Force TLS 1.2 for connections to prevent TLS/SSL errors - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - - $response = Invoke-RestMethod -Uri $githubApiUrl - $fileUrls = $response | Where-Object { $_.type -eq "file" } | Select-Object -ExpandProperty download_url - $fileUrls | ForEach-Object { - $fileName = $_.Substring($_.LastIndexOf("/") + 1) - $outputFile = Join-Path $folderPath $fileName - Invoke-RestMethod -Uri $_ -OutFile $outputFile - } - - If (-not $excludeFolders) { - $response | Where-Object { $_.type -eq "dir" } | ForEach-Object { - $folderName = $_.name - $path = Join-Path $folderPath $folderName - New-Item $path -ItemType Directory -Force -ErrorAction Continue - Get-GitHubFiles -githubApiUrl $_.url -folderPath $path - } - } - } - - $AgConfig = $using:AgConfig - $cluster = $using:cluster - $site = $cluster.Value - $siteName = $site.FriendlyName.ToLower() - $namingGuid = $using:namingGuid - $resourceGroup = $using:resourceGroup - $appClonedRepo = $using:appClonedRepo - $appsRepo = $using:appsRepo - - $AgConfig.AppConfig.GetEnumerator() | sort-object -Property @{Expression = { $_.value.Order }; Ascending = $true } | ForEach-Object { - $app = $_ - $store = $cluster.value.Branch.ToLower() - $clusterName = $cluster.value.ArcClusterName + "-$namingGuid" - $branch = $cluster.value.Branch.ToLower() - $configName = $app.value.GitOpsConfigName.ToLower() - $clusterType = $cluster.value.Type - $namespace = $app.value.Namespace - $appName = $app.Value.KustomizationName - $appPath = $app.Value.KustomizationPath - $retryCount = 0 - $maxRetries = 2 - - Write-Host "[$(Get-Date -Format t)] INFO: Creating GitOps config for $configName on $($cluster.Value.ArcClusterName+"-$namingGuid")" -ForegroundColor Gray - if ($clusterType -eq "AKS") { - $type = "managedClusters" - $clusterName = $cluster.value.ArcClusterName - } - else { - $type = "connectedClusters" - } - if ($branch -eq "main") { - $store = "dev" - } - - # Wait for Kubernetes API server to become available - $apiServer = kubectl config view --context $cluster.Name.ToLower() --minify -o jsonpath='{.clusters[0].cluster.server}' - $apiServerAddress = $apiServer -replace '.*https://| .*$' - $apiServerFqdn = ($apiServerAddress -split ":")[0] - $apiServerPort = ($apiServerAddress -split ":")[1] - - do { - $result = Test-NetConnection -ComputerName $apiServerFqdn -Port $apiServerPort -WarningAction SilentlyContinue - if ($result.TcpTestSucceeded) { - break - } - else { - Start-Sleep -Seconds 5 - } - } while ($true) - If ($app.Value.ConfigMaps){ - # download the config files - foreach ($configMap in $app.value.ConfigMaps.GetEnumerator()){ - $repoPath = $configMap.value.RepoPath - $configPath = "$configMapDir\$appPath\config\$($configMap.Name)\$branch" - $iotHubName = $Env:iotHubHostName.replace(".azure-devices.net", "") - $gitHubUser = $Env:gitHubUser - $githubBranch = $Env:githubBranch - - New-Item -Path $configPath -ItemType Directory -Force | Out-Null - - $githubApiUrl = "https://api.github.com/repos/$gitHubUser/$appsRepo/$($repoPath)?ref=$branch" - Get-GitHubFiles -githubApiUrl $githubApiUrl -folderPath $configPath - - # replace the IoT Hub name and the SAS Tokens with the deployment specific values - # this is a one-off for the broker, but needs to be generalized if/when another app needs it - If ($configMap.Name -eq "mqtt-broker-config"){ - $configFile = "$configPath\mosquitto.conf" - $update = (Get-Content $configFile -Raw) - $update = $update -replace "Ag-IotHub-\w*", $iotHubName - - foreach ($device in $site.IoTDevices) { - $deviceId = "$device-$($site.FriendlyName)" - $deviceSASToken = $(az iot hub generate-sas-token --device-id $deviceId --hub-name $iotHubName --resource-group $resourceGroup --duration (60 * 60 * 24 * 30) --query sas -o tsv --only-show-errors) - $update = $update -replace "Chicago", $site.FriendlyName - $update = $update -replace "SharedAccessSignature.*$($device).*",$deviceSASToken - } - - $update | Set-Content $configFile - } - - # create the namespace if needed - If (-not (kubectl get namespace $namespace --context $siteName)){ - kubectl create namespace $namespace --context $siteName - } - # create the configmap - kubectl create configmap $configMap.name --from-file=$configPath --namespace $namespace --context $siteName - } - } - - az k8s-configuration flux create ` - --cluster-name $clusterName ` - --resource-group $resourceGroup ` - --name $configName ` - --cluster-type $type ` - --url $appClonedRepo ` - --branch $branch ` - --sync-interval 5s ` - --kustomization name=$appName path=$appPath/$store prune=true retry_interval=1m ` - --timeout 10m ` - --namespace $namespace ` - --only-show-errors ` - 2>&1 | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") - - do { - $configStatus = $(az k8s-configuration flux show --name $configName --cluster-name $clusterName --cluster-type $type --resource-group $resourceGroup -o json 2>$null) | convertFrom-JSON - if ($configStatus.ComplianceState -eq "Compliant") { - Write-Host "[$(Get-Date -Format t)] INFO: GitOps configuration $configName is ready on $clusterName" -ForegroundColor DarkGreen | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") - } - else { - if ($configStatus.ComplianceState -ne "Non-compliant") { - Start-Sleep -Seconds 20 - } - elseif ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -lt $maxRetries) { - Start-Sleep -Seconds 20 - $configStatus = $(az k8s-configuration flux show --name $configName --cluster-name $clusterName --cluster-type $type --resource-group $resourceGroup -o json 2>$null) | convertFrom-JSON - if ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -lt $maxRetries) { - $retryCount++ - Write-Host "[$(Get-Date -Format t)] INFO: Attempting to re-install $configName on $clusterName" -ForegroundColor Gray | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") - Write-Host "[$(Get-Date -Format t)] INFO: Deleting $configName on $clusterName" -ForegroundColor Gray | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") - az k8s-configuration flux delete ` - --resource-group $resourceGroup ` - --cluster-name $clusterName ` - --cluster-type $type ` - --name $configName ` - --force ` - --yes ` - --only-show-errors ` - 2>&1 | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") - - Start-Sleep -Seconds 10 - Write-Host "[$(Get-Date -Format t)] INFO: Re-creating $configName on $clusterName" -ForegroundColor Gray | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") - - az k8s-configuration flux create ` - --cluster-name $clusterName ` - --resource-group $resourceGroup ` - --name $configName ` - --cluster-type $type ` - --url $appClonedRepo ` - --branch $branch ` - --sync-interval 5s ` - --kustomization name=$appName path=$appPath/$store prune=true ` - --timeout 30m ` - --namespace $namespace ` - --only-show-errors ` - 2>&1 | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") - } - } - elseif ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -eq $maxRetries) { - Write-Host "[$(Get-Date -Format t)] ERROR: GitOps configuration $configName has failed on $clusterName. Exiting..." -ForegroundColor White -BackgroundColor Red | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") - break - } - } - } until ($configStatus.ComplianceState -eq "Compliant") - } - } -} - -while ($(Get-Job -Name gitops).State -eq 'Running') { - #Write-Host "[$(Get-Date -Format t)] INFO: Waiting for GitOps configuration to complete on all clusters...waiting 60 seconds" -ForegroundColor Gray - Receive-Job -Name gitops -WarningAction SilentlyContinue - Start-Sleep -Seconds 60 -} - -Get-Job -name gitops | Remove-Job -Write-Host "[$(Get-Date -Format t)] INFO: GitOps configuration complete." -ForegroundColor Green -Write-Host +############################################################## +# Deploy Kubernetes Prometheus Stack for Observability +############################################################## +Deploy-Prometheus -AgConfig $AgConfig ##################################################################### -# Deploy Kubernetes Prometheus Stack for Observability +# Deploy Azure Workbook for Infrastructure Observability ##################################################################### -$AgMonitoringDir = $AgConfig.AgDirectories["AgMonitoringDir"] -$observabilityNamespace = $AgConfig.Monitoring["Namespace"] -$observabilityDashboards = $AgConfig.Monitoring["Dashboards"] -$adminPassword = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($adminPassword)) - -# Set Prod Grafana API endpoint -$grafanaDS = $AgConfig.Monitoring["ProdURL"] + "/api/datasources" - -# Installing Grafana -Write-Host "[$(Get-Date -Format t)] INFO: Installing and Configuring Observability components (Step 14/17)" -ForegroundColor DarkGreen -Write-Host "[$(Get-Date -Format t)] INFO: Installing Grafana." -ForegroundColor Gray -$latestRelease = (Invoke-WebRequest -Uri $websiteUrls["grafana"] | ConvertFrom-Json).tag_name.replace('v', '') -Start-Process msiexec.exe -Wait -ArgumentList "/I $AgToolsDir\grafana-$latestRelease.windows-amd64.msi /quiet" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") - -# Update Prometheus Helm charts -helm repo add prometheus-community $websiteUrls["prometheus"] | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") -helm repo update | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") - -# Update Grafana Icons -Copy-Item -Path $AgIconsDir\contoso.png -Destination "C:\Program Files\GrafanaLabs\grafana\public\img" -Copy-Item -Path $AgIconsDir\contoso.svg -Destination "C:\Program Files\GrafanaLabs\grafana\public\img\grafana_icon.svg" - -Get-ChildItem -Path 'C:\Program Files\GrafanaLabs\grafana\public\build\*.js' -Recurse -File | ForEach-Object { -(Get-Content $_.FullName) -replace 'className:u,src:"public/img/grafana_icon.svg"', 'className:u,src:"public/img/contoso.png"' | Set-Content $_.FullName -} - -# Reset Grafana UI -Get-ChildItem -Path 'C:\Program Files\GrafanaLabs\grafana\public\build\*.js' -Recurse -File | ForEach-Object { -(Get-Content $_.FullName) -replace 'Welcome to Grafana', 'Welcome to Grafana for Contoso Supermarket Production' | Set-Content $_.FullName -} - -# Reset Grafana Password -$Env:Path += ';C:\Program Files\GrafanaLabs\grafana\bin' -grafana-cli --homepath "C:\Program Files\GrafanaLabs\grafana" admin reset-admin-password $adminPassword | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") - -# Get Grafana Admin credentials -$adminCredentials = $AgConfig.Monitoring["AdminUser"] + ':' + $adminPassword -$adminEncodedcredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($adminCredentials)) - -$adminHeaders = @{ - "Authorization" = ("Basic " + $adminEncodedcredentials) - "Content-Type" = "application/json" -} - -# Get Contoso User credentials -$userCredentials = $adminUsername + ':' + $adminPassword -$userEncodedcredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($userCredentials)) - -$userHeaders = @{ - "Authorization" = ("Basic " + $userEncodedcredentials) - "Content-Type" = "application/json" -} - -# Download dashboards -foreach ($dashboard in $observabilityDashboards.'grafana.com') { - $grafanaDBPath = "$AgMonitoringDir\grafana-$dashboard.json" - $dashboardmetadata = Invoke-RestMethod -Uri https://grafana.com/api/dashboards/$dashboard/revisions - $dashboardversion = $dashboardmetadata.items | Sort-Object revision | Select-Object -Last 1 | Select-Object -ExpandProperty revision - Invoke-WebRequest https://grafana.com/api/dashboards/$dashboard/revisions/$dashboardversion/download -OutFile $grafanaDBPath -} - -$observabilityDashboardstoImport = @() -$observabilityDashboardstoImport += $observabilityDashboards.'grafana.com' -$observabilityDashboardstoImport += $observabilityDashboards.'custom' - -Write-Host "[$(Get-Date -Format t)] INFO: Creating Prod Grafana User" -ForegroundColor Gray -# Add Contoso Operator User -$grafanaUserBody = @{ - name = $AgConfig.Monitoring["User"] # Display Name - email = $AgConfig.Monitoring["Email"] - login = $adminUsername - password = $adminPassword -} | ConvertTo-Json - -# Make HTTP request to the API to create user -$retryCount = 10 -$retryDelay = 30 -do { - try { - Invoke-RestMethod -Method Post -Uri "$($AgConfig.Monitoring["ProdURL"])/api/admin/users" -Headers $adminHeaders -Body $grafanaUserBody | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") - $retryCount = 0 - } - catch { - $retryCount-- - if ($retryCount -gt 0) { - Write-Host "[$(Get-Date -Format t)] INFO: Retrying in $retryDelay seconds..." -ForegroundColor Gray - Start-Sleep -Seconds $retryDelay - } - } -} while ($retryCount -gt 0) - -# Deploying Kube Prometheus Stack for stores -$AgConfig.SiteConfig.GetEnumerator() | ForEach-Object { - Write-Host "[$(Get-Date -Format t)] INFO: Deploying Kube Prometheus Stack for $($_.Value.FriendlyName) environment" -ForegroundColor Gray - kubectx $_.Value.FriendlyName.ToLower() | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") - - # Wait for Kubernetes API server to become available - $apiServer = kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}' - $apiServerAddress = $apiServer -replace '.*https://| .*$' - $apiServerFqdn = ($apiServerAddress -split ":")[0] - $apiServerPort = ($apiServerAddress -split ":")[1] - - do { - $result = Test-NetConnection -ComputerName $apiServerFqdn -Port $apiServerPort -WarningAction SilentlyContinue - if ($result.TcpTestSucceeded) { - Write-Host "[$(Get-Date -Format t)] INFO: Kubernetes API server $apiServer is available" -ForegroundColor Gray - break - } - else { - Write-Host "[$(Get-Date -Format t)] INFO: Kubernetes API server $apiServer is not yet available. Retrying in 10 seconds..." -ForegroundColor Gray - Start-Sleep -Seconds 10 - } - } while ($true) - - # Install Prometheus Operator - $helmSetValue = $_.Value.HelmSetValue -replace 'adminPasswordPlaceholder', $adminPassword - helm install prometheus prometheus-community/kube-prometheus-stack --set $helmSetValue --namespace $observabilityNamespace --create-namespace --values "$AgMonitoringDir\$($_.Value.HelmValuesFile)" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") - - Do { - Write-Host "[$(Get-Date -Format t)] INFO: Waiting for $($_.Value.FriendlyName) monitoring service to provision.." -ForegroundColor Gray - Start-Sleep -Seconds 45 - $monitorIP = $(if (kubectl get $_.Value.HelmService --namespace $observabilityNamespace --output=jsonpath='{.status.loadBalancer}' | Select-String "ingress" -Quiet) { "Ready!" }Else { "Nope" }) - } while ($monitorIP -eq "Nope" ) - # Get Load Balancer IP - $monitorLBIP = kubectl --namespace $observabilityNamespace get $_.Value.HelmService --output=jsonpath='{.status.loadBalancer.ingress[0].ip}' - - if ($_.Value.IsProduction) { - Write-Host "[$(Get-Date -Format t)] INFO: Add $($_.Value.FriendlyName) Data Source to Grafana" - # Request body with information about the data source to add - $grafanaDSBody = @{ - name = $_.Value.FriendlyName.ToLower() - type = 'prometheus' - url = ("http://" + $monitorLBIP + ":9090") - access = 'proxy' - basicAuth = $false - isDefault = $true - } | ConvertTo-Json - - # Make HTTP request to the API - Invoke-RestMethod -Method Post -Uri $grafanaDS -Headers $adminHeaders -Body $grafanaDSBody | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") - } - - # Add Contoso Operator User - if (!$_.Value.IsProduction) { - Write-Host "[$(Get-Date -Format t)] INFO: Creating $($_.Value.FriendlyName) Grafana User" -ForegroundColor Gray - $grafanaUserBody = @{ - name = $AgConfig.Monitoring["User"] # Display Name - email = $AgConfig.Monitoring["Email"] - login = $adminUsername - password = $adminPassword - } | ConvertTo-Json - - # Make HTTP request to the API to create user - $retryCount = 10 - $retryDelay = 30 - - do { - try { - Invoke-RestMethod -Method Post -Uri "http://$monitorLBIP/api/admin/users" -Headers $adminHeaders -Body $grafanaUserBody | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") - $retryCount = 0 - } - catch { - $retryCount-- - if ($retryCount -gt 0) { - Write-Host "[$(Get-Date -Format t)] INFO: Retrying in $retryDelay seconds..." -ForegroundColor Gray - Start-Sleep -Seconds $retryDelay - } - } - } while ($retryCount -gt 0) - } - - Write-Host "[$(Get-Date -Format t)] INFO: Importing dashboards for $($_.Value.FriendlyName) environment" -ForegroundColor Gray - # Add dashboards - foreach ($dashboard in $observabilityDashboardstoImport) { - $grafanaDBPath = "$AgMonitoringDir\grafana-$dashboard.json" - # Replace the datasource - $replacementParams = @{ - "\$\{DS_PROMETHEUS}" = $_.Value.GrafanaDataSource - } - $content = Get-Content $grafanaDBPath - foreach ($key in $replacementParams.Keys) { - $content = $content -replace $key, $replacementParams[$key] - } - # Set dashboard JSON - $dashboardObject = $content | ConvertFrom-Json - # Best practice is to generate a random UID, such as a GUID - $dashboardObject.uid = [guid]::NewGuid().ToString() - - # Need to set this to null to let Grafana generate a new ID - $dashboardObject.id = $null - # Set dashboard title - $dashboardObject.title = $_.Value.FriendlyName + ' - ' + $dashboardObject.title - # Request body with dashboard to add - $grafanaDBBody = @{ - dashboard = $dashboardObject - overwrite = $true - } | ConvertTo-Json -Depth 8 - - if ($_.Value.IsProduction) { - # Set Grafana Dashboard endpoint - $grafanaDBURI = $AgConfig.Monitoring["ProdURL"] + "/api/dashboards/db" - $grafanaDBStarURI = $AgConfig.Monitoring["ProdURL"] + "/api/user/stars/dashboard" - } - else { - # Set Grafana Dashboard endpoint - $grafanaDBURI = "http://$monitorLBIP/api/dashboards/db" - $grafanaDBStarURI = "http://$monitorLBIP/api/user/stars/dashboard" - } - - # Make HTTP request to the API - $dashboardID=(Invoke-RestMethod -Method Post -Uri $grafanaDBURI -Headers $adminHeaders -Body $grafanaDBBody).id - - Invoke-RestMethod -Method Post -Uri "$grafanaDBStarURI/$dashboardID" -Headers $userHeaders | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") - - } +Deploy-Workbook "arc-inventory-workbook.bicep" +##################################################################### +# Deploy Azure Workbook for OS Performance +##################################################################### +Deploy-Workbook "arc-osperformance-workbook.bicep" -} -Write-Host +##################################################################### +# Deploy Azure Data Explorer Dashboard Reports +##################################################################### +Deploy-ADXDashboardReports ############################################################## # Creating bookmarks ############################################################## Write-Host "[$(Get-Date -Format t)] INFO: Creating Microsoft Edge Bookmarks in Favorites Bar (Step 15/17)" -ForegroundColor DarkGreen -$bookmarksFileName = "$AgToolsDir\Bookmarks" -$edgeBookmarksPath = "$Env:LOCALAPPDATA\Microsoft\Edge\User Data\Default" - -foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { - kubectx $cluster.Name.ToLower() | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") - $services = kubectl get services --all-namespaces -o json | ConvertFrom-Json - - # Matching url: pos - customer - $matchingServices = $services.items | Where-Object { - $_.spec.ports.port -contains 5000 -and - $_.spec.type -eq "LoadBalancer" - } - $posIps = $matchingServices.status.loadBalancer.ingress.ip - - foreach ($posIp in $posIps) { - $output = "http://$posIp" + ':5000' - $output | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") - - # Replace matching value in the Bookmarks file - $content = Get-Content -Path $bookmarksFileName - $newContent = $content -replace ("POS-" + $cluster.Name + "-URL-Customer"), $output - $newContent | Set-Content -Path $bookmarksFileName - - Start-Sleep -Seconds 2 - } - - # Matching url: pos - manager - $matchingServices = $services.items | Where-Object { - $_.spec.ports.port -contains 81 -and - $_.spec.type -eq "LoadBalancer" - } - $posIps = $matchingServices.status.loadBalancer.ingress.ip - - foreach ($posIp in $posIps) { - $output = "http://$posIp" + ':81' - $output | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") - - # Replace matching value in the Bookmarks file - $content = Get-Content -Path $bookmarksFileName - $newContent = $content -replace ("POS-" + $cluster.Name + "-URL-Manager"), $output - $newContent | Set-Content -Path $bookmarksFileName - - Start-Sleep -Seconds 2 - } - - # Matching url: prometheus-grafana - if ($cluster.Name -eq "Staging" -or $cluster.Name -eq "Dev") { - $matchingServices = $services.items | Where-Object { - $_.metadata.name -eq 'prometheus-grafana' - } - $grafanaIps = $matchingServices.status.loadBalancer.ingress.ip - - foreach ($grafanaIp in $grafanaIps) { - $output = "http://$grafanaIp" - $output | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") - - # Replace matching value in the Bookmarks file - $content = Get-Content -Path $bookmarksFileName - $newContent = $content -replace ("Grafana-" + $cluster.Name + "-URL"), $output - $newContent | Set-Content -Path $bookmarksFileName - - Start-Sleep -Seconds 2 - } - } - - # Matching url: prometheus - $matchingServices = $services.items | Where-Object { - $_.spec.ports.port -contains 9090 -and - $_.spec.type -eq "LoadBalancer" - } - $prometheusIps = $matchingServices.status.loadBalancer.ingress.ip - - foreach ($prometheusIp in $prometheusIps) { - $output = "http://$prometheusIp" + ':9090' - $output | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") - - # Replace matching value in the Bookmarks file - $content = Get-Content -Path $bookmarksFileName - $newContent = $content -replace ("Prometheus-" + $cluster.Name + "-URL"), $output - $newContent | Set-Content -Path $bookmarksFileName - - Start-Sleep -Seconds 2 - } +if($industry -eq "retail"){ + Deploy-RetailBookmarks +}else{ + Deploy-ManufacturingBookmarks } -# Matching url: Agora apps forked repo -$output = $appClonedRepo -$output | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") - -# Replace matching value in the Bookmarks file -$content = Get-Content -Path $bookmarksFileName -$newContent = $content -replace "Agora-Apps-Repo-Clone-URL", $output -$newContent = $newContent -replace "Agora-Apps-Repo-Your-Fork", "Agora Apps Repo - $githubUser" -$newContent | Set-Content -Path $bookmarksFileName - -Start-Sleep -Seconds 2 - -Copy-Item -Path $bookmarksFileName -Destination $edgeBookmarksPath -Force - -############################################################## -# Pinning important directories to Quick access -############################################################## -Write-Host "[$(Get-Date -Format t)] INFO: Pinning important directories to Quick access (Step 16/17)" -ForegroundColor DarkGreen -$quickAccess = new-object -com shell.application -$quickAccess.Namespace($AgConfig.AgDirectories.AgDir).Self.InvokeVerb("pintohome") -$quickAccess.Namespace($AgConfig.AgDirectories.AgLogsDir).Self.InvokeVerb("pintohome") - - ############################################################## # Cleanup ############################################################## @@ -1817,14 +219,16 @@ Write-Host "[$(Get-Date -Format t)] INFO: Cleaning up scripts and uploading logs Write-Host "[$(Get-Date -Format t)] INFO: Creating Hyper-V desktop shortcut." -ForegroundColor Gray Copy-Item -Path "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools\Hyper-V Manager.lnk" -Destination "C:\Users\All Users\Desktop" -Force - -Write-Host "[$(Get-Date -Format t)] INFO: Cleaning up images-cache job" -ForegroundColor Gray -while ($(Get-Job -Name images-cache-cleanup).State -eq 'Running') { - Write-Host "[$(Get-Date -Format t)] INFO: Waiting for images-cache job to complete on all clusters...waiting 60 seconds" -ForegroundColor Gray - Receive-Job -Name images-cache-cleanup -WarningAction SilentlyContinue - Start-Sleep -Seconds 60 +if($industry -eq "retail"){ + Write-Host "[$(Get-Date -Format t)] INFO: Cleaning up images-cache job" -ForegroundColor Gray + while ($(Get-Job -Name images-cache-cleanup).State -eq 'Running') { + Write-Host "[$(Get-Date -Format t)] INFO: Waiting for images-cache job to complete on all clusters...waiting 60 seconds" -ForegroundColor Gray + Receive-Job -Name images-cache-cleanup -WarningAction SilentlyContinue + Start-Sleep -Seconds 60 + } + Get-Job -name images-cache-cleanup | Remove-Job } -Get-Job -name images-cache-cleanup | Remove-Job + # Removing the LogonScript Scheduled Task Write-Host "[$(Get-Date -Format t)] INFO: Removing scheduled logon task so it won't run on next login." -ForegroundColor Gray @@ -1861,8 +265,12 @@ namespace Win32{ Add-Type $code [Win32.Wallpaper]::SetWallpaper($imgPath) -Write-Host "[$(Get-Date -Format t)] INFO: Starting Docker Desktop" -ForegroundColor Green -Start-Process "C:\Program Files\Docker\Docker\Docker Desktop.exe" +# Kill the open PowerShell monitoring kubectl get pods +# if ($industry -eq "manufacturing") { +# foreach ($shell in $kubectlMonShells) { +# Stop-Process -Id $shell.Id +# } +# } $endTime = Get-Date $timeSpan = New-TimeSpan -Start $starttime -End $endtime diff --git a/azure_jumpstart_ag/artifacts/PowerShell/Bootstrap.ps1 b/azure_jumpstart_ag/artifacts/PowerShell/Bootstrap.ps1 index 65636bb1c9..7f10b42be4 100644 --- a/azure_jumpstart_ag/artifacts/PowerShell/Bootstrap.ps1 +++ b/azure_jumpstart_ag/artifacts/PowerShell/Bootstrap.ps1 @@ -3,6 +3,7 @@ param ( [string]$adminPassword, [string]$spnClientId, [string]$spnClientSecret, + [string]$spnObjectId, [string]$spnTenantId, [string]$spnAuthority, [string]$subscriptionId, @@ -15,14 +16,19 @@ param ( [string]$acrName, [string]$cosmosDBName, [string]$cosmosDBEndpoint, - [string]$githubUser, [string]$templateBaseUrl, [string]$rdpPort, [string]$githubAccount, [string]$githubBranch, [string]$githubPAT, + [string]$githubUser, [string]$adxClusterName, - [string]$namingGuid + [string]$namingGuid, + [string]$industry, + [string]$customLocationRPOID, + [string]$aioStorageAccountName, + [string]$stcontainerName, + [string]$AKSEEPinnedSchemaVersion ) ############################################################## @@ -32,6 +38,7 @@ param ( [System.Environment]::SetEnvironmentVariable('adminPassword', $adminPassword, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('spnClientID', $spnClientId, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('spnClientSecret', $spnClientSecret, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('spnObjectID', $spnObjectId, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('spnTenantId', $spnTenantId, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('spnAuthority', $spnAuthority, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('SPN_CLIENT_ID', $spnClientId, [System.EnvironmentVariableTarget]::Machine) @@ -48,14 +55,19 @@ param ( [System.Environment]::SetEnvironmentVariable('acrName', $acrName, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('cosmosDBName', $cosmosDBName, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('cosmosDBEndpoint', $cosmosDBEndpoint, [System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable('githubUser', $githubUser, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('templateBaseUrl', $templateBaseUrl, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('githubAccount', $githubAccount, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('githubBranch', $githubBranch, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('githubUser', $githubUser, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('GITHUB_TOKEN', $githubPAT, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('AgDir', "C:\Ag", [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('adxClusterName', $adxClusterName, [System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('namingGuid', $namingGuid, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('industry', $industry, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('customLocationRPOID', $customLocationRPOID, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('aioStorageAccountName', $aioStorageAccountName, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('stcontainerName', $stcontainerName, [System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('AKSEEPinnedSchemaVersion', $AKSEEPinnedSchemaVersion, [System.EnvironmentVariableTarget]::Machine) $ErrorActionPreference = 'Continue' @@ -95,14 +107,20 @@ if (($rdpPort -ne $null) -and ($rdpPort -ne "") -and ($rdpPort -ne "3389")) { # Download configuration data file and declaring directories ############################################################## $ConfigurationDataFile = "C:\Temp\AgConfig.psd1" -Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/AgConfig.psd1") -OutFile $ConfigurationDataFile -$AgConfig = Import-PowerShellDataFile -Path $ConfigurationDataFile -$AgDirectory = $AgConfig.AgDirectories["AgDir"] -$AgToolsDir = $AgConfig.AgDirectories["AgToolsDir"] -$AgIconsDir = $AgConfig.AgDirectories["AgIconDir"] -$AgPowerShellDir = $AgConfig.AgDirectories["AgPowerShellDir"] -$AgMonitoringDir = $AgConfig.AgDirectories["AgMonitoringDir"] -$websiteUrls = $AgConfig.URLs + +switch ($industry) { + "retail" { Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/AgConfig-retail.psd1") -OutFile $ConfigurationDataFile } + "manufacturing" {Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/AgConfig-manufacturing.psd1") -OutFile $ConfigurationDataFile} +} + +$AgConfig = Import-PowerShellDataFile -Path $ConfigurationDataFile +$AgDirectory = $AgConfig.AgDirectories["AgDir"] +$AgToolsDir = $AgConfig.AgDirectories["AgToolsDir"] +$AgDeploymentFolder = $AgConfig.AgDirectories["AgL1Files"] +$AgIconsDir = $AgConfig.AgDirectories["AgIconDir"] +$AgPowerShellDir = $AgConfig.AgDirectories["AgPowerShellDir"] +$AgMonitoringDir = $AgConfig.AgDirectories["AgMonitoringDir"] +$websiteUrls = $AgConfig.URLs function BITSRequest { Param( @@ -212,19 +230,35 @@ $latestRelease = (Invoke-RestMethod -Uri $websiteUrls["grafana"]).tag_name.repla # Download artifacts ############################################################## [System.Environment]::SetEnvironmentVariable('AgConfigPath', "$AgPowerShellDir\AgConfig.psd1", [System.EnvironmentVariableTarget]::Machine) +Copy-Item $ConfigurationDataFile "$AgPowerShellDir\AgConfig.psd1" -Force + Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/AgLogonScript.ps1") -OutFile "$AgPowerShellDir\AgLogonScript.ps1" -Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/AgConfig.psd1") -OutFile "$AgPowerShellDir\AgConfig.psd1" -Invoke-WebRequest ($templateBaseUrl + "artifacts/icons/grafana.ico") -OutFile $AgIconsDir\grafana.ico -Invoke-WebRequest ($templateBaseUrl + "artifacts/icons/contoso.png") -OutFile $AgIconsDir\contoso.png -Invoke-WebRequest ($templateBaseUrl + "artifacts/icons/contoso.svg") -OutFile $AgIconsDir\contoso.svg +Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/Modules/common.psm1") -OutFile "$AgPowerShellDir\common.psm1" +Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/Modules/retail.psm1") -OutFile "$AgPowerShellDir\retail.psm1" +Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/Modules/manufacturing.psm1") -OutFile "$AgPowerShellDir\manufacturing.psm1" Invoke-WebRequest ($templateBaseUrl + "artifacts/settings/DockerDesktopSettings.json") -OutFile "$AgToolsDir\settings.json" -Invoke-WebRequest ($templateBaseUrl + "artifacts/settings/Bookmarks") -OutFile "$AgToolsDir\Bookmarks" Invoke-WebRequest "https://raw.githubusercontent.com/Azure/arc_jumpstart_docs/main/img/wallpaper/agora_wallpaper_dark.png" -OutFile $AgDirectory\wallpaper.png - -Invoke-WebRequest ($templateBaseUrl + "artifacts/monitoring/grafana-freezer-monitoring.json") -OutFile "$AgMonitoringDir\grafana-freezer-monitoring.json" Invoke-WebRequest ($templateBaseUrl + "artifacts/monitoring/grafana-node-exporter-full.json") -OutFile "$AgMonitoringDir\grafana-node-exporter-full.json" Invoke-WebRequest ($templateBaseUrl + "artifacts/monitoring/grafana-cluster-global.json") -OutFile "$AgMonitoringDir\grafana-cluster-global.json" +Invoke-WebRequest ($templateBaseUrl + "artifacts/monitoring/arc-inventory-workbook.bicep") -OutFile "$AgMonitoringDir\arc-inventory-workbook.bicep" +Invoke-WebRequest ($templateBaseUrl + "artifacts/monitoring/arc-osperformance-workbook.bicep") -OutFile "$AgMonitoringDir\arc-osperformance-workbook.bicep" Invoke-WebRequest ($templateBaseUrl + "artifacts/monitoring/prometheus-additional-scrape-config.yaml") -OutFile "$AgMonitoringDir\prometheus-additional-scrape-config.yaml" +Invoke-WebRequest ($templateBaseUrl + "artifacts/icons/grafana.ico") -OutFile $AgIconsDir\grafana.ico +Invoke-WebRequest ($templateBaseUrl + "artifacts/icons/contoso.png") -OutFile $AgIconsDir\contoso.png +Invoke-WebRequest ($templateBaseUrl + "artifacts/icons/contoso.svg") -OutFile $AgIconsDir\contoso.svg +Invoke-WebRequest ($templateBaseUrl + "artifacts/icons/contoso-motors.png") -OutFile $AgIconsDir\contoso-motors.png +Invoke-WebRequest ($templateBaseUrl + "artifacts/icons/contoso-motors.svg") -OutFile $AgIconsDir\contoso-motors.svg +Invoke-WebRequest ($templateBaseUrl + "artifacts/L1Files/config.json") -OutFile $AgDeploymentFolder\config.json + +if($industry -eq "retail"){ + Invoke-WebRequest ($templateBaseUrl + "artifacts/settings/Bookmarks-retail") -OutFile "$AgToolsDir\Bookmarks" + Invoke-WebRequest ($templateBaseUrl + "artifacts/monitoring/grafana-freezer-monitoring.json") -OutFile "$AgMonitoringDir\grafana-freezer-monitoring.json" +} +elseif ($industry -eq "manufacturing") { + Invoke-WebRequest ($templateBaseUrl + "artifacts/settings/Bookmarks-manufacturing") -OutFile "$AgToolsDir\Bookmarks" + Invoke-WebRequest ($templateBaseUrl + "artifacts/settings/mq_cloudConnector.yml") -OutFile "$AgToolsDir\mq_cloudConnector.yml" + Invoke-WebRequest ($templateBaseUrl + "artifacts/settings/mqtt_explorer_settings.json") -OutFile "$AgToolsDir\mqtt_explorer_settings.json" +} BITSRequest -Params @{'Uri' = 'https://aka.ms/wslubuntu'; 'Filename' = "$AgToolsDir\Ubuntu.appx" } BITSRequest -Params @{'Uri' = $websiteUrls["wslStoreStorage"]; 'Filename' = "$AgToolsDir\wsl_update_x64.msi" } diff --git a/azure_jumpstart_ag/artifacts/PowerShell/Modules/common.psm1 b/azure_jumpstart_ag/artifacts/PowerShell/Modules/common.psm1 new file mode 100644 index 0000000000..33e9792f0c --- /dev/null +++ b/azure_jumpstart_ag/artifacts/PowerShell/Modules/common.psm1 @@ -0,0 +1,1052 @@ +function Deploy-AzCLI { + $cliDir = New-Item -Path ($AgConfig.AgDirectories["AgLogsDir"] + "\.cli\") -Name ".Ag" -ItemType Directory + + if (-not $($cliDir.Parent.Attributes.HasFlag([System.IO.FileAttributes]::Hidden))) { + $folder = Get-Item $cliDir.Parent.FullName -ErrorAction SilentlyContinue + $folder.Attributes += [System.IO.FileAttributes]::Hidden + } + + $Env:AZURE_CONFIG_DIR = $cliDir.FullName + + # Making extension install dynamic + if ($AgConfig.AzCLIExtensions.Count -ne 0) { + Write-Host "[$(Get-Date -Format t)] INFO: Installing Azure CLI extensions: " ($AgConfig.AzCLIExtensions -join ', ') -ForegroundColor Gray + az config set extension.use_dynamic_install=yes_without_prompt --only-show-errors + # Installing Azure CLI extensions + foreach ($extension in $AgConfig.AzCLIExtensions) { + az extension add --name $extension --system --only-show-errors + } + } + + Write-Host "[$(Get-Date -Format t)] INFO: Az CLI configuration complete!" -ForegroundColor Green + Write-Host +} + +function Deploy-AzPowerShell { + $azurePassword = ConvertTo-SecureString $Env:spnClientSecret -AsPlainText -Force + $psCred = New-Object System.Management.Automation.PSCredential($Env:spnClientID , $azurePassword) + Connect-AzAccount -Credential $psCred -TenantId $Env:spnTenantId -ServicePrincipal -Subscription $subscriptionId | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\AzPowerShell.log") + + # Install PowerShell modules + if ($AgConfig.PowerShellModules.Count -ne 0) { + Write-Host "[$(Get-Date -Format t)] INFO: Installing PowerShell modules: " ($AgConfig.PowerShellModules -join ', ') -ForegroundColor Gray + foreach ($module in $AgConfig.PowerShellModules) { + Install-Module -Name $module -Force | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\AzPowerShell.log") + } + } + + # Register Azure providers + if ($AgConfig.AzureProviders.Count -ne 0) { + Write-Host "[$(Get-Date -Format t)] INFO: Registering Azure providers in the current subscription: " ($AgConfig.AzureProviders -join ', ') -ForegroundColor Gray + foreach ($provider in $AgConfig.AzureProviders) { + Register-AzResourceProvider -ProviderNamespace $provider | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\AzPowerShell.log") + } + } + Write-Host "[$(Get-Date -Format t)] INFO: Azure PowerShell configuration and resource provider registration complete!" -ForegroundColor Green + Write-Host +} + +function Deploy-WindowsTools { + $DevToolsInstallationJob = Invoke-Command -ScriptBlock { + $AgConfig = $using:AgConfig + $websiteUrls = $using:websiteUrls + $AgToolsDir = $using:AgToolsDir + $adminUsername = $using:adminUsername + + + If ($PSVersionTable.PSVersion.Major -ge 7) { Write-Error "This script needs be run by version of PowerShell prior to 7.0" } + $downloadDir = "C:\WinTerminal" + $frameworkPkgPath = "$downloadDir\Microsoft.VCLibs.x64.14.00.Desktop.appx" + $WindowsTerminalKitPath = "$downloadDir\Microsoft.WindowsTerminal.PreinstallKit.zip" + $windowsTerminalPath = "$downloadDir\WindowsTerminal" + $filenamePattern = "*PreinstallKit.zip" + $terminalDownloadUri = ((Invoke-RestMethod -Method GET -Uri $websiteUrls["windowsTerminal"]).assets | Where-Object name -like $filenamePattern ).browser_download_url | Select-Object -First 1 + + # Download C++ Runtime framework packages for Desktop Bridge and Windows Terminal latest release + Write-Host "[$(Get-Date -Format t)] INFO: Downloading binaries." -ForegroundColor Gray + + $ProgressPreference = 'SilentlyContinue' + + Invoke-WebRequest -Uri $websiteUrls["vcLibs"] -OutFile ( New-Item -Path $frameworkPkgPath -Force ) | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") + Invoke-WebRequest -Uri $terminalDownloadUri -OutFile ( New-Item -Path $windowsTerminalKitPath -Force ) | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") + + $ProgressPreference = 'Continue' + + # Extract Windows Terminal PreinstallKit + Write-Host "[$(Get-Date -Format t)] INFO: Expanding Windows Terminal PreinstallKit." -ForegroundColor Gray + Expand-Archive $WindowsTerminalKitPath $windowsTerminalPath | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") + + # Install WSL latest kernel update + Write-Host "[$(Get-Date -Format t)] INFO: Installing WSL." -ForegroundColor Gray + msiexec /i "$AgToolsDir\wsl_update_x64.msi" /qn | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") + + # Install C++ Runtime framework packages for Desktop Bridge and Windows Terminal latest release + Write-Host "[$(Get-Date -Format t)] INFO: Installing Windows Terminal" -ForegroundColor Gray + Add-AppxPackage -Path $frameworkPkgPath | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") + + # Install the Windows Terminal prereqs + foreach ($file in Get-ChildItem $windowsTerminalPath -Filter *x64*.appx) { + Add-AppxPackage -Path $file.FullName | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") + } + + # Install Windows Terminal + foreach ($file in Get-ChildItem $windowsTerminalPath -Filter *.msixbundle) { + Add-AppxPackage -Path $file.FullName | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") + } + + # Configure Windows Terminal + Set-Location $Env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminal*\LocalState + + # Launch Windows Terminal for default settings.json to be created + $action = New-ScheduledTaskAction -Execute $((Get-Command wt.exe).Source) + $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddSeconds(1) + $null = Register-ScheduledTask -Action $action -Trigger $trigger -TaskName WindowsTerminalInit + + # Give process time to initiate and create settings file + Start-Sleep 10 + + # Stop Windows Terminal process + Get-Process WindowsTerminal | Stop-Process + + Unregister-ScheduledTask -TaskName WindowsTerminalInit -Confirm:$false + + $settings = Get-Content .\settings.json | ConvertFrom-Json + $settings.profiles.defaults.elevate + + # Configure the default profile setting "Run this profile as Administrator" to "true" + $settings.profiles.defaults | Add-Member -Name elevate -MemberType NoteProperty -Value $true -Force + + $settings | ConvertTo-Json -Depth 8 | Set-Content .\settings.json + + # Install Ubuntu + Write-Host "[$(Get-Date -Format t)] INFO: Installing Ubuntu" -ForegroundColor Gray + Add-AppxPackage -Path "$AgToolsDir\Ubuntu.appx" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") + + # Setting WSL environment variables + $userenv = [System.Environment]::GetEnvironmentVariable("Path", "User") + [System.Environment]::SetEnvironmentVariable("PATH", $userenv + ";C:\Users\$adminUsername\Ubuntu", "User") + + # Initializing the wsl ubuntu app without requiring user input + $ubuntu_path = "c:/users/$adminUsername/AppData/Local/Microsoft/WindowsApps/ubuntu" + Invoke-Expression -Command "$ubuntu_path install --root" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") + + # Create Windows Terminal shortcut + $WshShell = New-Object -comObject WScript.Shell + $WinTerminalPath = (Get-ChildItem "C:\Program Files\WindowsApps" -Recurse | Where-Object { $_.name -eq "wt.exe" }).FullName + $Shortcut = $WshShell.CreateShortcut("$Env:USERPROFILE\Desktop\Windows Terminal.lnk") + $Shortcut.TargetPath = $WinTerminalPath + $shortcut.WindowStyle = 3 + $shortcut.Save() + + ############################################################# + # Install VSCode extensions + ############################################################# + Write-Host "[$(Get-Date -Format t)] INFO: Installing VSCode extensions: " + ($AgConfig.VSCodeExtensions -join ', ') -ForegroundColor Gray + # Install VSCode extensions + foreach ($extension in $AgConfig.VSCodeExtensions) { + code --install-extension $extension 2>&1 | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Tools.log") + } + + ############################################################# + # Install Docker Desktop + ############################################################# + Write-Host "[$(Get-Date -Format t)] INFO: Installing Docker Desktop." -ForegroundColor DarkGreen + # Download and Install Docker Desktop + $arguments = 'install --quiet --accept-license' + Start-Process "$AgToolsDir\DockerDesktopInstaller.exe" -Wait -ArgumentList $arguments + Get-ChildItem "$Env:USERPROFILE\Desktop\Docker Desktop.lnk" | Remove-Item -Confirm:$false + Copy-Item "$AgToolsDir\settings.json" -Destination "$Env:USERPROFILE\AppData\Roaming\Docker\settings.json" -Force + Start-Process "C:\Program Files\Docker\Docker\Docker Desktop.exe" + Start-Sleep -Seconds 25 + Get-Process | Where-Object { $_.name -like "Docker Desktop" } | Stop-Process -Force + # Cleanup + Remove-Item $downloadDir -Recurse -Force + + } -JobName step3 -ThrottleLimit 16 -AsJob -ComputerName . + + Write-Host "[$(Get-Date -Format t)] INFO: Dev Tools installation initiated in background job." -ForegroundColor Green + + $DevToolsInstallationJob + + Write-Host +} + +function Deploy-VirtualizationInfrastructure { + $password = ConvertTo-SecureString $AgConfig.L1Password -AsPlainText -Force + $Credentials = New-Object System.Management.Automation.PSCredential($AgConfig.L1Username, $password) + + # Turn the .kube folder to a shared folder where all Kubernetes kubeconfig files will be copied to + $kubeFolder = "$Env:USERPROFILE\.kube" + New-Item -ItemType Directory $kubeFolder -Force | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + New-SmbShare -Name "kube" -Path "$Env:USERPROFILE\.kube" -FullAccess "Everyone" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + + # Enable Enhanced Session Mode on Host + Write-Host "[$(Get-Date -Format t)] INFO: Enabling Enhanced Session Mode on Hyper-V host" -ForegroundColor Gray + Set-VMHost -EnableEnhancedSessionMode $true | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + + # Create Internal Hyper-V switch for the L1 nested virtual machines + New-VMSwitch -Name $AgConfig.L1SwitchName -SwitchType Internal | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + $ifIndex = (Get-NetAdapter -Name ("vEthernet (" + $AgConfig.L1SwitchName + ")")).ifIndex + New-NetIPAddress -IPAddress $AgConfig.L1DefaultGateway -PrefixLength 24 -InterfaceIndex $ifIndex | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + New-NetNat -Name $AgConfig.L1SwitchName -InternalIPInterfaceAddressPrefix $AgConfig.L1NatSubnetPrefix | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + + ##################################################################### + # Deploying the nested L1 virtual machines + ##################################################################### + Write-Host "[$(Get-Date -Format t)] INFO: Fetching Windows 11 IoT Enterprise VM image from Azure storage. This may take a few minutes." -ForegroundColor Yellow + # azcopy cp $AgConfig.PreProdVHDBlobURL $AgConfig.AgDirectories["AgVHDXDir"] --recursive=true --check-length=false --log-level=ERROR | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + azcopy cp $AgConfig.ProdVHDBlobURL $AgConfig.AgDirectories["AgVHDXDir"] --recursive=true --check-length=false --log-level=ERROR | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + + # Create three virtual machines from the base VHDX image + $vhdxPath = Get-ChildItem $AgConfig.AgDirectories["AgVHDXDir"] -Filter *.vhdx | Select-Object -ExpandProperty FullName + foreach ($site in $AgConfig.SiteConfig.GetEnumerator()) { + if ($site.Value.Type -eq "AKSEE") { + # Create disks for each site host + Write-Host "[$(Get-Date -Format t)] INFO: Creating $($site.Name) disk." -ForegroundColor Gray + $destVhdxPath = "$($AgConfig.AgDirectories["AgVHDXDir"])\$($site.Name)Disk.vhdx" + $destPath = $AgConfig.AgDirectories["AgVHDXDir"] + New-VHD -ParentPath $vhdxPath -Path $destVhdxPath -Differencing | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + + # Create a new virtual machine and attach the existing virtual hard disk + Write-Host "[$(Get-Date -Format t)] INFO: Creating and configuring $($site.Name) virtual machine." -ForegroundColor Gray + + New-VM -Name $site.Name ` + -Path $destPath ` + -MemoryStartupBytes $AgConfig.L1VMMemory ` + -BootDevice VHD ` + -VHDPath $destVhdxPath ` + -Generation 2 ` + -Switch $AgConfig.L1SwitchName | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + + # Set up the virtual machine before coping all AKS Edge Essentials automation files + Set-VMProcessor -VMName $site.Name ` + -Count $AgConfig.L1VMNumVCPU ` + -ExposeVirtualizationExtensions $true | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + + Get-VMNetworkAdapter -VMName $site.Name | Set-VMNetworkAdapter -MacAddressSpoofing On | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + Enable-VMIntegrationService -VMName $site.Name -Name "Guest Service Interface" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + + # Start the virtual machine + Start-VM -Name $site.Name | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + } + } + + Start-Sleep -Seconds 20 + # Create an array with VM names + $VMnames = (Get-VM).Name + + $sourcePath = "$PsHome\Profile.ps1" + $destinationPath = "C:\Deployment\Profile.ps1" + $maxRetries = 3 + + foreach ($VM in $VMNames) { + $retryCount = 0 + $copySucceeded = $false + + while (-not $copySucceeded -and $retryCount -lt $maxRetries) { + try { + Copy-VMFile $VM -SourcePath $sourcePath -DestinationPath $destinationPath -CreateFullPath -FileSource Host -Force -ErrorAction Stop + $copySucceeded = $true + Write-Host "File copied to $VM successfully." + } + catch { + $retryCount++ + Write-Host "Attempt $retryCount : File copy to $VM failed. Retrying..." + Start-Sleep -Seconds 30 # Wait for 30 seconds before retrying + } + } + + if (-not $copySucceeded) { + Write-Host "File copy to $VM failed after $maxRetries attempts." + } + } + + ######################################################################## + # Prepare L1 nested virtual machines for AKS Edge Essentials bootstrap + ######################################################################## + foreach ($site in $AgConfig.SiteConfig.GetEnumerator()) { + if ($site.Value.Type -eq "AKSEE") { + Write-Host "[$(Get-Date -Format t)] INFO: Renaming computer name of $($site.Name)" -ForegroundColor Gray + $ErrorActionPreference = "SilentlyContinue" + Invoke-Command -VMName $site.Name -Credential $Credentials -ScriptBlock { + $site = $using:site + (gwmi win32_computersystem).Rename($site.Name) + } | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + $ErrorActionPreference = "Continue" + Stop-VM -Name $site.Name -Force -Confirm:$false + Start-VM -Name $site.Name + } + } + + foreach ($VM in $VMNames) { + $VMStatus = Get-VMIntegrationService -VMName $VM -Name Heartbeat + while ($VMStatus.PrimaryStatusDescription -ne "OK") { + $VMStatus = Get-VMIntegrationService -VMName $VM -Name Heartbeat + write-host "[$(Get-Date -Format t)] INFO: Waiting for $VM to finish booting." -ForegroundColor Gray + Start-Sleep -Seconds 5 + } + } + + Write-Host "[$(Get-Date -Format t)] INFO: Fetching the latest two AKS Edge Essentials releases." -ForegroundColor Gray + $AKSEESchemaVersions = @() + if($AKSEEPinnedSchemaVersion -eq "useLatest"){ + $latestReleaseTag = (Invoke-WebRequest $websiteUrls["aksEEReleases"] | ConvertFrom-Json)[0].tag_name + $beforeLatestReleaseTag = (Invoke-WebRequest $websiteUrls["aksEEReleases"] | ConvertFrom-Json)[1].tag_name + $AKSEEReleasesTags = ($latestReleaseTag, $beforeLatestReleaseTag) + + for ($i = 0; $i -lt $AKSEEReleasesTags.Count; $i++) { + $releaseTag = (Invoke-WebRequest $websiteUrls["aksEEReleases"] | ConvertFrom-Json)[$i].tag_name + $AKSEEReleaseDownloadUrl = "https://github.com/Azure/AKS-Edge/archive/refs/tags/$releaseTag.zip" + $output = Join-Path $AgToolsDir "$releaseTag.zip" + Invoke-WebRequest $AKSEEReleaseDownloadUrl -OutFile $output + Expand-Archive $output -DestinationPath $AgToolsDir -Force + $AKSEEReleaseConfigFilePath = "$AgToolsDir\AKS-Edge-$releaseTag\tools\aksedge-config.json" + $jsonContent = Get-Content -Raw -Path $AKSEEReleaseConfigFilePath | ConvertFrom-Json + $schemaVersion = $jsonContent.SchemaVersion + $AKSEESchemaVersions += $schemaVersion + # Clean up the downloaded release files + Remove-Item -Path $output -Force + Remove-Item -Path "$AgToolsDir\AKS-Edge-$releaseTag" -Force -Recurse + } + } + + Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { + $hostname = hostname + $ProgressPreference = "SilentlyContinue" + ########################################### + # Preparing environment folders structure + ########################################### + Write-Host "[$(Get-Date -Format t)] INFO: Preparing folder structure on $hostname." -ForegroundColor Gray + $deploymentFolder = "C:\Deployment" # Deployment folder is already pre-created in the VHD image + $logsFolder = "$deploymentFolder\Logs" + $kubeFolder = "$Env:USERPROFILE\.kube" + + # Set up an array of folders + $folders = @($logsFolder, $kubeFolder) + + # Loop through each folder and create it + foreach ($Folder in $folders) { + New-Item -ItemType Directory $Folder -Force + } + } | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + + Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { + # Start logging + $hostname = hostname + $ProgressPreference = "SilentlyContinue" + $deploymentFolder = "C:\Deployment" # Deployment folder is already pre-created in the VHD image + $logsFolder = "$deploymentFolder\Logs" + Start-Transcript -Path $logsFolder\AKSEEBootstrap.log + $AgConfig = $using:AgConfig + $AgToolsDir = $using:AgToolsDir + $websiteUrls = $using:websiteUrls + $AKSEEPinnedSchemaVersion = $using:AKSEEPinnedSchemaVersion + + ########################################## + # Deploying AKS Edge Essentials clusters + ######################################### + + # Assigning network adapter IP address + $NetIPAddress = $AgConfig.SiteConfig[$Env:COMPUTERNAME].NetIPAddress + $DefaultGateway = $AgConfig.SiteConfig[$Env:COMPUTERNAME].DefaultGateway + $PrefixLength = $AgConfig.SiteConfig[$Env:COMPUTERNAME].PrefixLength + $DNSClientServerAddress = $AgConfig.SiteConfig[$Env:COMPUTERNAME].DNSClientServerAddress + Write-Host "[$(Get-Date -Format t)] INFO: Configuring networking interface on $hostname with IP address $NetIPAddress." -ForegroundColor Gray + $AdapterName = (Get-NetAdapter -Name Ethernet*).Name + $ifIndex = (Get-NetAdapter -Name $AdapterName).ifIndex + New-NetIPAddress -IPAddress $NetIPAddress -DefaultGateway $DefaultGateway -PrefixLength $PrefixLength -InterfaceIndex $ifIndex | Out-Null + Set-DNSClientServerAddress -InterfaceIndex $ifIndex -ServerAddresses $DNSClientServerAddress | Out-Null + + ########################################### + # Validating internet connectivity + ########################################### + $timeElapsed = 0 + do { + Write-Host "[$(Get-Date -Format t)] INFO: Waiting for internet connection to be healthy on $hostname." -ForegroundColor Gray + Start-Sleep -Seconds 5 + $timeElapsed = $timeElapsed + 10 + } until ((Test-Connection bing.com -Count 1 -ErrorAction SilentlyContinue) -or ($timeElapsed -eq 60)) + + # Fetching latest AKS Edge Essentials msi file + Write-Host "[$(Get-Date -Format t)] INFO: Fetching latest AKS Edge Essentials install file on $hostname." -ForegroundColor Gray + Invoke-WebRequest $websiteUrls["aksEEk3s"] -OutFile $deploymentFolder\AKSEEK3s.msi + + # Fetching required GitHub artifacts from Jumpstart repository + Write-Host "[$(Get-Date -Format t)] INFO: Fetching GitHub artifacts" -ForegroundColor Gray + $repoName = "azure_arc" # While testing, change to your GitHub fork's repository name + $githubApiUrl = "https://api.github.com/repos/$using:githubAccount/$repoName/contents/azure_jumpstart_ag/artifacts/L1Files?ref=$using:githubBranch" + $response = Invoke-RestMethod -Uri $githubApiUrl + $fileUrls = $response | Where-Object { $_.type -eq "file" } | Select-Object -ExpandProperty download_url + $fileUrls | ForEach-Object { + $fileName = $_.Substring($_.LastIndexOf("/") + 1) + $outputFile = Join-Path $deploymentFolder $fileName + Invoke-RestMethod -Uri $_ -OutFile $outputFile + } + + ############################################################################### + # Setting up replacement parameters for AKS Edge Essentials config json file + ############################################################################### + Write-Host "[$(Get-Date -Format t)] INFO: Building AKS Edge Essentials config json file on $hostname." -ForegroundColor Gray + $AKSEEConfigFilePath = "$deploymentFolder\ScalableCluster.json" + $AdapterName = (Get-NetAdapter -Name Ethernet*).Name + $namingGuid = $using:namingGuid + $arcClusterName = $AgConfig.SiteConfig[$Env:COMPUTERNAME].ArcClusterName + "-$namingGuid" + + # Fetch schemaVersion release from the AgConfig file + $AKSEESchemaVersionUseLatest = $AgConfig.SiteConfig[$Env:COMPUTERNAME].AKSEEReleaseUseLatest + if ($AKSEESchemaVersionUseLatest -and $AKSEEPinnedSchemaVersion -eq "useLatest") { + $SchemaVersion = $using:AKSEESchemaVersions[0] + } + elseif (!$AKSEESchemaVersionUseLatest -and $AKSEEPinnedSchemaVersion -eq "useLatest") { + $SchemaVersion = $using:AKSEESchemaVersions[1] + } + elseif ($AKSEEPinnedSchemaVersion -ne "useLatest") { + $SchemaVersion = $AKSEEPinnedSchemaVersion + } + + $replacementParams = @{ + "SchemaVersion-null" = $SchemaVersion + "ServiceIPRangeStart-null" = $AgConfig.SiteConfig[$Env:COMPUTERNAME].ServiceIPRangeStart + "1000" = $AgConfig.SiteConfig[$Env:COMPUTERNAME].ServiceIPRangeSize + "ControlPlaneEndpointIp-null" = $AgConfig.SiteConfig[$Env:COMPUTERNAME].ControlPlaneEndpointIp + "Ip4GatewayAddress-null" = $AgConfig.SiteConfig[$Env:COMPUTERNAME].DefaultGateway + "2000" = $AgConfig.SiteConfig[$Env:COMPUTERNAME].PrefixLength + "DnsServer-null" = $AgConfig.SiteConfig[$Env:COMPUTERNAME].DNSClientServerAddress + "Ethernet-Null" = $AdapterName + "Ip4Address-null" = $AgConfig.SiteConfig[$Env:COMPUTERNAME].LinuxNodeIp4Address + "ClusterName-null" = $arcClusterName + "Location-null" = $using:azureLocation + "ResourceGroupName-null" = $using:resourceGroup + "SubscriptionId-null" = $using:subscriptionId + "TenantId-null" = $using:spnTenantId + "ClientId-null" = $using:spnClientId + "ClientSecret-null" = $using:spnClientSecret + } + + ################################################### + # Preparing AKS Edge Essentials config json file + ################################################### + $content = Get-Content $AKSEEConfigFilePath + foreach ($key in $replacementParams.Keys) { + $content = $content -replace $key, $replacementParams[$key] + } + Set-Content "$deploymentFolder\Config.json" -Value $content + } + Write-Host "[$(Get-Date -Format t)] INFO: Initial L1 virtualization infrastructure configuration complete." -ForegroundColor Green + Write-Host + + Write-Host "[$(Get-Date -Format t)] INFO: Installing AKS Edge Essentials (Step 7/17)" -ForegroundColor DarkGreen + foreach ($VMName in $VMNames) { + $Session = New-PSSession -VMName $VMName -Credential $Credentials + Write-Host "[$(Get-Date -Format t)] INFO: Rebooting $VMName." -ForegroundColor Gray + Invoke-Command -Session $Session -ScriptBlock { + $Action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -File C:\Deployment\AKSEEBootstrap.ps1" + $Trigger = New-ScheduledTaskTrigger -AtStartup + Register-ScheduledTask -TaskName "Startup Scan" -Action $Action -Trigger $Trigger -User $Env:USERNAME -Password 'Agora123!!' -RunLevel Highest | Out-Null + Restart-Computer -Force -Confirm:$false + } | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1AKSInfra.log") + Remove-PSSession $Session | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1AKSInfra.log") + } + + Write-Host "[$(Get-Date -Format t)] INFO: Sleeping for three (3) minutes to allow for AKS EE installs to complete." -ForegroundColor Gray + Start-Sleep -Seconds 180 # Give some time for the AKS EE installs to complete. This will take a few minutes. + + ##################################################################### + # Monitor until the kubeconfig files are detected and copied over + ##################################################################### + $elapsedTime = Measure-Command { + foreach ($VMName in $VMNames) { + $path = "C:\Users\Administrator\.kube\config-" + $VMName.ToLower() + $user = $AgConfig.L1Username + [securestring]$secStringPassword = ConvertTo-SecureString $AgConfig.L1Password -AsPlainText -Force + $credential = New-Object System.Management.Automation.PSCredential($user, $secStringPassword) + Start-Sleep 5 + while (!(Invoke-Command -VMName $VMName -Credential $credential -ScriptBlock { Test-Path $using:path })) { + Start-Sleep 30 + Write-Host "[$(Get-Date -Format t)] INFO: Waiting for AKS Edge Essentials kubeconfig to be available on $VMName." -ForegroundColor Gray + } + + Write-Host "[$(Get-Date -Format t)] INFO: $VMName's kubeconfig is ready - copying over config-$VMName" -ForegroundColor DarkGreen + $destinationPath = $Env:USERPROFILE + "\.kube\config-" + $VMName + $s = New-PSSession -VMName $VMName -Credential $credential + Copy-Item -FromSession $s -Path $path -Destination $destinationPath + $file = Get-Item $destinationPath + if ($file.Length -eq 0) { + Write-Host "[$(Get-Date -Format t)] ERROR: Kubeconfig on $VMName is corrupt. This error is unrecoverable. Exiting." -ForegroundColor White -BackgroundColor Red + exit 1 + } + } + } + + # Display the elapsed time in seconds it took for kubeconfig files to show up in folder + Write-Host "[$(Get-Date -Format t)] INFO: Waiting on kubeconfig files took $($elapsedTime.ToString("g"))." -ForegroundColor Gray + + ##################################################################### + # Merging kubeconfig files on the L0 virtual machine + ##################################################################### + Write-Host "[$(Get-Date -Format t)] INFO: All three kubeconfig files are present. Merging kubeconfig files for use with kubectx." -ForegroundColor Gray + $kubeconfigpath = "" + foreach ($VMName in $VMNames) { + $kubeconfigpath = $kubeconfigpath + "$Env:USERPROFILE\.kube\config-" + $VMName.ToLower() + ";" + } + $Env:KUBECONFIG = $kubeconfigpath + kubectl config view --merge --flatten > "$Env:USERPROFILE\.kube\config-raw" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1AKSInfra.log") + kubectl config get-clusters --kubeconfig="$Env:USERPROFILE\.kube\config-raw" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1AKSInfra.log") + Rename-Item -Path "$Env:USERPROFILE\.kube\config-raw" -NewName "$Env:USERPROFILE\.kube\config" + $Env:KUBECONFIG = "$Env:USERPROFILE\.kube\config" + + # Print a message indicating that the merge is complete + Write-Host "[$(Get-Date -Format t)] INFO: All three kubeconfig files merged successfully." -ForegroundColor Gray + + # Validate context switching using kubectx & kubectl + foreach ($cluster in $VMNames) { + Write-Host "[$(Get-Date -Format t)] INFO: Testing connectivity to kube api on $cluster cluster." -ForegroundColor Gray + kubectx $cluster.ToLower() + kubectl get nodes -o wide + } + Write-Host "[$(Get-Date -Format t)] INFO: AKS Edge Essentials installs are complete!" -ForegroundColor Green + Write-Host +} + +function Deploy-AzContainerRegistry { + az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\AzCLI.log") + az account set -s $subscriptionId + az aks get-credentials --resource-group $Env:resourceGroup --name $Env:aksStagingClusterName --admin | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") + kubectx staging="$Env:aksStagingClusterName-admin" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") + + # Attach ACR to staging cluster + Write-Host "[$(Get-Date -Format t)] INFO: Attaching Azure Container Registry to AKS staging cluster." -ForegroundColor Gray + az aks update -n $Env:aksStagingClusterName -g $Env:resourceGroup --attach-acr $acrName | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") +} + +function Deploy-ClusterNamespaces { + foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { + $clusterName = $cluster.Name.ToLower() + kubectx $clusterName | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") + foreach ($namespace in $AgConfig.Namespaces) { + Write-Host "[$(Get-Date -Format t)] INFO: Creating namespace $namespace on $clusterName" -ForegroundColor Gray + kubectl create namespace $namespace | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") + } + } +} + +function Deploy-ClusterSecrets { + foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { + $clusterName = $cluster.Name.ToLower() + foreach ($namespace in $AgConfig.Namespaces) { + if ($namespace -eq "contoso-supermarket" -or $namespace -eq "images-cache") { + Write-Host "[$(Get-Date -Format t)] INFO: Configuring Azure Container registry on $clusterName" + kubectx $clusterName | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") + kubectl create secret docker-registry acr-secret ` + --namespace $namespace ` + --docker-server="$acrName.azurecr.io" ` + --docker-username="$Env:spnClientId" ` + --docker-password="$Env:spnClientSecret" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") + } + } + } + + ##################################################################### + # Create secrets for GitHub actions + ##################################################################### + if ($Env:industry -eq "retail") { + Write-Host "[$(Get-Date -Format t)] INFO: Creating Kubernetes secrets" -ForegroundColor Gray + $cosmosDBKey = $(az cosmosdb keys list --name $cosmosDBName --resource-group $resourceGroup --query primaryMasterKey --output tsv) + foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { + $clusterName = $cluster.Name.ToLower() + Write-Host "[$(Get-Date -Format t)] INFO: Creating Kubernetes secrets on $clusterName" -ForegroundColor Gray + foreach ($namespace in $AgConfig.Namespaces) { + if ($namespace -eq "contoso-supermarket" -or $namespace -eq "images-cache") { + kubectx $cluster.Name.ToLower() | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") + kubectl create secret generic postgrespw --from-literal=POSTGRES_PASSWORD='Agora123!!' --namespace $namespace | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") + kubectl create secret generic cosmoskey --from-literal=COSMOS_KEY=$cosmosDBKey --namespace $namespace | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") + kubectl create secret generic github-token --from-literal=token=$githubPat --namespace $namespace | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") + } + } + } + Write-Host "[$(Get-Date -Format t)] INFO: Cluster secrets configuration complete." -ForegroundColor Green + Write-Host + } +} + +function Deploy-AzArcK8s { + # Running pre-checks to ensure that the aksedge ConfigMap is present on all clusters + $maxRetries = 5 + $retryInterval = 30 # seconds + $retryCount = 0 + foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { + $clusterName = $cluster.Name.ToLower() + if ($clusterName -ne "staging") { + while ($retryCount -lt $maxRetries) { + kubectx $clusterName + $configMap = kubectl get configmap -n aksedge aksedge + if ($null -eq $configMap) { + $retryCount++ + Write-Host "Retry ${retryCount}/${maxRetries}: aksedge ConfigMap not found on $clusterName. Retrying in $retryInterval seconds..." | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ArcConnectivity.log") + Start-Sleep -Seconds $retryInterval + } + else { + # ConfigMap found, continue with the rest of the script + Write-Host "aksedge ConfigMap found on $clusterName. Continuing with the script..." | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ArcConnectivity.log") + break # Exit the loop + } + } + + if ($retryCount -eq $maxRetries) { + Write-Host "[$(Get-Date -Format t)] ERROR: aksedge ConfigMap not found on $clusterName. Exiting..." -ForegroundColor White -BackgroundColor Red | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ArcConnectivity.log") + exit 1 # Exit the script + } + } + } + $VMnames = (Get-VM).Name + foreach ($VM in $VMNames) { + $secret = $Env:spnClientSecret + $clientId = $Env:spnClientId + $tenantId = $Env:spnTenantId + $location = $Env:azureLocation + $resourceGroup = $Env:resourceGroup + + Invoke-Command -VMName $VM -Credential $Credentials -ScriptBlock { + # Install prerequisites + . C:\Deployment\Profile.ps1 + $hostname = hostname + $ProgressPreference = "SilentlyContinue" + Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force + Install-Module Az.Resources -Repository PSGallery -Force -AllowClobber -ErrorAction Stop + Install-Module Az.Accounts -Repository PSGallery -Force -AllowClobber -ErrorAction Stop + Install-Module Az.ConnectedKubernetes -Repository PSGallery -Force -AllowClobber -ErrorAction Stop + Install-Module Az.ConnectedMachine -Force -AllowClobber -ErrorAction Stop + + # Connect servers to Arc + $azurePassword = ConvertTo-SecureString $using:secret -AsPlainText -Force + $psCred = New-Object System.Management.Automation.PSCredential($using:clientId, $azurePassword) + Connect-AzAccount -Credential $psCred -TenantId $using:tenantId -ServicePrincipal -Subscription $using:subscriptionId + Write-Host "[$(Get-Date -Format t)] INFO: Arc-enabling $hostname server." -ForegroundColor Gray + Redo-Command -ScriptBlock { Connect-AzConnectedMachine -ResourceGroupName $using:resourceGroup -Name "Ag-$hostname-Host" -Location $using:location } + + # Connect clusters to Arc + $deploymentPath = "C:\Deployment\config.json" + Write-Host "[$(Get-Date -Format t)] INFO: Arc-enabling $hostname AKS Edge Essentials cluster." -ForegroundColor Gray + + kubectl get svc + + $retryCount = 5 # Number of times to retry the operation + $retryDelay = 30 # Delay in seconds between retries + + for ($retry = 1; $retry -le $retryCount; $retry++) { + $return = Connect-AksEdgeArc -JsonConfigFilePath $deploymentPath + if ($return -ne "OK") { + Write-Output "Failed to onboard AKS Edge Essentials cluster to Azure Arc. Retrying (Attempt $retry of $retryCount)..." + if ($retry -lt $retryCount) { + Start-Sleep -Seconds $retryDelay # Wait before retrying + } + else { + Write-Output "Exceeded maximum retry attempts. Exiting." + break # Exit the loop after the maximum number of retries + } + } + else { + Write-Output "Successfully onboarded AKS Edge Essentials cluster to Azure Arc." + break # Exit the loop if the connection is successful + } + } + + + } 2>&1 | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ArcConnectivity.log") + } + + ##################################################################### + # Tag Azure Arc resources + ##################################################################### + $arcResourceTypes = $AgConfig.ArcServerResourceType, $AgConfig.ArcK8sResourceType + $Tag = @{$AgConfig.TagName = $AgConfig.TagValue } + + # Iterate over the Arc resources and tag it + foreach ($arcResourceType in $arcResourceTypes) { + $arcResources = Get-AzResource -ResourceType $arcResourceType -ResourceGroupName $Env:resourceGroup + foreach ($arcResource in $arcResources) { + Update-AzTag -ResourceId $arcResource.Id -Tag $Tag -Operation Merge | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ArcConnectivity.log") + } + } + + Write-Host "[$(Get-Date -Format t)] INFO: AKS Edge Essentials clusters and hosts have been registered with Azure Arc!" -ForegroundColor Green + Write-Host + +} + +function Deploy-ClusterFluxExtension { + $resourceTypes = @($AgConfig.ArcK8sResourceType, $AgConfig.AksResourceType) + $resources = Get-AzResource -ResourceGroupName $Env:resourceGroup | Where-Object { $_.ResourceType -in $resourceTypes } + + $jobs = @() + foreach ($resource in $resources) { + $resourceName = $resource.Name + $resourceType = $resource.Type + + Write-Host "[$(Get-Date -Format t)] INFO: Installing flux extension on $resourceName" -ForegroundColor Gray + $job = Start-Job -Name $resourceName -ScriptBlock { + param($resourceName, $resourceType) + + $retryCount = 10 + $retryDelaySeconds = 60 + + switch ($resourceType) { + 'Microsoft.Kubernetes/connectedClusters' { $ClusterType = 'ConnectedClusters' } + 'Microsoft.ContainerService/managedClusters' { $ClusterType = 'ManagedClusters' } + } + if ($clusterType -eq 'ConnectedClusters') { + # Check if cluster is connected to Azure Arc control plane + $ConnectivityStatus = (Get-AzConnectedKubernetes -ResourceGroupName $Env:resourceGroup -ClusterName $resourceName).ConnectivityStatus + if (-not ($ConnectivityStatus -eq 'Connected')) { + for ($attempt = 1; $attempt -le $retryCount; $attempt++) { + $ConnectivityStatus = (Get-AzConnectedKubernetes -ResourceGroupName $Env:resourceGroup -ClusterName $resourceName).ConnectivityStatus + + # Check the condition + if ($ConnectivityStatus -eq 'Connected') { + # Condition is true, break out of the loop + break + } + + # Wait for a specific duration before re-evaluating the condition + Start-Sleep -Seconds $retryDelaySeconds + + + if ($attempt -lt $retryCount) { + Write-Host "Retrying in $retryDelaySeconds seconds..." + Start-Sleep -Seconds $retryDelaySeconds + } + else { + $ProvisioningState = "Timed out after $($retryDelaySeconds * $retryCount) seconds while waiting for cluster to become connected to Azure Arc control plane. Current status: $ConnectivityStatus" + break # Max retry attempts reached, exit the loop + } + + } + } + } + + az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId + $extension = az k8s-extension list --cluster-name $resourceName --resource-group $Env:resourceGroup --cluster-type $ClusterType --output json | ConvertFrom-Json + $extension = $extension | Where-Object extensionType -eq 'microsoft.flux' + + if ($extension.ProvisioningState -ne 'Succeeded' -and ($ConnectivityStatus -eq 'Connected' -or $clusterType -eq "ManagedClusters")) { + for ($attempt = 1; $attempt -le $retryCount; $attempt++) { + try { + if ($extension) { + az k8s-extension delete --name "flux" --cluster-name $resourceName --resource-group $Env:resourceGroup --cluster-type $ClusterType --force --yes + } + az k8s-extension create --name "flux" --extension-type "microsoft.flux" --cluster-name $resourceName --resource-group $Env:resourceGroup --cluster-type $ClusterType --output json | ConvertFrom-Json -OutVariable extension + break # Command succeeded, exit the loop + } + catch { + Write-Warning "An error occurred: $($_.Exception.Message)" + + if ($attempt -lt $retryCount) { + Write-Host "Retrying in $retryDelaySeconds seconds..." + Start-Sleep -Seconds $retryDelaySeconds + } + else { + Write-Error "Failed to execute the command after $retryCount attempts." + $ProvisioningState = $($_.Exception.Message) + break # Max retry attempts reached, exit the loop + } + } + } + } + $ProvisioningState = $extension.ProvisioningState + [PSCustomObject]@{ + ResourceName = $resourceName + ResourceType = $resourceType + ProvisioningState = $ProvisioningState + } + } -ArgumentList $resourceName, $resourceType + $jobs += $job + } + + # Wait for all jobs to complete + $FluxExtensionJobs = $jobs | Wait-Job | Receive-Job -Keep + $jobs | Format-Table Name, PSBeginTime, PSEndTime -AutoSize + + # Clean up jobs + $jobs | Remove-Job + # Abort if Flux-extension fails on any cluster + # if ($FluxExtensionJobs | Where-Object ProvisioningState -ne 'Succeeded') { + # throw "One or more Flux-extension deployments failed - aborting" + # } +} + +function Deploy-Workbook ($workbookFileName) { + $AgMonitoringDir = $AgConfig.AgDirectories["AgMonitoringDir"] + Write-Host "[$(Get-Date -Format t)] INFO: Deploying Azure Workbook $workbookFileName." + Write-Host "`n" + $workbookTemplateFilePath = "$AgMonitoringDir\$workbookFileName" + # Read the content of the workbook template-file + $content = Get-Content -Path $workbookTemplateFilePath -Raw + # Replace placeholders with actual values + $updatedContent = $content -replace 'rg-placeholder', $resourceGroup + $updatedContent = $updatedContent -replace'/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/xxxx/providers/Microsoft.OperationalInsights/workspaces/xxxx', "/subscriptions/$($subscriptionId)/resourceGroups/$($Env:resourceGroup)/providers/Microsoft.OperationalInsights/workspaces/$($Env:workspaceName)" + $updatedContent = $updatedContent -replace'/subscriptions/00000000-0000-0000-0000-000000000000', "/subscriptions/$($subscriptionId)" + + # Write the updated content back to the file + Set-Content -Path $workbookTemplateFilePath -Value $updatedContent + # Deploy the workbook + try { + New-AzResourceGroupDeployment -ResourceGroupName $Env:resourceGroup -TemplateFile $workbookTemplateFilePath -ErrorAction Stop + Write-Host "[$(Get-Date -Format t)] INFO: Deployment of template-file $workbookTemplateFilePath succeeded." + } catch { + Write-Error "[$(Get-Date -Format t)] ERROR: Deployment of template-file $workbookTemplateFilePath failed. Error details: $PSItem.Exception.Message" + } +} + +function Deploy-Prometheus { + param ( + $AgConfig + ) + $AgMonitoringDir = $AgConfig.AgDirectories["AgMonitoringDir"] + $observabilityNamespace = $AgConfig.Monitoring["Namespace"] + $observabilityDashboards = $AgConfig.Monitoring["Dashboards"] + $adminPassword = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($env:adminPassword)) + + # Set Prod Grafana API endpoint + $grafanaDS = $AgConfig.Monitoring["ProdURL"] + "/api/datasources" + + # Installing Grafana + Write-Host "[$(Get-Date -Format t)] INFO: Installing and Configuring Observability components (Step 14/17)" -ForegroundColor DarkGreen + Write-Host "[$(Get-Date -Format t)] INFO: Installing Grafana." -ForegroundColor Gray + $latestRelease = (Invoke-WebRequest -Uri $websiteUrls["grafana"] | ConvertFrom-Json).tag_name.replace('v', '') + Start-Process msiexec.exe -Wait -ArgumentList "/I $AgToolsDir\grafana-$latestRelease.windows-amd64.msi /quiet" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") + + # Update Prometheus Helm charts + helm repo add prometheus-community $websiteUrls["prometheus"] | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") + helm repo update | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") + + if ($Env:industry -eq "retail") { + # Update Grafana Icons + Copy-Item -Path $AgIconsDir\contoso.png -Destination "C:\Program Files\GrafanaLabs\grafana\public\img" + Copy-Item -Path $AgIconsDir\contoso.svg -Destination "C:\Program Files\GrafanaLabs\grafana\public\img\grafana_icon.svg" + + Get-ChildItem -Path 'C:\Program Files\GrafanaLabs\grafana\public\build\*.js' -Recurse -File | ForEach-Object { + (Get-Content $_.FullName) -replace 'className:u,src:"public/img/grafana_icon.svg"', 'className:u,src:"public/img/contoso.png"' | Set-Content $_.FullName + } + + # Reset Grafana UI + Get-ChildItem -Path 'C:\Program Files\GrafanaLabs\grafana\public\build\*.js' -Recurse -File | ForEach-Object { + (Get-Content $_.FullName) -replace 'Welcome to Grafana', 'Welcome to Grafana for Contoso Supermarket Production' | Set-Content $_.FullName + } + } + elseif ($Env:industry -eq "manufacturing") { + # Update Grafana Icons + Copy-Item -Path $AgIconsDir\contoso-motors.png -Destination "C:\Program Files\GrafanaLabs\grafana\public\img" + Copy-Item -Path $AgIconsDir\contoso-motors.svg -Destination "C:\Program Files\GrafanaLabs\grafana\public\img\grafana_icon.svg" + + Get-ChildItem -Path 'C:\Program Files\GrafanaLabs\grafana\public\build\*.js' -Recurse -File | ForEach-Object { + (Get-Content $_.FullName) -replace 'className:u,src:"public/img/grafana_icon.svg"', 'className:u,src:"public/img/contoso-motors.png"' | Set-Content $_.FullName + } + + # Reset Grafana UI + Get-ChildItem -Path 'C:\Program Files\GrafanaLabs\grafana\public\build\*.js' -Recurse -File | ForEach-Object { + (Get-Content $_.FullName) -replace 'Welcome to Grafana', 'Welcome to Grafana for Contoso Motors' | Set-Content $_.FullName + } + } + + # Reset Grafana Password + $Env:Path += ';C:\Program Files\GrafanaLabs\grafana\bin' + $retryCount = 5 + $retryDelay = 30 + do { + try { + grafana-cli --homepath "C:\Program Files\GrafanaLabs\grafana" admin reset-admin-password $adminPassword | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") + $retryCount = 0 + } + catch { + $retryCount-- + if ($retryCount -gt 0) { + Write-Host "[$(Get-Date -Format t)] INFO: Retrying in $retryDelay seconds..." -ForegroundColor Gray + Start-Sleep -Seconds $retryDelay + } + } + } while ($retryCount -gt 0) + + # Get Grafana Admin credentials + $adminCredentials = $AgConfig.Monitoring["AdminUser"] + ':' + $adminPassword + $adminEncodedcredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($adminCredentials)) + + $adminHeaders = @{ + "Authorization" = ("Basic " + $adminEncodedcredentials) + "Content-Type" = "application/json" + } + + # Get Contoso User credentials + $userCredentials = $adminUsername + ':' + $adminPassword + $userEncodedcredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($userCredentials)) + + $userHeaders = @{ + "Authorization" = ("Basic " + $userEncodedcredentials) + "Content-Type" = "application/json" + } + + # Download dashboards + foreach ($dashboard in $observabilityDashboards.'grafana.com') { + $grafanaDBPath = "$AgMonitoringDir\grafana-$dashboard.json" + $dashboardmetadata = Invoke-RestMethod -Uri https://grafana.com/api/dashboards/$dashboard/revisions + $dashboardversion = $dashboardmetadata.items | Sort-Object revision | Select-Object -Last 1 | Select-Object -ExpandProperty revision + Invoke-WebRequest https://grafana.com/api/dashboards/$dashboard/revisions/$dashboardversion/download -OutFile $grafanaDBPath + } + + $observabilityDashboardstoImport = @() + $observabilityDashboardstoImport += $observabilityDashboards.'grafana.com' + $observabilityDashboardstoImport += $observabilityDashboards.'custom' + + Write-Host "[$(Get-Date -Format t)] INFO: Creating Prod Grafana User" -ForegroundColor Gray + # Add Contoso Operator User + $grafanaUserBody = @{ + name = $AgConfig.Monitoring["User"] # Display Name + email = $AgConfig.Monitoring["Email"] + login = $adminUsername + password = $adminPassword + } | ConvertTo-Json + + # Make HTTP request to the API to create user + $retryCount = 10 + $retryDelay = 30 + do { + try { + Invoke-RestMethod -Method Post -Uri "$($AgConfig.Monitoring["ProdURL"])/api/admin/users" -Headers $adminHeaders -Body $grafanaUserBody | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") + $retryCount = 0 + } + catch { + $retryCount-- + if ($retryCount -gt 0) { + Write-Host "[$(Get-Date -Format t)] INFO: Retrying in $retryDelay seconds..." -ForegroundColor Gray + Start-Sleep -Seconds $retryDelay + } + } + } while ($retryCount -gt 0) + + # Deploying Kube Prometheus Stack for stores + $AgConfig.SiteConfig.GetEnumerator() | ForEach-Object { + Write-Host "[$(Get-Date -Format t)] INFO: Deploying Kube Prometheus Stack for $($_.Value.FriendlyName) environment" -ForegroundColor Gray + kubectx $_.Value.FriendlyName.ToLower() | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") + + # Wait for Kubernetes API server to become available + $apiServer = kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}' + $apiServerAddress = $apiServer -replace '.*https://| .*$' + $apiServerFqdn = ($apiServerAddress -split ":")[0] + $apiServerPort = ($apiServerAddress -split ":")[1] + + do { + $result = Test-NetConnection -ComputerName $apiServerFqdn -Port $apiServerPort -WarningAction SilentlyContinue + if ($result.TcpTestSucceeded) { + Write-Host "[$(Get-Date -Format t)] INFO: Kubernetes API server $apiServer is available" -ForegroundColor Gray + break + } + else { + Write-Host "[$(Get-Date -Format t)] INFO: Kubernetes API server $apiServer is not yet available. Retrying in 10 seconds..." -ForegroundColor Gray + Start-Sleep -Seconds 10 + } + } while ($true) + + # Install Prometheus Operator + $helmSetValue = $_.Value.HelmSetValue -replace 'adminPasswordPlaceholder', $adminPassword + helm install prometheus prometheus-community/kube-prometheus-stack --set $helmSetValue --namespace $observabilityNamespace --create-namespace --values "$AgMonitoringDir\$($_.Value.HelmValuesFile)" | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") + + Do { + Write-Host "[$(Get-Date -Format t)] INFO: Waiting for $($_.Value.FriendlyName) monitoring service to provision.." -ForegroundColor Gray + Start-Sleep -Seconds 45 + $monitorIP = $(if (kubectl get $_.Value.HelmService --namespace $observabilityNamespace --output=jsonpath='{.status.loadBalancer}' | Select-String "ingress" -Quiet) { "Ready!" }Else { "Nope" }) + } while ($monitorIP -eq "Nope" ) + # Get Load Balancer IP + $monitorLBIP = kubectl --namespace $observabilityNamespace get $_.Value.HelmService --output=jsonpath='{.status.loadBalancer.ingress[0].ip}' + + if ($_.Value.IsProduction) { + Write-Host "[$(Get-Date -Format t)] INFO: Add $($_.Value.FriendlyName) Data Source to Grafana" + # Request body with information about the data source to add + $grafanaDSBody = @{ + name = $_.Value.FriendlyName.ToLower() + type = 'prometheus' + url = ("http://" + $monitorLBIP + ":9090") + access = 'proxy' + basicAuth = $false + isDefault = $true + } | ConvertTo-Json + + # Make HTTP request to the API + Invoke-RestMethod -Method Post -Uri $grafanaDS -Headers $adminHeaders -Body $grafanaDSBody | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") + } + + # Add Contoso Operator User + if (!$_.Value.IsProduction) { + Write-Host "[$(Get-Date -Format t)] INFO: Creating $($_.Value.FriendlyName) Grafana User" -ForegroundColor Gray + $grafanaUserBody = @{ + name = $AgConfig.Monitoring["User"] # Display Name + email = $AgConfig.Monitoring["Email"] + login = $adminUsername + password = $adminPassword + } | ConvertTo-Json + + # Make HTTP request to the API to create user + $retryCount = 10 + $retryDelay = 30 + + do { + try { + Invoke-RestMethod -Method Post -Uri "http://$monitorLBIP/api/admin/users" -Headers $adminHeaders -Body $grafanaUserBody | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") + $retryCount = 0 + } + catch { + $retryCount-- + if ($retryCount -gt 0) { + Write-Host "[$(Get-Date -Format t)] INFO: Retrying in $retryDelay seconds..." -ForegroundColor Gray + Start-Sleep -Seconds $retryDelay + } + } + } while ($retryCount -gt 0) + } + + Write-Host "[$(Get-Date -Format t)] INFO: Importing dashboards for $($_.Value.FriendlyName) environment" -ForegroundColor Gray + # Add dashboards + foreach ($dashboard in $observabilityDashboardstoImport) { + $grafanaDBPath = "$AgMonitoringDir\grafana-$dashboard.json" + # Replace the datasource + $replacementParams = @{ + "\$\{DS_PROMETHEUS}" = $_.Value.GrafanaDataSource + } + $content = Get-Content $grafanaDBPath + foreach ($key in $replacementParams.Keys) { + $content = $content -replace $key, $replacementParams[$key] + } + # Set dashboard JSON + $dashboardObject = $content | ConvertFrom-Json + # Best practice is to generate a random UID, such as a GUID + $dashboardObject.uid = [guid]::NewGuid().ToString() + + # Need to set this to null to let Grafana generate a new ID + $dashboardObject.id = $null + # Set dashboard title + $dashboardObject.title = $_.Value.FriendlyName + ' - ' + $dashboardObject.title + # Request body with dashboard to add + $grafanaDBBody = @{ + dashboard = $dashboardObject + overwrite = $true + } | ConvertTo-Json -Depth 8 + + if ($_.Value.IsProduction) { + # Set Grafana Dashboard endpoint + $grafanaDBURI = $AgConfig.Monitoring["ProdURL"] + "/api/dashboards/db" + $grafanaDBStarURI = $AgConfig.Monitoring["ProdURL"] + "/api/user/stars/dashboard" + } + else { + # Set Grafana Dashboard endpoint + $grafanaDBURI = "http://$monitorLBIP/api/dashboards/db" + $grafanaDBStarURI = "http://$monitorLBIP/api/user/stars/dashboard" + } + + # Make HTTP request to the API + $dashboardID = (Invoke-RestMethod -Method Post -Uri $grafanaDBURI -Headers $adminHeaders -Body $grafanaDBBody).id + + Invoke-RestMethod -Method Post -Uri "$grafanaDBStarURI/$dashboardID" -Headers $userHeaders | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Observability.log") + + } + + } + Write-Host +} diff --git a/azure_jumpstart_ag/artifacts/PowerShell/Modules/manufacturing.psm1 b/azure_jumpstart_ag/artifacts/PowerShell/Modules/manufacturing.psm1 new file mode 100644 index 0000000000..0466f07161 --- /dev/null +++ b/azure_jumpstart_ag/artifacts/PowerShell/Modules/manufacturing.psm1 @@ -0,0 +1,568 @@ +function Deploy-ManufacturingConfigs { + Write-Host "[$(Get-Date -Format t)] INFO: Configuring OVMS prerequisites on Kubernetes nodes." -ForegroundColor Gray + $VMs = (Get-VM).Name + foreach ($VM in $VMs) { + Invoke-Command -VMName $VM -Credential $Credentials -ScriptBlock { + Invoke-AksEdgeNodeCommand -NodeType Linux -command "curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.27.0/install.sh | bash -s v0.27.0" + } + kubectx $VM.ToLower() + kubectl create -f https://operatorhub.io/install/ovms-operator.yaml + } + + # Loop through the clusters and deploy the configs in AppConfig hashtable in AgConfig-manufacturing.psd1 + foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { + Start-Job -Name gitops -ScriptBlock { + $AgConfig = $using:AgConfig + $cluster = $using:cluster + $namingGuid = $using:namingGuid + $resourceGroup = $using:resourceGroup + $appClonedRepo = $using:appUpstreamRepo + $appsRepo = $using:appsRepo + + $AgConfig.AppConfig.GetEnumerator() | sort-object -Property @{Expression = { $_.value.Order }; Ascending = $true } | ForEach-Object { + $app = $_ + $clusterName = $cluster.value.ArcClusterName + "-$namingGuid" + $branch = $cluster.value.Branch.ToLower() + $configName = $app.value.GitOpsConfigName.ToLower() + $namespace = $app.value.Namespace + $appName = $app.Value.KustomizationName + $appPath = $app.Value.KustomizationPath + $retryCount = 0 + $maxRetries = 2 + + Write-Host "[$(Get-Date -Format t)] INFO: Creating GitOps config for $configName on $($cluster.Value.ArcClusterName+"-$namingGuid")" -ForegroundColor Gray + $type = "connectedClusters" + + # Wait for Kubernetes API server to become available + $apiServer = kubectl config view --context $cluster.Name.ToLower() --minify -o jsonpath='{.clusters[0].cluster.server}' + $apiServerAddress = $apiServer -replace '.*https://| .*$' + $apiServerFqdn = ($apiServerAddress -split ":")[0] + $apiServerPort = ($apiServerAddress -split ":")[1] + + do { + $result = Test-NetConnection -ComputerName $apiServerFqdn -Port $apiServerPort -WarningAction SilentlyContinue + if ($result.TcpTestSucceeded) { + break + } + else { + Start-Sleep -Seconds 5 + } + } while ($true) + + + az k8s-configuration flux create ` + --cluster-name $clusterName ` + --resource-group $resourceGroup ` + --name $configName ` + --cluster-type $type ` + --scope cluster ` + --url $appClonedRepo ` + --branch $branch ` + --sync-interval 3s ` + --kustomization name=$appName path=$appPath prune=true retry_interval=1m ` + --timeout 10m ` + --namespace $namespace ` + --only-show-errors ` + 2>&1 | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + + do { + $configStatus = $(az k8s-configuration flux show --name $configName --cluster-name $clusterName --cluster-type $type --resource-group $resourceGroup -o json 2>$null) | convertFrom-JSON + if ($configStatus.ComplianceState -eq "Compliant") { + Write-Host "[$(Get-Date -Format t)] INFO: GitOps configuration $configName is ready on $clusterName" -ForegroundColor DarkGreen | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + } + else { + if ($configStatus.ComplianceState -ne "Non-compliant") { + Start-Sleep -Seconds 20 + } + elseif ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -lt $maxRetries) { + Start-Sleep -Seconds 20 + $configStatus = $(az k8s-configuration flux show --name $configName --cluster-name $clusterName --cluster-type $type --resource-group $resourceGroup -o json 2>$null) | convertFrom-JSON + if ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -lt $maxRetries) { + $retryCount++ + Write-Host "[$(Get-Date -Format t)] INFO: Attempting to re-install $configName on $clusterName" -ForegroundColor Gray | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + Write-Host "[$(Get-Date -Format t)] INFO: Deleting $configName on $clusterName" -ForegroundColor Gray | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + az k8s-configuration flux delete ` + --resource-group $resourceGroup ` + --cluster-name $clusterName ` + --cluster-type $type ` + --name $configName ` + --force ` + --yes ` + --only-show-errors ` + 2>&1 | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + + Start-Sleep -Seconds 10 + Write-Host "[$(Get-Date -Format t)] INFO: Re-creating $configName on $clusterName" -ForegroundColor Gray | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + + az k8s-configuration flux create ` + --cluster-name $clusterName ` + --resource-group $resourceGroup ` + --name $configName ` + --cluster-type $type ` + --scope cluster ` + --url $appClonedRepo ` + --branch $branch ` + --sync-interval 3s ` + --kustomization name=$appName path=$appPath prune=true ` + --timeout 30m ` + --namespace $namespace ` + --only-show-errors ` + 2>&1 | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + } + } + elseif ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -eq $maxRetries) { + Write-Host "[$(Get-Date -Format t)] ERROR: GitOps configuration $configName has failed on $clusterName. Exiting..." -ForegroundColor White -BackgroundColor Red | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + break + } + } + } until ($configStatus.ComplianceState -eq "Compliant") + } + } + } + + while ($(Get-Job -Name gitops).State -eq 'Running') { + #Write-Host "[$(Get-Date -Format t)] INFO: Waiting for GitOps configuration to complete on all clusters...waiting 60 seconds" -ForegroundColor Gray + Receive-Job -Name gitops -WarningAction SilentlyContinue + Start-Sleep -Seconds 60 + } + + Get-Job -name gitops | Remove-Job + Write-Host "[$(Get-Date -Format t)] INFO: GitOps configuration complete." -ForegroundColor Green + Write-Host +} + +function Deploy-AIO { + # Deploys Azure IoT Operations on all k8s clusters in the config file + + ############################################################## + # Preparing clusters for aio + ############################################################## + $VMnames = $AgConfig.SiteConfig.GetEnumerator().Name.ToLower() + + Invoke-Command -VMName $VMnames -Credential $Credentials -ScriptBlock { + $ProgressPreference = "SilentlyContinue" + ########################################### + # Preparing environment folders structure + ########################################### + Write-Host "[$(Get-Date -Format t)] INFO: Preparing AKSEE clusters for AIO" -ForegroundColor DarkGray + Write-Host "`n" + try { + $localPathProvisionerYaml = "https://raw.githubusercontent.com/Azure/AKS-Edge/main/samples/storage/local-path-provisioner/local-path-storage.yaml" + & kubectl apply -f $localPathProvisionerYaml + $pvcYaml = @" + apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: local-path-pvc + namespace: default + spec: + accessModes: + - ReadWriteOnce + storageClassName: local-path + resources: + requests: + storage: 15Gi +"@ + + $pvcYaml | kubectl apply -f - + + Write-Host "Successfully deployment the local path provisioner" + } + catch { + Write-Host "Error: local path provisioner deployment failed" -ForegroundColor Red + } + + Write-Host "Configuring firewall specific to AIO" + Write-Host "Add firewall rule for AIO MQTT Broker" + New-NetFirewallRule -DisplayName "AIO MQTT Broker" -Direction Inbound -Action Allow | Out-Null + try { + $deploymentInfo = Get-AksEdgeDeploymentInfo + # Get the service ip address start to determine the connect address + $connectAddress = $deploymentInfo.LinuxNodeConfig.ServiceIpRange.split("-")[0] + $portProxyRulExists = netsh interface portproxy show v4tov4 | findstr /C:"1883" | findstr /C:"$connectAddress" + if ( $null -eq $portProxyRulExists ) { + Write-Host "Configure port proxy for AIO" + netsh interface portproxy add v4tov4 listenport=1883 listenaddress=0.0.0.0 connectport=1883 connectaddress=$connectAddress | Out-Null + netsh interface portproxy add v4tov4 listenport=1883 listenaddress=0.0.0.0 connectport=18883 connectaddress=$connectAddress | Out-Null + netsh interface portproxy add v4tov4 listenport=1883 listenaddress=0.0.0.0 connectport=8883 connectaddress=$connectAddress | Out-Null + } + else { + Write-Host "Port proxy rule for AIO exists, skip configuring port proxy..." + } + } + catch { + Write-Host "Error: port proxy update for aio failed" -ForegroundColor Red + } + Write-Host "Update the iptables rules" + try { + $iptableRulesExist = Invoke-AksEdgeNodeCommand -NodeType "Linux" -command "sudo iptables-save | grep -- '-m tcp --dport 9110 -j ACCEPT'" -ignoreError + if ( $null -eq $iptableRulesExist ) { + Invoke-AksEdgeNodeCommand -NodeType "Linux" -command "sudo iptables -A INPUT -p tcp -m state --state NEW -m tcp --dport 9110 -j ACCEPT" + Write-Host "Updated runtime iptable rules for node exporter" + Invoke-AksEdgeNodeCommand -NodeType "Linux" -command "sudo sed -i '/-A OUTPUT -j ACCEPT/i-A INPUT -p tcp -m tcp --dport 9110 -j ACCEPT' /etc/systemd/scripts/ip4save" + Write-Host "Persisted iptable rules for node exporter" + # increase the maximum number of files + Invoke-AksEdgeNodeCommand -NodeType "Linux" -Command "echo 'fs.inotify.max_user_instances = 1024' | sudo tee -a /etc/sysctl.conf && sudo sysctl -p" + } + else { + Write-Host "iptable rule exists, skip configuring iptable rules..." + } + } + catch { + Write-Host "Error: iptable rule update failed" -ForegroundColor Red + } + } | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\L1Infra.log") + + ############################################################# + # Deploying AIO on the clusters + ############################################################# + + Write-Host "[$(Get-Date -Format t)] INFO: Deploying AIO to the clusters" -ForegroundColor DarkGray + Write-Host "`n" + $kvIndex = 0 + + foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { + $clusterName = $cluster.Name.ToLower() + Write-Host "[$(Get-Date -Format t)] INFO: Deploying AIO to the $clusterName cluster" -ForegroundColor Gray + Write-Host "`n" + kubectx $clusterName + $arcClusterName = $AgConfig.SiteConfig[$clusterName].ArcClusterName + "-$namingGuid" + $keyVaultId = (az keyvault list -g $resourceGroup --resource-type vault --query "[$kvIndex].id" -o tsv) + $retryCount = 0 + $maxRetries = 5 + $aioStatus = "notDeployed" + + # Enable custom locations on the Arc-enabled cluster + Write-Host "[$(Get-Date -Format t)] INFO: Enabling custom locations on the Arc-enabled cluster" -ForegroundColor DarkGray + Write-Host "`n" + az config set extension.use_dynamic_install=yes_without_prompt + az connectedk8s enable-features --name $arcClusterName ` + --resource-group $resourceGroup ` + --features cluster-connect custom-locations ` + --custom-locations-oid $customLocationRPOID ` + --only-show-errors + + do { + az iot ops init --cluster $arcClusterName -g $resourceGroup --kv-id $keyVaultId --sp-app-id $spnClientId --sp-secret $spnClientSecret --sp-object-id $spnObjectId --broker-service-type loadBalancer --add-insecure-listener true --simulate-plc false --no-block --only-show-errors + if ($? -eq $false) { + $aioStatus = "notDeployed" + Write-Host "`n" + Write-Host "[$(Get-Date -Format t)] Error: An error occured while deploying AIO on the cluster...Retrying" -ForegroundColor DarkRed + Write-Host "`n" + az iot ops init --cluster $arcClusterName -g $resourceGroup --kv-id $keyVaultId --sp-app-id $spnClientId --sp-secret $spnClientSecret --sp-object-id $spnObjectId --broker-service-type loadBalancer --add-insecure-listener true --simulate-plc false --no-block --only-show-errors + $retryCount++ + } + else { + $aioStatus = "deployed" + } + } until ($aioStatus -eq "deployed" -or $retryCount -eq $maxRetries) + $kvIndex++ + } + foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { + $clusterName = $cluster.Name.ToLower() + $retryCount = 0 + $maxRetries = 25 + kubectx $clusterName + do { + $output = az iot ops check --as-object --only-show-errors + $output = $output | ConvertFrom-Json + $mqServiceStatus = ($output.postDeployment | Where-Object { $_.name -eq "evalBrokerListeners" }).status + if ($mqServiceStatus -ne "Success") { + Write-Host "Waiting for AIO to be deployed successfully on $clusterName...waiting for 60 seconds" -ForegroundColor DarkGray + Start-Sleep -Seconds 60 + $retryCount++ + } + } until ($mqServiceStatus -eq "Success" -or $retryCount -eq $maxRetries) + + if ($retryCount -eq $maxRetries) { + Write-Host "[$(Get-Date -Format t)] ERROR: AIO deployment failed. Exiting..." -ForegroundColor White -BackgroundColor Red + exit 1 # Exit the script + } + #Write-Host "AIO deployed successfully on the $clusterName cluster" -ForegroundColor Green + #Write-Host "`n" + Write-Host "[$(Get-Date -Format t)] INFO: Started Event Grid role assignment process" -ForegroundColor DarkGray + $extensionPrincipalId = (az k8s-extension list --cluster-name $arcClusterName --resource-group $resourceGroup --cluster-type "connectedClusters" --query "[?extensionType=='microsoft.iotoperations']" --output json | ConvertFrom-Json).identity.principalId + $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 $eventGridNamespacePrincipalId --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 $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'}}" + az eventgrid namespace update -g $resourceGroup -n $eventGridNamespaceName --topic-spaces-configuration $eventGridConfig --only-show-errors + + Start-Sleep -Seconds 60 + + ## Adding MQTT bridge to Event Grid MQTT + $mqconfigfile = "$AgToolsDir\mq_cloudConnector.yml" + Copy-Item $mqconfigfile "$AgToolsDir\mq_cloudConnector_$clusterName.yml" -Force + $bridgeConfig = "$AgToolsDir\mq_cloudConnector_$clusterName.yml" + (Get-Content $bridgeConfig) -replace 'clusterName', $clusterName | Set-Content $bridgeConfig + Write-Host "[$(Get-Date -Format t)] INFO: Configuring the MQ Event Grid bridge" -ForegroundColor DarkGray + $eventGridHostName = (az eventgrid namespace list --resource-group $resourceGroup --query "[0].topicSpacesConfiguration.hostname" -o tsv --only-show-errors) + (Get-Content -Path $bridgeConfig) -replace 'eventGridPlaceholder', $eventGridHostName | Set-Content -Path $bridgeConfig + kubectl apply -f $bridgeConfig -n $aioNamespace + + ## Patching MQTT listener + } +} + +function Set-MQTTIpAddress { + $mqttIpArray = @() + $clusters = $AgConfig.SiteConfig.GetEnumerator() + foreach ($cluster in $clusters) { + $clusterName = $cluster.Name.ToLower() + kubectx $clusterName | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\ClusterSecrets.log") + Write-Host "[$(Get-Date -Format t)] INFO: Getting MQ IP address" -ForegroundColor DarkGray + + do { + $mqttIp = kubectl get service $mqListenerService -n $aioNamespace -o jsonpath="{.status.loadBalancer.ingress[0].ip}" + $services = kubectl get pods -n $aioNamespace -o json | ConvertFrom-Json + $matchingServices = $services.items | Where-Object { + $_.metadata.name -match "aio-mq-dmqtt" -and + $_.status.phase -notmatch "running" + } + Write-Host "[$(Get-Date -Format t)] INFO: Waiting for MQTT services to initialize and the service Ip address to be assigned...Waiting for 20 seconds" -ForegroundColor DarkGray + Start-Sleep -Seconds 20 + } while ( + $null -eq $mqttIp -and $matchingServices.Count -ne 0 + ) + if (-not [string]::IsNullOrEmpty($mqttIp)) { + $newObject = [PSCustomObject]@{ + cluster = $clusterName + ip = $mqttIp + } + $mqttIpArray += $newObject + } + + Invoke-Command -VMName $clusterName -Credential $Credentials -ScriptBlock { + netsh interface portproxy add v4tov4 listenport=1883 listenaddress=0.0.0.0 connectport=1883 connectaddress=$using:mqttIp + } + } + + $mqttIpArray = $mqttIpArray | Where-Object { $_ -ne "" } + + return $mqttIpArray +} + +function Deploy-MQTTSimulator { + param ( + [array]$mqttIpArray + ) + + $mqsimulatorfile = "$AgToolsDir\mqtt_simulator.yml" + + $clusters = $AgConfig.SiteConfig.GetEnumerator() + + foreach ($cluster in $clusters) { + $clusterName = $cluster.Name.ToLower() + Copy-Item $mqsimulatorfile "$AgToolsDir\mqtt_simulator_$clusterName.yml" -Force + $simualtorConfig = "$AgToolsDir\mqtt_simulator_$clusterName.yml" + $mqttIp = $mqttIpArray | Where-Object { $_.cluster -eq $clusterName } | Select-Object -ExpandProperty ip + Write-Host "[$(Get-Date -Format t)] INFO: Deploying MQTT Simulator to the $clusterName cluster" -ForegroundColor Gray + Write-Host "`n" + kubectx $clusterName + (Get-Content $simualtorConfig ) -replace 'MQTTIpPlaceholder', $mqttIp | Set-Content $simualtorConfig + netsh interface portproxy add v4tov4 listenport=1883 listenaddress=0.0.0.0 connectport=1883 connectaddress=$mqttIp + kubectl apply -f $simualtorConfig -n $aioNamespace + } +} + +############################################################## +# Install MQTT Explorer +############################################################## +function Deploy-MQTTExplorer { + param ( + [array]$mqttIpArray + ) + Write-Host "`n" + Write-Host "[$(Get-Date -Format t)] INFO: Installing MQTT Explorer." -ForegroundColor DarkGreen + Write-Host "`n" + $aioToolsDir = $AgConfig.AgDirectories["AgToolsDir"] + $mqttExplorerSettings = "$env:USERPROFILE\AppData\Roaming\MQTT-Explorer\settings.json" + $latestReleaseTag = (Invoke-WebRequest $mqttExplorerReleasesUrl | ConvertFrom-Json)[0].tag_name + $versionToDownload = $latestReleaseTag.Split("v")[1] + $mqttExplorerReleaseDownloadUrl = ((Invoke-WebRequest $mqttExplorerReleasesUrl | ConvertFrom-Json)[0].assets | Where-object { $_.name -like "MQTT-Explorer-Setup-${versionToDownload}.exe" }).browser_download_url + $output = Join-Path $aioToolsDir "mqtt-explorer-$latestReleaseTag.exe" + $clusters = $AgConfig.SiteConfig.GetEnumerator() + + $ProgressPreference = "SilentlyContinue" + Invoke-WebRequest $mqttExplorerReleaseDownloadUrl -OutFile $output + Start-Process -FilePath $output -ArgumentList "/S" -Wait + + Write-Host "[$(Get-Date -Format t)] INFO: Configuring MQTT explorer" -ForegroundColor DarkGray + Start-Process "$env:USERPROFILE\AppData\Local\Programs\MQTT-Explorer\MQTT Explorer.exe" + Start-Sleep -Seconds 5 + Stop-Process -Name "MQTT Explorer" + Copy-Item "$aioToolsDir\mqtt_explorer_settings.json" -Destination $mqttExplorerSettings -Force + foreach ($cluster in $clusters) { + $clusterName = $cluster.Name.ToLower() + $mqttIp = $mqttIpArray | Where-Object { $_.cluster -eq $clusterName } | Select-Object -ExpandProperty ip + (Get-Content $mqttExplorerSettings ) -replace "${clusterName}IpPlaceholder", $mqttIp | Set-Content $mqttExplorerSettings + } + $ProgressPreference = "Continue" +} + +# Function to deploy Azure Data Explorer dashboard reports +function Deploy-ADXDashboardReports { + ### BELOW IS AN ALTERNATIVE APPROACH TO IMPORT DASHBOARD USING README INSTRUCTIONS + $adxDashBoardsDir = $AgConfig.AgDirectories["AgAdxDashboards"] + + # Create directory if do not exist + if (-not (Test-Path -LiteralPath $adxDashBoardsDir)) { + New-Item -Path $adxDashBoardsDir -ItemType Directory -ErrorAction Stop | Out-Null #-Force + } + + #$dataEmulatorDir = $AgConfig.AgDirectories["AgDataEmulator"] + $kustoCluster = Get-AzKustoCluster -ResourceGroupName $resourceGroup -Name $adxClusterName + if ($null -ne $kustoCluster) { + $adxEndPoint = $kustoCluster.Uri + if ($null -ne $adxEndPoint -and $adxEndPoint -ne "") { + $ordersDashboardBody = (Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/adx_dashboards/adx-dashboard-contoso-motors-auto-parts.json").Content -replace '{{ADX_CLUSTER_URI}}', $adxEndPoint -replace '{{ADX_CLUSTER_NAME}}', $adxClusterName -replace '{{GITHUB_BRANCH}}', $env:githubBranch -replace '{{GITHUB_ACCOUNT}}', $env:githubAccount + Set-Content -Path "$adxDashBoardsDir\adx-dashboard-contoso-motors-auto-parts.json" -Value $ordersDashboardBody -Force -ErrorAction Ignore + } + else { + Write-Host "[$(Get-Date -Format t)] ERROR: Unable to find Azure Data Explorer endpoint from the cluster resource in the resource group." + } + } + + # Create EventHub environment variables + $eventHubNamespace = (az eventhubs namespace list --resource-group $env:resourceGroup --query [0].name --output tsv) + if ($null -ne $eventHubNamespace) { + # Find EventHub and create connection string + $eventHub = (az eventhubs eventhub list --namespace-name $eventHubNamespace --resource-group $env:resourceGroup --query [0].name --output tsv) + + # Create authorization rule + $authRuleName = "data-emulator" + az eventhubs eventhub authorization-rule create --authorization-rule-name $authRuleName --eventhub-name $eventHub --namespace-name $eventHubNamespace --resource-group $env:resourceGroup --rights Send Listen + + # Get connection string + $connectionString = (az eventhubs eventhub authorization-rule keys list --resource-group $env:resourceGroup --namespace-name $eventHubNamespace --eventhub-name $eventHub --name $authRuleName --query primaryConnectionString --output tsv) + + # Set environment variables + [System.Environment]::SetEnvironmentVariable('EVENTHUB_CONNECTION_STRING', $connectionString, [System.EnvironmentVariableTarget]::Machine) + [System.Environment]::SetEnvironmentVariable('EVENTHUB_NAME', $eventHub, [System.EnvironmentVariableTarget]::Machine) + } + + # Create desktop icons + $AgDataEmulatorDir = $AgConfig.AgDirectories["AgDataEmulator"] + $dataEmulatorFile = "$AgDataEmulatorDir\data-emulator.py" + Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/data_emulator/data-emulator.py" -OutFile $dataEmulatorFile + if (!(Test-Path -Path $dataEmulatorFile)) { + Write-Host "Unabled to download data-emulator.py file. Please download manually from GitHub into the DataEmulator folder." + } + + $emulationScriptContent = "@echo off `r`ncmd /k `"cd /d $AgDataEmulatorDir & python data-emulator.py`"" + $emulatorLocation = "$AgDataEmulatorDir\dataemulator.cmd" + Set-Content -Path $emulatorLocation -Value $emulationScriptContent + + # Download icon file + $AgIconsDir = $AgConfig.AgDirectories["AgIconDir"] + + $iconPath = "$AgIconsDir\emulator.ico" + Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/icons/emulator.ico" -OutFile $iconPath + if (!(Test-Path -Path $iconPath)) { + Write-Host "Unabled to download emulator.ico file. Please download manually from GitHub into the icons folder." + } + + # Create desktop shortcut + $shortcutLocation = "$Env:Public\Desktop\Data Emulator.lnk" + $wScriptShell = New-Object -ComObject WScript.Shell + $shortcut = $wScriptShell.CreateShortcut($shortcutLocation) + $shortcut.TargetPath = $emulatorLocation + $shortcut.IconLocation = "$iconPath, 0" + $shortcut.WindowStyle = 8 + $shortcut.Save() + + # Install azure.eventhub python module to run data emulator + pip install azure.eventhub +} + +function Deploy-ManufacturingBookmarks { + $bookmarksFileName = "$AgToolsDir\Bookmarks" + $edgeBookmarksPath = "$Env:LOCALAPPDATA\Microsoft\Edge\User Data\Default" + + foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { + kubectx $cluster.Name.ToLower() | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") + $services = kubectl get services --all-namespaces -o json | ConvertFrom-Json + + # Matching url: flask app + $matchingServices = $services.items | Where-Object { + $_.metadata.name -eq 'flask-app-service' -and + $_.spec.ports.port -contains 8888 + } + $flaskIps = $matchingServices.status.loadBalancer.ingress.ip + + foreach ($flaskIp in $flaskIps) { + $output = "http://${flaskIp}:8888" + $output | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") + + # Replace matching value in the Bookmarks file + $content = Get-Content -Path $bookmarksFileName + $newContent = $content -replace ("Flask-" + $cluster.Name + "-URL"), $output + $newContent | Set-Content -Path $bookmarksFileName + + Start-Sleep -Seconds 2 + } + + # Matching url: Influxdb + $matchingServices = $services.items | Where-Object { + $_.metadata.name -eq 'Influxdb' -and + $_.spec.ports.port -contains 8086 + } + $influxdbIps = $matchingServices.status.loadBalancer.ingress.ip + + foreach ($influxdbIp in $influxdbIps) { + $output = "http://${influxdbIp}:8086" + $output | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") + + # Replace matching value in the Bookmarks file + $content = Get-Content -Path $bookmarksFileName + $newContent = $content -replace ("Influxdb-" + $cluster.Name + "-URL"), $output + $newContent | Set-Content -Path $bookmarksFileName + + Start-Sleep -Seconds 2 + } + + # Matching url: prometheus + $matchingServices = $services.items | Where-Object { + $_.spec.ports.port -contains 9090 -and + $_.spec.type -eq "LoadBalancer" + } + $prometheusIps = $matchingServices.status.loadBalancer.ingress.ip + + foreach ($prometheusIp in $prometheusIps) { + $output = "http://${prometheusIp}:9090" + $output | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") + + # Replace matching value in the Bookmarks file + $content = Get-Content -Path $bookmarksFileName + $newContent = $content -replace ("Prometheus-" + $cluster.Name + "-URL"), $output + $newContent | Set-Content -Path $bookmarksFileName + + Start-Sleep -Seconds 2 + } + } + + Start-Sleep -Seconds 2 + + Copy-Item -Path $bookmarksFileName -Destination $edgeBookmarksPath -Force + + ############################################################## + # Pinning important directories to Quick access + ############################################################## + Write-Host "[$(Get-Date -Format t)] INFO: Pinning important directories to Quick access (Step 16/17)" -ForegroundColor DarkGreen + $quickAccess = new-object -com shell.application + $quickAccess.Namespace($AgConfig.AgDirectories.AgDir).Self.InvokeVerb("pintohome") + $quickAccess.Namespace($AgConfig.AgDirectories.AgLogsDir).Self.InvokeVerb("pintohome") +} diff --git a/azure_jumpstart_ag/artifacts/PowerShell/Modules/retail.psm1 b/azure_jumpstart_ag/artifacts/PowerShell/Modules/retail.psm1 new file mode 100644 index 0000000000..77d684c59b --- /dev/null +++ b/azure_jumpstart_ag/artifacts/PowerShell/Modules/retail.psm1 @@ -0,0 +1,719 @@ +function SetupRetailRepo { + Set-Location $AgAppsRepo + Write-Host "INFO: Checking if the $appsRepo repository is forked" -ForegroundColor Gray + $retryCount = 0 + $maxRetries = 5 + do { + $forkExists = $false + try { + $response = Invoke-RestMethod -Uri "$gitHubAPIBaseUri/repos/$githubUser/$appsRepo" + if ($response) { + write-host "INFO: Fork exists....Proceeding" -ForegroundColor Gray + $forkExists = $true + } + } + catch { + if ($retryCount -lt $maxRetries) { + Write-Host "ERROR: $githubUser/$appsRepo Fork doesn't exist, please fork https://github.com/microsoft/jumpstart-agora-apps to proceed (attempt $retryCount/$maxRetries) . . . waiting 60 seconds" -ForegroundColor Red + $retryCount++ + $forkExists = $false + start-sleep -Seconds 60 + } + else { + Write-Host "[$(Get-Date -Format t)] ERROR: Retry limit reached, $githubUser/$appsRepo Fork doesn't exist. Exiting." -ForegroundColor Red + exit + } + } + } until ($forkExists -eq $true) + + Write-Host "INFO: Checking if the GitHub access token is valid." -ForegroundColor Gray + do { + $response = gh auth status 2>&1 + if ($response -match "authentication failed") { + write-host "ERROR: The GitHub Personal access token is not valid" -ForegroundColor Red + Write-Host "INFO: Please try to re-generate the personal access token and provide it here (https://aka.ms/AgoraPreReqs): " + do { + $githubPAT = Read-Host "GitHub personal access token" + } while ($githubPAT -eq "") + } + } until ( + $response -notmatch "authentication failed" + ) + + Write-Host "INFO: The GitHub Personal access token is valid. Proceeding." -ForegroundColor DarkGreen + $Env:GITHUB_TOKEN = $githubPAT.Trim() + [System.Environment]::SetEnvironmentVariable('GITHUB_TOKEN', $githubPAT.Trim(), [System.EnvironmentVariableTarget]::Machine) + + Write-Host "INFO: Checking if the personal access token is assigned on the $githubUser/$appsRepo Fork" -ForegroundColor Gray + $headers = @{ + Authorization = "token $githubPat" + "Content-Type" = "application/json" + } + $retryCount = 0 + $maxRetries = 5 + $uri = "$gitHubAPIBaseUri/repos/$githubUser/$appsRepo/actions/secrets" + do { + try { + $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers + Write-Host "INFO: Personal access token is assigned on $githubUser/$appsRepo fork" -ForegroundColor DarkGreen + $PatAssigned = $true + } + catch { + if ($retryCount -lt $maxRetries) { + Write-Host "ERROR: Personal access token is not assigned on $githubUser/$appsRepo fork. Please assign the personal access token to your fork (https://aka.ms/AgoraPreReqs) (attempt $retryCount/$maxRetries).....waiting 60 seconds" -ForegroundColor Red + $PatAssigned = $false + $retryCount++ + start-sleep -Seconds 60 + } + else { + Write-Host "[$(Get-Date -Format t)] ERROR: Retry limit reached, the personal access token is not assigned to $githubUser/$appsRepo. Exiting." -ForegroundColor Red + exit + } + } + } until ($PatAssigned -eq $true) + + + Write-Host "INFO: Cloning the GitHub repository locally" -ForegroundColor Gray + git clone "https://$githubPat@github.com/$githubUser/$appsRepo.git" "$AgAppsRepo\$appsRepo" + Set-Location "$AgAppsRepo\$appsRepo" + + Write-Host "INFO: Verifying 'Administration' permissions" -ForegroundColor Gray + $retryCount = 0 + $maxRetries = 5 + + $body = @{ + required_status_checks = $null + enforce_admins = $false + required_pull_request_reviews = @{ + required_approving_review_count = 0 + } + dismiss_stale_reviews = $true + restrictions = $null + } | ConvertTo-Json + + do { + try { + $response = Invoke-WebRequest -Uri "$gitHubAPIBaseUri/repos/$githubUser/$appsRepo/branches/main/protection" -Method Put -Headers $headers -Body $body -ContentType "application/json" + } + catch { + if ($retryCount -lt $maxRetries) { + Write-Host "ERROR: The GitHub Personal access token doesn't seem to have 'Administration' write permissions, please assign the right permissions (https://aka.ms/AgoraPreReqs) (attempt $retryCount/$maxRetries)...waiting 60 seconds" -ForegroundColor Red + $retryCount++ + start-sleep -Seconds 60 + } + else { + Write-Host "[$(Get-Date -Format t)] ERROR: Retry limit reached, the personal access token doesn't have 'Administration' write permissions assigned. Exiting." -ForegroundColor Red + exit + } + } + } until ($response) + Write-Host "INFO: 'Administration' write permissions verified" -ForegroundColor DarkGreen + + + Write-Host "INFO: Checking if there are existing branch protection policies" -ForegroundColor Gray + $protectedBranches = Invoke-RestMethod -Uri "$gitHubAPIBaseUri/repos/$githubUser/$appsRepo/branches?protected=true" -Method GET -Headers $headers + foreach ($branch in $protectedBranches) { + $branchName = $branch.name + $deleteProtectionUrl = "$gitHubAPIBaseUri/repos/$githubUser/$appsRepo/branches/$branchName/protection" + Invoke-RestMethod -Uri $deleteProtectionUrl -Headers $headers -Method Delete + Write-Host "INFO: Deleted protection policy for branch: $branchName" -ForegroundColor Gray + } + + Write-Host "INFO: Pulling latests changes to GitHub repository" -ForegroundColor Gray + git config --global user.email "dev@agora.com" + git config --global user.name "Agora Dev" + git remote add upstream "$appUpstreamRepo.git" + git fetch upstream + git checkout main + git reset --hard upstream/main + git push origin main -f + git pull + git remote remove upstream + git remote add upstream "$appClonedRepo.git" + + Write-Host "INFO: Creating GitHub workflows" -ForegroundColor Gray + New-Item -ItemType Directory ".github/workflows" -Force + $githubApiUrl = "$gitHubAPIBaseUri/repos/$githubAccount/azure_arc/contents/azure_jumpstart_ag/artifacts/workflows?ref=$githubBranch" + $response = Invoke-RestMethod -Uri $githubApiUrl + $fileUrls = $response | Where-Object { $_.type -eq "file" } | Select-Object -ExpandProperty download_url + $fileUrls | ForEach-Object { + $fileName = $_.Substring($_.LastIndexOf("/") + 1) + $outputFile = Join-Path "$AgAppsRepo\$appsRepo\.github\workflows" $fileName + Invoke-RestMethod -Uri $_ -OutFile $outputFile + } + git add . + git commit -m "Pushing GitHub Actions to apps fork" + git push + Start-Sleep -Seconds 20 + + Write-Host "INFO: Verifying 'Secrets' permissions" -ForegroundColor Gray + $retryCount = 0 + $maxRetries = 5 + do { + $response = gh secret set "test" -b "test" 2>&1 + if ($response -match "error") { + if ($retryCount -eq $maxRetries) { + Write-Host "[$(Get-Date -Format t)] ERROR: Retry limit reached, the personal access token doesn't have 'Secrets' write permissions assigned. Exiting." -ForegroundColor Red + exit + } + else { + $retryCount++ + write-host "ERROR: The GitHub Personal access token doesn't seem to have 'Secrets' write permissions, please assign the right permissions (https://aka.ms/AgoraPreReqs) (attempt $retryCount/$maxRetries)...waiting 60 seconds" -ForegroundColor Red + Start-Sleep -Seconds 60 + } + } + } while ($response -match "error" -or $retryCount -ge $maxRetries) + gh secret delete test + Write-Host "INFO: 'Secrets' write permissions verified" -ForegroundColor DarkGreen + + Write-Host "INFO: Verifying 'Actions' permissions" -ForegroundColor Gray + $retryCount = 0 + $maxRetries = 5 + do { + $response = gh workflow enable update-files.yml 2>&1 + if ($response -match "failed") { + if ($retryCount -eq $maxRetries) { + Write-Host "[$(Get-Date -Format t)] ERROR: Retry limit reached, the personal access token doesn't have 'Actions' write permissions assigned. Exiting." -ForegroundColor Red + exit + } + else { + $retryCount++ + write-host "ERROR: The GitHub Personal access token doesn't seem to have 'Actions' write permissions, please assign the right permissions (https://aka.ms/AgoraPreReqs) (attempt $retryCount/$maxRetries)...waiting 60 seconds" -ForegroundColor Red + Start-Sleep -Seconds 60 + } + } + } while ($response -match "failed" -or $retryCount -ge $maxRetries) + Write-Host "INFO: 'Actions' write permissions verified" -ForegroundColor DarkGreen + + write-host "INFO: Creating GitHub secrets" -ForegroundColor Gray + Write-Host "INFO: Getting Cosmos DB access key" -ForegroundColor Gray + Write-Host "INFO: Adding GitHub secrets to apps fork" -ForegroundColor Gray + gh api -X PUT "/repos/$githubUser/$appsRepo/actions/permissions/workflow" -F can_approve_pull_request_reviews=true + gh repo set-default "$githubUser/$appsRepo" + gh secret set "SPN_CLIENT_ID" -b $spnClientID + gh secret set "SPN_CLIENT_SECRET" -b $spnClientSecret + gh secret set "ACR_NAME" -b $acrName + gh secret set "PAT_GITHUB" -b $githubPat + gh secret set "COSMOS_DB_ENDPOINT" -b $cosmosDBEndpoint + gh secret set "SPN_TENANT_ID" -b $spnTenantId + + Write-Host "INFO: Updating ACR name and Cosmos DB endpoint in all branches" -ForegroundColor Gray + gh workflow run update-files.yml + while ($workflowStatus.status -ne "completed") { + Write-Host "INFO: Waiting for update-files workflow to complete" -ForegroundColor Gray + Start-Sleep -Seconds 10 + $workflowStatus = (gh run list --workflow=update-files.yml --json status) | ConvertFrom-Json + } + Write-Host "INFO: Starting Contoso supermarket pos application v1.0 image build" -ForegroundColor Gray + gh workflow run pos-app-initial-images-build.yml + + Write-Host "INFO: Creating GitHub branches to $appsRepo fork" -ForegroundColor Gray + $branches = $AgConfig.GitBranches + foreach ($branch in $branches) { + try { + $response = Invoke-RestMethod -Uri "$gitHubAPIBaseUri/repos/$githubUser/$appsRepo/branches/$branch" + if ($response) { + if ($branch -ne "main") { + Write-Host "INFO: branch $branch already exists! Deleting and recreating the branch" -ForegroundColor Gray + git push origin --delete $branch + git branch -d $branch + git fetch origin + git checkout main + git pull origin main + git checkout -b $branch main + git pull origin main + git push --set-upstream origin $branch + } + } + } + catch { + Write-Host "INFO: Creating $branch branch" -ForegroundColor Gray + git fetch origin + git checkout main + git pull origin main + git checkout -b $branch main + git pull origin main + git push --set-upstream origin $branch + } + } + Write-Host "INFO: Cleaning up any other branches" -ForegroundColor Gray + $existingBranches = gh api "repos/$githubUser/$appsRepo/branches" | ConvertFrom-Json + $branches = $AgConfig.GitBranches + foreach ($branch in $existingBranches) { + if ($branches -notcontains $branch.name) { + $branchToDelete = $branch.name + git push origin --delete $branchToDelete + } + } + + Write-Host "INFO: Switching to main branch" -ForegroundColor Gray + git checkout main + + Write-Host "INFO: Adding branch protection policies for all branches" -ForegroundColor Gray + foreach ($branch in $branches) { + Write-Host "INFO: Adding branch protection policies for $branch branch" -ForegroundColor Gray + $headers = @{ + "Authorization" = "Bearer $githubPat" + "Accept" = "application/vnd.github+json" + } + $body = @{ + required_status_checks = $null + enforce_admins = $false + required_pull_request_reviews = @{ + required_approving_review_count = 0 + } + dismiss_stale_reviews = $true + restrictions = $null + } | ConvertTo-Json + + Invoke-WebRequest -Uri "$gitHubAPIBaseUri/repos/$githubUser/$appsRepo/branches/$branch/protection" -Method Put -Headers $headers -Body $body -ContentType "application/json" + } + Write-Host "INFO: GitHub repo configuration complete!" -ForegroundColor Green + Write-Host +} + +function Deploy-AzureIOTHub { + if ($githubUser -ne "microsoft") { + $iotHubHostName = $Env:iotHubHostName + $iotHubName = $iotHubHostName.replace(".azure-devices.net", "") + $sites = $AgConfig.SiteConfig.Values + Write-Host "[$(Get-Date -Format t)] INFO: Create an Azure IoT device for each site" -ForegroundColor Gray + foreach ($site in $sites) { + foreach ($device in $site.IoTDevices) { + $deviceId = "$device-$($site.FriendlyName)" + Add-AzIotHubDevice -ResourceGroupName $resourceGroup -IotHubName $iotHubName -DeviceId $deviceId -EdgeEnabled | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\IoT.log") + } + } + Write-Host "[$(Get-Date -Format t)] INFO: Azure IoT Hub configuration complete!" -ForegroundColor Green + Write-Host + } + else { + Write-Host "[$(Get-Date -Format t)] ERROR: You have to fork the jumpstart-agora-apps repository!" -ForegroundColor Red + } + + ### BELOW IS AN ALTERNATIVE APPROACH TO IMPORT DASHBOARD USING README INSTRUCTIONS + $adxDashBoardsDir = $AgConfig.AgDirectories["AgAdxDashboards"] + $dataEmulatorDir = $AgConfig.AgDirectories["AgDataEmulator"] + $kustoCluster = Get-AzKustoCluster -ResourceGroupName $resourceGroup -Name $adxClusterName + if ($null -ne $kustoCluster) { + $adxEndPoint = $kustoCluster.Uri + if ($null -ne $adxEndPoint -and $adxEndPoint -ne "") { + $ordersDashboardBody = (Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/adx_dashboards/adx-dashboard-orders-payload.json").Content -replace '{{ADX_CLUSTER_URI}}', $adxEndPoint -replace '{{ADX_CLUSTER_NAME}}', $adxClusterName + Set-Content -Path "$adxDashBoardsDir\adx-dashboard-orders-payload.json" -Value $ordersDashboardBody -Force -ErrorAction Ignore + $iotSensorsDashboardBody = (Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/adx_dashboards/adx-dashboard-iotsensor-payload.json") -replace '{{ADX_CLUSTER_URI}}', $adxEndPoint -replace '{{ADX_CLUSTER_NAME}}', $adxClusterName + Set-Content -Path "$adxDashBoardsDir\adx-dashboard-iotsensor-payload.json" -Value $iotSensorsDashboardBody -Force -ErrorAction Ignore + } + else { + Write-Host "[$(Get-Date -Format t)] ERROR: Unable to find Azure Data Explorer endpoint from the cluster resource in the resource group." + } + } + + # Download DataEmulator.zip into Agora folder and unzip + $emulatorPath = "$dataEmulatorDir\DataEmulator.zip" + Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/data_emulator/DataEmulator.zip" -OutFile $emulatorPath + + # Unzip DataEmulator.zip to copy DataEmulator exe and config file to generate sample data for dashboards + if (Test-Path -Path $emulatorPath) { + Expand-Archive -Path "$emulatorPath" -DestinationPath "$dataEmulatorDir" -ErrorAction SilentlyContinue -Force + } + + # Download products.json and stores.json file to use in Data Emulator + $productsJsonPath = "$dataEmulatorDir\products.json" + Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/data_emulator/products.json" -OutFile $productsJsonPath + if (!(Test-Path -Path $productsJsonPath)) { + Write-Host "Unabled to download products.json file. Please download manually from GitHub into the data_emulator folder." + } + + $storesJsonPath = "$dataEmulatorDir\stores.json" + Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/data_emulator/stores.json" -OutFile $storesJsonPath + if (!(Test-Path -Path $storesJsonPath)) { + Write-Host "Unabled to download stores.json file. Please download manually from GitHub into the data_emulator folder." + } + + # Download icon file + $iconPath = "$AgIconsDir\emulator.ico" + Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/icons/emulator.ico" -OutFile $iconPath + if (!(Test-Path -Path $iconPath)) { + Write-Host "Unabled to download emulator.ico file. Please download manually from GitHub into the icons folder." + } + + # Create desktop shortcut + $shortcutLocation = "$Env:Public\Desktop\Data Emulator.lnk" + $wScriptShell = New-Object -ComObject WScript.Shell + $shortcut = $wScriptShell.CreateShortcut($shortcutLocation) + $shortcut.TargetPath = "$dataEmulatorDir\DataEmulator.exe" + $shortcut.IconLocation = "$iconPath, 0" + $shortcut.WindowStyle = 7 + $shortcut.Save() +} + +function Deploy-K8sImagesCache { + if ($Env:industry -eq "retail") { + Write-Host "[$(Get-Date -Format t)] INFO: Caching contoso-supermarket images on all clusters" -ForegroundColor Gray + while ($workflowStatus.status -ne "completed") { + Write-Host "INFO: Waiting for pos-app-initial-images-build workflow to complete" -ForegroundColor Gray + Start-Sleep -Seconds 10 + $workflowStatus = (gh run list --workflow=pos-app-initial-images-build.yml --json status) | ConvertFrom-Json + } + foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { + $branch = $cluster.Name.ToLower() + $context = $cluster.Name.ToLower() + $applicationName = "contoso-supermarket" + $imageTag = "v1.0" + $imagePullSecret = "acr-secret" + $namespace = "images-cache" + if ($branch -eq "chicago") { + $branch = "canary" + } + if ($branch -eq "seattle") { + $branch = "production" + } + Save-K8sImage -applicationName $applicationName -imageName "contosoai" -imageTag $imageTag -namespace $namespace -imagePullSecret $imagePullSecret -branch $branch -acrName $acrName -context $context + Save-K8sImage -applicationName $applicationName -imageName "pos" -imageTag $imageTag -namespace $namespace -imagePullSecret $imagePullSecret -branch $branch -acrName $acrName -context $context + Save-K8sImage -applicationName $applicationName -imageName "pos-cloudsync" -imageTag $imageTag -namespace $namespace -imagePullSecret $imagePullSecret -branch $branch -acrName $acrName -context $context + Save-K8sImage -applicationName $applicationName -imageName "queue-monitoring-backend" -imageTag $imageTag -namespace $namespace -imagePullSecret $imagePullSecret -branch $branch -acrName $acrName -context $context + Save-K8sImage -applicationName $applicationName -imageName "queue-monitoring-frontend" -imageTag $imageTag -namespace $namespace -imagePullSecret $imagePullSecret -branch $branch -acrName $acrName -context $context + } + } +} +function Get-GitHubFiles ($githubApiUrl, $folderPath, [Switch]$excludeFolders) { + # Force TLS 1.2 for connections to prevent TLS/SSL errors + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + + $response = Invoke-RestMethod -Uri $githubApiUrl + $fileUrls = $response | Where-Object { $_.type -eq "file" } | Select-Object -ExpandProperty download_url + $fileUrls | ForEach-Object { + $fileName = $_.Substring($_.LastIndexOf("/") + 1) + $outputFile = Join-Path $folderPath $fileName + Invoke-RestMethod -Uri $_ -OutFile $outputFile + } + + If (-not $excludeFolders) { + $response | Where-Object { $_.type -eq "dir" } | ForEach-Object { + $folderName = $_.name + $path = Join-Path $folderPath $folderName + New-Item $path -ItemType Directory -Force -ErrorAction Continue + Get-GitHubFiles -githubApiUrl $_.url -folderPath $path + } + } +} +function Deploy-RetailConfigs { + Write-Host "[$(Get-Date -Format t)] INFO: Cleaning up images-cache namespace on all clusters" -ForegroundColor Gray + # Cleaning up images-cache namespace on all clusters + foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { + Start-Job -Name images-cache-cleanup -ScriptBlock { + $cluster = $using:cluster + $clusterName = $cluster.Name.ToLower() + Write-Host "[$(Get-Date -Format t)] INFO: Deleting images-cache namespace on cluster $clusterName" -ForegroundColor Gray + kubectl delete namespace "images-cache" --context $clusterName + } + } + + # TODO - this looks app-specific so should perhaps be moved to the app loop + while ($workflowStatus.status -ne "completed") { + Write-Host "INFO: Waiting for pos-app-initial-images-build workflow to complete" -ForegroundColor Gray + Start-Sleep -Seconds 10 + $workflowStatus = (gh run list --workflow=pos-app-initial-images-build.yml --json status) | ConvertFrom-Json + } + + foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { + Start-Job -Name gitops -ScriptBlock { + + Function Get-GitHubFiles ($githubApiUrl, $folderPath, [Switch]$excludeFolders) { + # Force TLS 1.2 for connections to prevent TLS/SSL errors + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + + $response = Invoke-RestMethod -Uri $githubApiUrl + $fileUrls = $response | Where-Object { $_.type -eq "file" } | Select-Object -ExpandProperty download_url + $fileUrls | ForEach-Object { + $fileName = $_.Substring($_.LastIndexOf("/") + 1) + $outputFile = Join-Path $folderPath $fileName + Invoke-RestMethod -Uri $_ -OutFile $outputFile + } + + If (-not $excludeFolders) { + $response | Where-Object { $_.type -eq "dir" } | ForEach-Object { + $folderName = $_.name + $path = Join-Path $folderPath $folderName + New-Item $path -ItemType Directory -Force -ErrorAction Continue + Get-GitHubFiles -githubApiUrl $_.url -folderPath $path + } + } + } + + $AgConfig = $using:AgConfig + $cluster = $using:cluster + $site = $cluster.Value + $siteName = $site.FriendlyName.ToLower() + $namingGuid = $using:namingGuid + $resourceGroup = $using:resourceGroup + $appClonedRepo = $using:appClonedRepo + $appsRepo = $using:appsRepo + + $AgConfig.AppConfig.GetEnumerator() | sort-object -Property @{Expression = { $_.value.Order }; Ascending = $true } | ForEach-Object { + $app = $_ + $store = $cluster.value.Branch.ToLower() + $clusterName = $cluster.value.ArcClusterName + "-$namingGuid" + $branch = $cluster.value.Branch.ToLower() + $configName = $app.value.GitOpsConfigName.ToLower() + $clusterType = $cluster.value.Type + $namespace = $app.value.Namespace + $appName = $app.Value.KustomizationName + $appPath = $app.Value.KustomizationPath + $retryCount = 0 + $maxRetries = 2 + + Write-Host "[$(Get-Date -Format t)] INFO: Creating GitOps config for $configName on $($cluster.Value.ArcClusterName+"-$namingGuid")" -ForegroundColor Gray + if ($clusterType -eq "AKS") { + $type = "managedClusters" + $clusterName = $cluster.value.ArcClusterName + } + else { + $type = "connectedClusters" + } + if ($branch -eq "main") { + $store = "dev" + } + + # Wait for Kubernetes API server to become available + $apiServer = kubectl config view --context $cluster.Name.ToLower() --minify -o jsonpath='{.clusters[0].cluster.server}' + $apiServerAddress = $apiServer -replace '.*https://| .*$' + $apiServerFqdn = ($apiServerAddress -split ":")[0] + $apiServerPort = ($apiServerAddress -split ":")[1] + + do { + $result = Test-NetConnection -ComputerName $apiServerFqdn -Port $apiServerPort -WarningAction SilentlyContinue + if ($result.TcpTestSucceeded) { + break + } + else { + Start-Sleep -Seconds 5 + } + } while ($true) + If ($app.Value.ConfigMaps) { + # download the config files + foreach ($configMap in $app.value.ConfigMaps.GetEnumerator()) { + $repoPath = $configMap.value.RepoPath + $configPath = "$configMapDir\$appPath\config\$($configMap.Name)\$branch" + $iotHubName = $Env:iotHubHostName.replace(".azure-devices.net", "") + $gitHubUser = $Env:gitHubUser + $githubBranch = $Env:githubBranch + + New-Item -Path $configPath -ItemType Directory -Force | Out-Null + + $githubApiUrl = "https://api.github.com/repos/$gitHubUser/$appsRepo/$($repoPath)?ref=$branch" + Get-GitHubFiles -githubApiUrl $githubApiUrl -folderPath $configPath + + # replace the IoT Hub name and the SAS Tokens with the deployment specific values + # this is a one-off for the broker, but needs to be generalized if/when another app needs it + If ($configMap.Name -eq "mqtt-broker-config") { + $configFile = "$configPath\mosquitto.conf" + $update = (Get-Content $configFile -Raw) + $update = $update -replace "Ag-IotHub-\w*", $iotHubName + + foreach ($device in $site.IoTDevices) { + $deviceId = "$device-$($site.FriendlyName)" + $deviceSASToken = $(az iot hub generate-sas-token --device-id $deviceId --hub-name $iotHubName --resource-group $resourceGroup --duration (60 * 60 * 24 * 30) --query sas -o tsv --only-show-errors) + $update = $update -replace "Chicago", $site.FriendlyName + $update = $update -replace "SharedAccessSignature.*$($device).*", $deviceSASToken + } + + $update | Set-Content $configFile + } + + # create the namespace if needed + If (-not (kubectl get namespace $namespace --context $siteName)) { + kubectl create namespace $namespace --context $siteName + } + # create the configmap + kubectl create configmap $configMap.name --from-file=$configPath --namespace $namespace --context $siteName + } + } + + az k8s-configuration flux create ` + --cluster-name $clusterName ` + --resource-group $resourceGroup ` + --name $configName ` + --cluster-type $type ` + --url $appClonedRepo ` + --branch $branch ` + --sync-interval 5s ` + --kustomization name=$appName path=$appPath/$store prune=true retry_interval=1m ` + --timeout 10m ` + --namespace $namespace ` + --only-show-errors ` + 2>&1 | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + + do { + $configStatus = $(az k8s-configuration flux show --name $configName --cluster-name $clusterName --cluster-type $type --resource-group $resourceGroup -o json 2>$null) | convertFrom-JSON + if ($configStatus.ComplianceState -eq "Compliant") { + Write-Host "[$(Get-Date -Format t)] INFO: GitOps configuration $configName is ready on $clusterName" -ForegroundColor DarkGreen | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + } + else { + if ($configStatus.ComplianceState -ne "Non-compliant") { + Start-Sleep -Seconds 20 + } + elseif ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -lt $maxRetries) { + Start-Sleep -Seconds 20 + $configStatus = $(az k8s-configuration flux show --name $configName --cluster-name $clusterName --cluster-type $type --resource-group $resourceGroup -o json 2>$null) | convertFrom-JSON + if ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -lt $maxRetries) { + $retryCount++ + Write-Host "[$(Get-Date -Format t)] INFO: Attempting to re-install $configName on $clusterName" -ForegroundColor Gray | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + Write-Host "[$(Get-Date -Format t)] INFO: Deleting $configName on $clusterName" -ForegroundColor Gray | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + az k8s-configuration flux delete ` + --resource-group $resourceGroup ` + --cluster-name $clusterName ` + --cluster-type $type ` + --name $configName ` + --force ` + --yes ` + --only-show-errors ` + 2>&1 | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + + Start-Sleep -Seconds 10 + Write-Host "[$(Get-Date -Format t)] INFO: Re-creating $configName on $clusterName" -ForegroundColor Gray | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + + az k8s-configuration flux create ` + --cluster-name $clusterName ` + --resource-group $resourceGroup ` + --name $configName ` + --cluster-type $type ` + --url $appClonedRepo ` + --branch $branch ` + --sync-interval 5s ` + --kustomization name=$appName path=$appPath/$store prune=true ` + --timeout 30m ` + --namespace $namespace ` + --only-show-errors ` + 2>&1 | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + } + } + elseif ($configStatus.ComplianceState -eq "Non-compliant" -and $retryCount -eq $maxRetries) { + Write-Host "[$(Get-Date -Format t)] ERROR: GitOps configuration $configName has failed on $clusterName. Exiting..." -ForegroundColor White -BackgroundColor Red | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\GitOps-$clusterName.log") + break + } + } + } until ($configStatus.ComplianceState -eq "Compliant") + } + } + } + + while ($(Get-Job -Name gitops).State -eq 'Running') { + #Write-Host "[$(Get-Date -Format t)] INFO: Waiting for GitOps configuration to complete on all clusters...waiting 60 seconds" -ForegroundColor Gray + Receive-Job -Name gitops -WarningAction SilentlyContinue + Start-Sleep -Seconds 60 + } + + Get-Job -name gitops | Remove-Job + Write-Host "[$(Get-Date -Format t)] INFO: GitOps configuration complete." -ForegroundColor Green + Write-Host +} + +function Deploy-RetailBookmarks { + $bookmarksFileName = "$AgToolsDir\Bookmarks" + $edgeBookmarksPath = "$Env:LOCALAPPDATA\Microsoft\Edge\User Data\Default" + + foreach ($cluster in $AgConfig.SiteConfig.GetEnumerator()) { + kubectx $cluster.Name.ToLower() | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") + $services = kubectl get services --all-namespaces -o json | ConvertFrom-Json + + # Matching url: pos - customer + $matchingServices = $services.items | Where-Object { + $_.spec.ports.port -contains 5000 -and + $_.spec.type -eq "LoadBalancer" + } + $posIps = $matchingServices.status.loadBalancer.ingress.ip + + foreach ($posIp in $posIps) { + $output = "http://$posIp" + ':5000' + $output | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") + + # Replace matching value in the Bookmarks file + $content = Get-Content -Path $bookmarksFileName + $newContent = $content -replace ("POS-" + $cluster.Name + "-URL-Customer"), $output + $newContent | Set-Content -Path $bookmarksFileName + + Start-Sleep -Seconds 2 + } + + # Matching url: pos - manager + $matchingServices = $services.items | Where-Object { + $_.spec.ports.port -contains 81 -and + $_.spec.type -eq "LoadBalancer" + } + $posIps = $matchingServices.status.loadBalancer.ingress.ip + + foreach ($posIp in $posIps) { + $output = "http://$posIp" + ':81' + $output | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") + + # Replace matching value in the Bookmarks file + $content = Get-Content -Path $bookmarksFileName + $newContent = $content -replace ("POS-" + $cluster.Name + "-URL-Manager"), $output + $newContent | Set-Content -Path $bookmarksFileName + + Start-Sleep -Seconds 2 + } + + # Matching url: prometheus-grafana + if ($cluster.Name -eq "Staging" -or $cluster.Name -eq "Dev") { + $matchingServices = $services.items | Where-Object { + $_.metadata.name -eq 'prometheus-grafana' + } + $grafanaIps = $matchingServices.status.loadBalancer.ingress.ip + + foreach ($grafanaIp in $grafanaIps) { + $output = "http://$grafanaIp" + $output | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") + + # Replace matching value in the Bookmarks file + $content = Get-Content -Path $bookmarksFileName + $newContent = $content -replace ("Grafana-" + $cluster.Name + "-URL"), $output + $newContent | Set-Content -Path $bookmarksFileName + + Start-Sleep -Seconds 2 + } + } + + # Matching url: prometheus + $matchingServices = $services.items | Where-Object { + $_.spec.ports.port -contains 9090 -and + $_.spec.type -eq "LoadBalancer" + } + $prometheusIps = $matchingServices.status.loadBalancer.ingress.ip + + foreach ($prometheusIp in $prometheusIps) { + $output = "http://$prometheusIp" + ':9090' + $output | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") + + # Replace matching value in the Bookmarks file + $content = Get-Content -Path $bookmarksFileName + $newContent = $content -replace ("Prometheus-" + $cluster.Name + "-URL"), $output + $newContent | Set-Content -Path $bookmarksFileName + + Start-Sleep -Seconds 2 + } + } + + # Matching url: Agora apps forked repo + $output = $appClonedRepo + $output | Out-File -Append -FilePath ($AgConfig.AgDirectories["AgLogsDir"] + "\Bookmarks.log") + + # Replace matching value in the Bookmarks file + $content = Get-Content -Path $bookmarksFileName + $newContent = $content -replace "Agora-Apps-Repo-Clone-URL", $output + $newContent = $newContent -replace "Agora-Apps-Repo-Your-Fork", "Agora Apps Repo - $githubUser" + $newContent | Set-Content -Path $bookmarksFileName + + Start-Sleep -Seconds 2 + + Copy-Item -Path $bookmarksFileName -Destination $edgeBookmarksPath -Force + + ############################################################## + # Pinning important directories to Quick access + ############################################################## + Write-Host "[$(Get-Date -Format t)] INFO: Pinning important directories to Quick access (Step 16/17)" -ForegroundColor DarkGreen + $quickAccess = new-object -com shell.application + $quickAccess.Namespace($AgConfig.AgDirectories.AgDir).Self.InvokeVerb("pintohome") + $quickAccess.Namespace($AgConfig.AgDirectories.AgLogsDir).Self.InvokeVerb("pintohome") +} diff --git a/azure_jumpstart_ag/artifacts/adx_dashboards/adx-dashboard-contoso-motors-auto-parts.json b/azure_jumpstart_ag/artifacts/adx_dashboards/adx-dashboard-contoso-motors-auto-parts.json new file mode 100644 index 0000000000..c6a2595976 --- /dev/null +++ b/azure_jumpstart_ag/artifacts/adx_dashboards/adx-dashboard-contoso-motors-auto-parts.json @@ -0,0 +1,789 @@ +{ + "$schema": "https://dataexplorer.azure.com/static/d/schema/51/dashboard.json", + "id": "71f47749-912f-46d4-92ca-b61286839e73", + "eTag": "8b1a68cd-b2c1-46b1-8e36-20bb29f97673", + "schema_version": "51", + "title": "Contoso Motors", + "tiles": [ + { + "id": "86ba667a-63c7-4b8b-ba61-76c21d506910", + "title": "Type of Cars Manufactured", + "visualType": "multistat", + "pageId": "25083f67-f980-4066-a550-5f8bb1a183d6", + "layout": { + "x": 0, + "y": 12, + "width": 9, + "height": 7 + }, + "queryRef": { + "kind": "query", + "queryId": "266487a6-71ec-4dd7-9990-5f1b6a2aca2a" + }, + "visualOptions": { + "multiStat__textSize": "auto", + "multiStat__valueColumn": "Model", + "colorRulesDisabled": false, + "colorRules": [ + { + "id": "c693c85b-9b9a-4ea1-b296-b24cb8971319", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": "==", + "column": "EngineType", + "values": [ + "electric" + ] + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "blue", + "tag": "Electric", + "icon": "car", + "ruleName": "" + }, + { + "id": "cb98f18a-cf84-431a-b2c0-245671583f57", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": "==", + "column": "EngineType", + "values": [ + "hybrid" + ] + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "green", + "tag": "Hybrid", + "icon": "car", + "ruleName": "" + }, + { + "id": "561d4a9f-d446-4dcd-a534-f8ba657e21f9", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "values": [ + "gasoline" + ], + "operator": "==", + "column": "EngineType" + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "yellow", + "tag": "Gasoline", + "icon": "car", + "ruleName": "" + } + ], + "colorStyle": "light", + "multiStat__displayOrientation": "horizontal", + "multiStat__labelColumn": "EngineType", + "multiStat__slot": { + "width": 3, + "height": 1 + } + } + }, + { + "id": "2fce3f37-c0f9-4edd-9be5-83188a2a6a46", + "title": "Production Quality Control", + "visualType": "multistat", + "pageId": "25083f67-f980-4066-a550-5f8bb1a183d6", + "layout": { + "x": 3, + "y": 8, + "width": 6, + "height": 4 + }, + "queryRef": { + "kind": "query", + "queryId": "caa8a37f-0829-497e-ac88-6b96c03137e1" + }, + "visualOptions": { + "multiStat__textSize": "auto", + "multiStat__valueColumn": "Value", + "colorRulesDisabled": false, + "colorRules": [ + { + "id": "fadb24cf-114b-49d4-bbc4-e09ec77d8dce", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": "==", + "column": "Column", + "values": [ + "Passed" + ] + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "green", + "tag": "", + "icon": null, + "ruleName": "" + }, + { + "id": "fc43b0fb-e9bd-4372-b018-4119bc5db734", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": "==", + "column": "Column", + "values": [ + "Failed" + ] + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "red", + "tag": "", + "icon": null, + "ruleName": "" + } + ], + "colorStyle": "light", + "multiStat__displayOrientation": "horizontal", + "multiStat__labelColumn": null, + "multiStat__slot": { + "width": 2, + "height": 1 + } + } + }, + { + "id": "a805f6c6-c356-4915-9ab3-2a13d17302a2", + "title": "Actual Production Statistics", + "visualType": "multistat", + "pageId": "25083f67-f980-4066-a550-5f8bb1a183d6", + "layout": { + "x": 3, + "y": 4, + "width": 6, + "height": 4 + }, + "queryRef": { + "kind": "query", + "queryId": "3f86cb95-ad80-450b-9b07-f0c4f324a70e" + }, + "visualOptions": { + "multiStat__textSize": "auto", + "multiStat__valueColumn": "Value", + "colorRulesDisabled": false, + "colorRules": [ + { + "id": "c9b5efb8-db51-4db1-b681-fe6f408829c0", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": "==", + "column": "Column", + "values": [ + "UnitsManufactured" + ] + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "green", + "tag": "", + "icon": "completed", + "ruleName": "" + }, + { + "id": "f7e65c83-ba3d-4177-abbe-008df52934c4", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": "==", + "column": "Column", + "values": [ + "UnitsRejected" + ] + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "red", + "tag": "", + "icon": "critical", + "ruleName": "" + } + ], + "colorStyle": "light", + "multiStat__displayOrientation": "vertical", + "multiStat__labelColumn": null, + "multiStat__slot": { + "width": 2, + "height": 1 + } + } + }, + { + "id": "fc15aa93-a039-465b-8fc9-1bcf97c76cf7", + "title": "Equipment Overall Efficiency", + "visualType": "multistat", + "pageId": "25083f67-f980-4066-a550-5f8bb1a183d6", + "layout": { + "x": 9, + "y": 4, + "width": 9, + "height": 8 + }, + "queryRef": { + "kind": "query", + "queryId": "ceb76c0b-64cf-487f-9b1a-41c7c1adf68a" + }, + "visualOptions": { + "multiStat__textSize": "auto", + "multiStat__valueColumn": "Efficiency", + "colorRulesDisabled": false, + "colorRules": [ + { + "id": "63328d11-72ad-4d5f-80fd-75b27c1d69a9", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": ">", + "column": "Efficiency", + "values": [ + "95%" + ] + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "green", + "tag": "Excellent", + "icon": "happy", + "ruleName": "" + }, + { + "id": "4712b226-a175-4f58-b3ca-27f0233f6790", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": ">=", + "column": "Efficiency", + "values": [ + "90" + ] + }, + { + "values": [ + "95" + ], + "operator": "<", + "column": "Efficiency" + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "blue", + "tag": "Good", + "icon": "emojiNeutral", + "ruleName": "" + }, + { + "id": "a55db212-3c59-4b6c-af8f-da020772e4fe", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": "<", + "column": "Efficiency", + "values": [ + "90" + ] + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "yellow", + "tag": "Fair", + "icon": "sad", + "ruleName": "" + } + ], + "colorStyle": "light", + "multiStat__displayOrientation": "horizontal", + "multiStat__labelColumn": "EquipmentID", + "multiStat__slot": { + "width": 3, + "height": 2 + } + } + }, + { + "id": "8edfae11-6e9f-4d42-a6df-5ac010294610", + "title": "Contoso Motors", + "visualType": "markdownCard", + "pageId": "25083f67-f980-4066-a550-5f8bb1a183d6", + "layout": { + "x": 0, + "y": 0, + "width": 18, + "height": 4 + }, + "markdownText": "![Contoso Motors adn Auto Parts](https://github.com/{{GITHUB_ACCOUNT}}/azure_arc/blob/{{GITHUB_BRANCH}}/azure_jumpstart_ag/artifacts/adx_dashboards/contoso-motors-autoparts.png?raw=true)", + "visualOptions": {} + }, + { + "id": "67de7813-5182-483d-8e7f-b360b1ce1d5f", + "title": "Overall Efficiency", + "visualType": "card", + "pageId": "25083f67-f980-4066-a550-5f8bb1a183d6", + "layout": { + "x": 0, + "y": 4, + "width": 3, + "height": 4 + }, + "queryRef": { + "kind": "query", + "queryId": "6e57f2be-95d5-4805-98e4-3a57278c4f90" + }, + "visualOptions": { + "multiStat__textSize": "auto", + "multiStat__valueColumn": null, + "colorRulesDisabled": false, + "colorRules": [ + { + "id": "2d8c2bed-f661-4ad2-97fa-cee1b1a7cbae", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": ">", + "column": null, + "values": [ + "90" + ] + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "green", + "tag": "", + "icon": "happy", + "ruleName": "" + }, + { + "id": "b615fe3a-54a1-4b10-bff2-b51ac7c5bd5d", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": ">", + "column": null, + "values": [ + "80" + ] + }, + { + "values": [ + "90" + ], + "operator": "==", + "column": null + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "yellow", + "tag": "", + "icon": "emojiNeutral", + "ruleName": "" + }, + { + "id": "4eba33ea-9772-4f05-817c-aedc751609d8", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": "<", + "column": null, + "values": [ + "80" + ] + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "red", + "tag": "Poor", + "icon": "sad", + "ruleName": "" + } + ], + "colorStyle": "light" + } + }, + { + "id": "0236925d-7aec-4fb6-93cd-95e9934a26f3", + "title": "Contoso Motors", + "visualType": "markdownCard", + "pageId": "b26b3811-394b-4451-9407-40d162f34d1c", + "layout": { + "x": 0, + "y": 0, + "width": 18, + "height": 4 + }, + "markdownText": "![Contoso Motors adn Auto Parts](https://github.com/{{GITHUB_ACCOUNT}}/azure_arc/blob/{{GITHUB_BRANCH}}/azure_jumpstart_ag/artifacts/adx_dashboards/contoso-motors-autoparts.png?raw=true)", + "visualOptions": {} + }, + { + "id": "8ca3cd34-13aa-481d-82ee-ccdf69603f76", + "title": "Cars Manufactured by Model", + "visualType": "pie", + "pageId": "b26b3811-394b-4451-9407-40d162f34d1c", + "layout": { + "x": 9, + "y": 4, + "width": 9, + "height": 7 + }, + "queryRef": { + "kind": "query", + "queryId": "9277db4c-0c4d-4739-b571-df776263f87d" + }, + "visualOptions": { + "hideLegend": false, + "xColumn": null, + "yColumns": null, + "seriesColumns": null, + "crossFilterDisabled": false, + "drillthroughDisabled": false, + "labelDisabled": false, + "pie__label": [ + "name", + "percentage" + ], + "tooltipDisabled": false, + "pie__tooltip": [ + "name", + "percentage", + "value" + ], + "pie__orderBy": "size", + "pie__kind": "pie", + "pie__topNSlices": null, + "crossFilter": [], + "drillthrough": [] + } + }, + { + "id": "ffdc38cb-2792-467f-8445-1c4030398257", + "title": "Cars Manufactured by Plant Location", + "visualType": "bar", + "pageId": "b26b3811-394b-4451-9407-40d162f34d1c", + "layout": { + "x": 0, + "y": 4, + "width": 9, + "height": 7 + }, + "queryRef": { + "kind": "query", + "queryId": "bd5c10b0-a055-4888-bab0-9ba38fce173b" + }, + "visualOptions": { + "multipleYAxes": { + "base": { + "id": "-1", + "label": "Units Manufactured vs Rejected", + "columns": [], + "yAxisMaximumValue": null, + "yAxisMinimumValue": null, + "yAxisScale": "linear", + "horizontalLines": [] + }, + "additional": [], + "showMultiplePanels": false + }, + "hideLegend": false, + "xColumnTitle": "Plant Location", + "xColumn": null, + "yColumns": null, + "seriesColumns": null, + "xAxisScale": "linear", + "verticalLine": "", + "crossFilterDisabled": false, + "drillthroughDisabled": false, + "crossFilter": [], + "drillthrough": [] + } + }, + { + "id": "c2ec69a9-9f1e-485f-a49c-44a28c27ba65", + "title": "Availability", + "visualType": "card", + "pageId": "25083f67-f980-4066-a550-5f8bb1a183d6", + "layout": { + "x": 0, + "y": 8, + "width": 3, + "height": 4 + }, + "queryRef": { + "kind": "query", + "queryId": "bfab8c5d-284e-4309-a647-7e37c4b8c2ba" + }, + "visualOptions": { + "multiStat__textSize": "auto", + "multiStat__valueColumn": null, + "colorRulesDisabled": false, + "colorRules": [ + { + "id": "a7d41089-df67-4dae-a974-a5fd4116003f", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": ">", + "column": null, + "values": [ + "90" + ] + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "green", + "tag": "Good", + "icon": "completed", + "ruleName": "" + }, + { + "id": "3d2d5448-8bf9-4b7f-add5-d050d8a2b75d", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": ">", + "column": null, + "values": [ + "80" + ] + }, + { + "operator": "<=", + "column": null, + "values": [ + "90" + ] + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "yellow", + "tag": "Fair", + "icon": "critical", + "ruleName": "" + }, + { + "id": "912eb6bb-40f4-4a1e-82b4-c287343c878a", + "ruleType": "colorByCondition", + "applyToColumn": null, + "hideText": false, + "applyTo": "cells", + "conditions": [ + { + "operator": "<", + "column": null, + "values": [ + "80" + ] + } + ], + "chainingOperator": "and", + "visualType": "stat", + "colorStyle": "bold", + "color": "red", + "tag": "", + "icon": "warning", + "ruleName": "" + } + ], + "colorStyle": "light" + } + } + ], + "baseQueries": [], + "parameters": [ + { + "kind": "duration", + "id": "72e78587-ba97-4390-9374-b93688428c00", + "displayName": "Time range", + "description": "", + "beginVariableName": "_startTime", + "endVariableName": "_endTime", + "defaultValue": { + "kind": "dynamic", + "count": 1, + "unit": "hours" + }, + "showOnPages": { + "kind": "all" + } + } + ], + "dataSources": [ + { + "id": "0a4a6594-6c22-4a61-9c94-63ddb16967af", + "name": "{{ADX_CLUSTER_NAME}}", + "scopeId": "kusto", + "clusterUri": "{{ADX_CLUSTER_URI}}", + "database": "manufacturing", + "kind": "manual-kusto" + } + ], + "pages": [ + { + "name": "Production Efficiency", + "id": "25083f67-f980-4066-a550-5f8bb1a183d6" + }, + { + "id": "b26b3811-394b-4451-9407-40d162f34d1c", + "name": "Production Metrics" + } + ], + "queries": [ + { + "dataSource": { + "kind": "inline", + "dataSourceId": "0a4a6594-6c22-4a61-9c94-63ddb16967af" + }, + "text": "assemblyline\n| extend cp= parse_json(cars_produced)\n| mv-apply cp on (extend cp.model, cp.engine_type)\n| distinct Model = tostring(cp.model), EngineType = tostring(cp.engine_type)\n", + "id": "266487a6-71ec-4dd7-9990-5f1b6a2aca2a", + "usedVariables": [] + }, + { + "dataSource": { + "kind": "inline", + "dataSourceId": "0a4a6594-6c22-4a61-9c94-63ddb16967af" + }, + "text": "assemblyline\n| extend cp= parse_json(cars_produced)\n| mv-apply cp on (extend cp.model, cp.engine_type, cp.assembly_status, cp.quality_check)\n| where cp.assembly_status == \"completed\"\n| summarize Passed = countif(cp.quality_check == \"pass\"), Failed = countif(cp.quality_check == \"fail\") | evaluate narrow()\n| project Column, toint(Value)", + "id": "caa8a37f-0829-497e-ac88-6b96c03137e1", + "usedVariables": [] + }, + { + "dataSource": { + "kind": "inline", + "dataSourceId": "0a4a6594-6c22-4a61-9c94-63ddb16967af" + }, + "text": "// Final cars production\nassemblyline\n| extend ap= parse_json(actual_production)\n| mv-apply ap on (extend ap.units_manufactured, ap.units_rejected)\n| summarize UnitsManufactured = sum(toint(ap.units_manufactured)), UnitsRejected = sum(toint(ap.units_rejected)) | evaluate narrow()\n| project Column, toint(Value)", + "id": "3f86cb95-ad80-450b-9b07-f0c4f324a70e", + "usedVariables": [] + }, + { + "dataSource": { + "kind": "inline", + "dataSourceId": "0a4a6594-6c22-4a61-9c94-63ddb16967af" + }, + "text": "assemblyline\n| extend ee = parse_json(equipment_telemetry)\n| mv-apply ee on (extend ee.date_time, ee.equipment_id, ee.efficiency)\n| summarize Efficiency = round(avg( toreal(ee.efficiency)), 2) by EquipmentID = tostring(ee.equipment_id) \n", + "id": "ceb76c0b-64cf-487f-9b1a-41c7c1adf68a", + "usedVariables": [] + }, + { + "dataSource": { + "kind": "inline", + "dataSourceId": "0a4a6594-6c22-4a61-9c94-63ddb16967af" + }, + "text": "assemblyline\n| extend ee = parse_json(equipment_telemetry)\n| mv-apply ee on (extend ee.date_time, ee.equipment_id, ee.efficiency)\n| summarize Efficiency = round(avg( toreal(ee.efficiency)), 2)", + "id": "6e57f2be-95d5-4805-98e4-3a57278c4f90", + "usedVariables": [] + }, + { + "dataSource": { + "kind": "inline", + "dataSourceId": "0a4a6594-6c22-4a61-9c94-63ddb16967af" + }, + "text": "// Total cars manufactured by type\nassemblyline\n| extend cp= parse_json(cars_produced)\n| mv-apply cp on (extend cp.model, cp.engine_type, cp.assembly_status, cp.quality_check)\n| where cp.assembly_status == \"completed\"\n| summarize count() by tostring(cp.model)", + "id": "9277db4c-0c4d-4739-b571-df776263f87d", + "usedVariables": [] + }, + { + "dataSource": { + "kind": "inline", + "dataSourceId": "0a4a6594-6c22-4a61-9c94-63ddb16967af" + }, + "text": "// Car production by plant\nassemblyline\n| extend ap= parse_json(actual_production), pd = parse_json(plant_details)\n| mv-apply ap on (extend ap.units_manufactured, ap.units_rejected)\n| mv-apply ap on (extend pd.plant_id, pd.location)\n| where ap.units_manufactured > 0 or ap.units_rejected > 0\n| project plant_id = pd.plant_id, location = pd.location, units_manufactured = ap.units_manufactured, units_rejected = ap.units_rejected\n| summarize UnitsManufactured = sum(toint(units_manufactured)), UnitsRejected = sum(toint(units_rejected)) by Location = tostring(location) \n", + "id": "bd5c10b0-a055-4888-bab0-9ba38fce173b", + "usedVariables": [] + }, + { + "dataSource": { + "kind": "inline", + "dataSourceId": "0a4a6594-6c22-4a61-9c94-63ddb16967af" + }, + "text": "assemblyline\n| extend pm = parse_json(performance_metrics)\n| mv-apply pm on (extend pm.availability_oee)\n| summarize availability = round(avg(toreal(pm.availability_oee)) * 100.0, 2)", + "id": "bfab8c5d-284e-4309-a647-7e37c4b8c2ba", + "usedVariables": [] + } + ] +} \ No newline at end of file diff --git a/azure_jumpstart_ag/artifacts/adx_dashboards/contoso-motors-autoparts.png b/azure_jumpstart_ag/artifacts/adx_dashboards/contoso-motors-autoparts.png new file mode 100644 index 0000000000..eebca5a038 Binary files /dev/null and b/azure_jumpstart_ag/artifacts/adx_dashboards/contoso-motors-autoparts.png differ diff --git a/azure_jumpstart_ag/artifacts/data_emulator/data-emulator.py b/azure_jumpstart_ag/artifacts/data_emulator/data-emulator.py new file mode 100644 index 0000000000..f312a22f3d --- /dev/null +++ b/azure_jumpstart_ag/artifacts/data_emulator/data-emulator.py @@ -0,0 +1,369 @@ +import json +import random +from datetime import timedelta, time, datetime +import time +import os +from azure.eventhub import EventHubProducerClient, EventData +import sys +import uuid + +# Create following environment variables to publish events to Event Hub +# EVENTHUB_CONNECTION_STRING = '' +# EVENTHUB_NAME = 'manufacturing' +# HISTORICAL_DATA_DAYS = 7 + +EVENTHUB_CONNECTION_STRING = os.environ.get('EVENTHUB_CONNECTION_STRING') +EVENTHUB_NAME = os.environ.get('EVENTHUB_NAME') +HISTORICAL_DATA_DAYS = (0-int(os.environ.get('HISTORICAL_DATA_DAYS', 7))) # This is to generate date prior to current date for dashboards. + +# Create event producer as a global object to avoid connection creation overhead for each event. +if EVENTHUB_CONNECTION_STRING == "" or EVENTHUB_NAME == "": + print('Event Hub connection string and/or Event Hub name not configured.') + sys.exit() + +# Initialize EventHub connection to send events +event_producer = EventHubProducerClient.from_connection_string(conn_str=EVENTHUB_CONNECTION_STRING, eventhub_name=EVENTHUB_NAME) + +maintenance_last_generated = datetime.now() +production_last_generated = datetime.now() + +# Define common schema templates to generate telemetry data +plant_details = [ + { "plant_id": "PT-01", "location": "Detroit, US" }, + { "plant_id": "PT-02", "location": "Monterrey, MX" }, + { "plant_id": "PT-03", "location": "Shanghai, CN" }, + { "plant_id": "PT-04", "location": "Hamburg, DE" } +] + +production_schedule = {"production_date": "2024-03-20", "scheduled_start": "20:00", "scheduled_end": "08:00", "planned_production_time_hours": 12, "actual_production_time_hours": 10} + +employees = [ + {"employee_id": "E-001", "name": "John Doe", "role": "supervisor"}, + {"employee_id": "E-002", "name": "Jane Smith", "role": "engineer"}, + {"employee_id": "E-003", "name": "Mike Johnson", "role": "technician"} +] + +cars_produced = [ + {"assembly_line": "AL-01", "car_id": "E-001", "model": "Sedan", "color": "red", "engine_type": "electric", "assembly_status": "none", "quality_check": "none" }, + {"assembly_line": "AL-02", "car_id": "H-002", "model": "SUV", "color": "blue", "engine_type": "hybrid", "assembly_status": "none", "quality_check": "none" }, + {"assembly_line": "AL-03", "car_id": "G-003", "model": "Coupe", "color": "black", "engine_type": "gasoline", "assembly_status": "none", "quality_check": "none" } +] + +equipment_info = [ + { + "equipment_id": "EQ-001", + "type": "assembly_robot", + "maintenance_schedule": "30d", + "technical_specs": {} + }, + { + "equipment_id": "EQ-002", + "type": "conveyor_belt", + "maintenance_schedule": "10d", + "technical_specs": {} + }, + { + "equipment_id": "EQ-003", + "type": "paint_station", + "maintenance_schedule": "90d", + "technical_specs": {} + }, + { + "equipment_id": "EQ-004", + "type": "welding-assembly_robot", + "model": "RoboArm X2000", + "maintenance_schedule": "15d", + "technical_specs": { + "arm_reach": "1.5 meters", + "load_capacity": "10 kg", + "precision": "0.02 mm", + "rotation": "360 degrees" + } + }, + { + "equipment_id": "WLD-001", + "type": "welding_robot", + "model": "WeldMaster 3000", + "maintenance_schedule": "5d", + "technical_specs": { + "welding_speed": "1.5 meters per minute", + "welding_technologies": ["MIG", "TIG"], + "maximum_thickness": "10 mm", + "precision": "+/- 0.5 mm" + } + } +] + +# Equipment +equipment_maintenance_history = [ + { + "equipment_id": "WLD-001", + "date": "2024-03-20", + "start_time": "10:21", + "end_time": "12:30", + "type": "routine_check", + "notes": "All systems operational, no issues found." + }, + { + "equipment_id": "WLD-001", + "date": "2024-01-15", + "start_time": "8:00", + "end_time": "8:30", + "type": "repair", + "notes": "Replaced servo motor in joint 3." + } +] + +maintenance_types = [ + { "type": "routine_check", "message": "All systems operational, no issues found" }, + { "type": "emergency_check", "message": "Equipment is in critical condition, need immediate attention" } +] + +production_shifts = [ + { "shift":"morning", "utc_start_hour":0, "utc_end_hour":7 }, + { "shift":"afternoon", "utc_start_hour":8, "utc_end_hour":15 }, + { "shift":"night", "utc_start_hour":16, "utc_end_hour":23 } +] + +# Get shift information +def get_shift(current_date_time): + for shift in production_shifts: + if current_date_time.hour >= shift['utc_start_hour'] and current_date_time.hour <= shift['utc_end_hour']: + return shift['shift'] + +# produce this randomly with random delay between 1 to 3 hours +def simulate_equipment_maintenance(current_time): + return { + "equipment_id": "WLD-001", # Pick random equipment + "maintenance_date": str(current_time.date), + "start_time": "10:21", # Produce random + "end_time": "12:30", # Add random duration + "type": "routine_check", + "notes": "All systems operational, no issues found." + } + +# Produce equipment telemetry every 30 seconds or a minute +def simulate_equipment_telemetry(current_time): + # Convert time string + current_time = str(current_time) + + equipment_telemetry = [ + # Assembly Robot + { + "date_time": current_time, + "equipment_id": "EQ-001", + "type": "assembly_robot", + "status": random.choice(["operational", "maintenance_required"]), # Instead of random, use periodic maintenance + "operational_time_hours": random.uniform(10, 12), + "cycles_completed": random.randint(3400, 3500), + "efficiency": random.uniform(93, 97), + "maintenance_alert": random.choice(["none", "scheduled_check", "urgent_maintenance_required"]), # Instead of random, use periodic maintenance alerts + "last_maintenance": "2024-02-20", + "next_scheduled_maintenance": "2024-04-01" + }, + # Conveyor Belt + { + "date_time": current_time, + "equipment_id": "EQ-002", + "type": "conveyor_belt", + "status": random.choice(["operational", "maintenance_required"]), + "operational_time_hours": random.uniform(10, 12), + "distance_covered_meters": random.randint(10000, 12000), + "efficiency": random.uniform(98, 99), + "maintenance_alert": random.choice(["none", "scheduled_check"]), + "last_maintenance": "2024-03-15", + "next_scheduled_maintenance": "2024-03-30" + }, + # Paint Station + { + "date_time": current_time, + "equipment_id": "EQ-003", + "type": "paint_station", + "status": random.choice(["operational", "maintenance_required"]), + "operational_time_hours": random.uniform(7, 9), + "units_processed": random.randint(400, 500), + "efficiency": random.uniform(90, 93), + "maintenance_alert": random.choice(["none", "urgent_maintenance_required"]), + "last_maintenance": "2024-02-25", + "next_scheduled_maintenance": "Overdue" + }, + # Welding-Assembly Robot + { + "date_time": current_time, + "equipment_id": "EQ-004", + "status": random.choice(["operational", "maintenance_required"]), + "operational_time_hours": random.uniform(10, 12), + "cycles_completed": random.randint(3400, 3500), + "efficiency": random.uniform(94, 96), + "maintenance_alert": random.choice(["none", "scheduled_check"]), + "last_maintenance": "2024-03-20", + "next_scheduled_maintenance": "2024-04-01", + "operation_stats": { + "average_cycle_time": "10 seconds", + "failures_last_month": random.randint(1, 3), + "success_rate": random.uniform(98.5, 99.9) + } + }, + # Welding Robot + { + "date_time": current_time, + "equipment_id": "WLD-001", + "status": random.choice(["operational", "maintenance_required"]), + "operational_time_hours": random.uniform(9, 11), + "welds_completed": random.randint(5100, 5300), + "efficiency": random.uniform(97, 99), + "maintenance_alert": random.choice(["none", "scheduled_check"]), + "last_maintenance": "2024-03-22", + "next_scheduled_maintenance": "2024-04-05", + "operation_stats": { + "average_weld_time": "30 seconds", + "failures_last_month": random.randint(0, 3), + "success_rate": random.uniform(98, 99.9) + } + } + ] + + return equipment_telemetry + +# Update assembly status with progress, start with in_progress and end with completed. Update status every 1 minutes +def simulate_production_telemetry(): + # Once the status completed change production id + for car in cars_produced: + car['assembly_status'] = random.choice(["completed", "in_progress"]) + if car['assembly_status'] == "completed": + car['quality_check'] = random.choice(["pass", "fail"]) + + return cars_produced + +# Produce this data for every 8 hours +def simulate_production_by_shift(): + return {} + +# Generate performance metrics +def generate_performance_metrics(): + return { + "availability_oee": random.uniform(0.95, 0.99), + "reject_rate": random.uniform(0.04, 0.06), + "comments": "Minor downtime due to equipment maintenance. Overall production efficiency remains high." + } + +# Generate actual production data +def generate_actual_production_data(date_time): + return { + "start_time": "20:00", + "end_time": "07:30", + "actual_production_time_hours": 11.5, + "production_downtime_hours": 0.5, + "units_manufactured": random.randint(690, 710), + "units_rejected": random.randint(30, 40), + "details": [ + {"utc_hour": "20", "units_produced": random.randint(55, 60), "units_rejected": random.randint(2, 4)}, + # Additional hourly details can be added here + ] + } + +# Produce assembly production data every minute for realtime data and every hour for historical data +def simulate_assembly_line_data(date_time): + + # Get current shift + current_shift = get_shift(date_time) + + # Produce cars production progress data every time this method is called + cars_produced = simulate_production_telemetry() + + # Produce cars production stats every one hour + global maintenance_last_generated + if int((date_time - maintenance_last_generated).total_seconds() / 60) > 5: + actual_production = simulate_production_by_shift() + else: + actual_production = {} + + # Produce equipment telemetry data every time this method is called + equipment_telemetry = simulate_equipment_telemetry(date_time) + + # Produce equipment maintenance data every 5 minutes + global production_last_generated + if int((date_time - production_last_generated).total_seconds() / (5 *60)) > 5: + equipment_maintenance = simulate_equipment_maintenance(date_time) + maintenance_last_generated = date_time + else: + equipment_maintenance = {} + + # Make this by shift production information + actual_production = generate_actual_production_data(date_time) + + # Include in the hourly production performance metrics + performance_metrics = generate_performance_metrics() + + # Split this into hourly and by shift + current_time = date_time.isoformat() + "Z" + simulation_data = { + "date_time": current_time, + "plant_details": plant_details[random.randint(0, len(plant_details)-1)], + "shift": current_shift, + "employees_on_shift": employees, + "cars_produced": cars_produced, + "equipment_maintenance": equipment_maintenance, + "production_schedule": production_schedule, + "actual_production": actual_production, + "equipment_telemetry": equipment_telemetry, + "performance_metrics": performance_metrics + } + + return simulation_data + +def send_product_to_event_hub(simulation_data): + + # format data to pubish into staging table + payload_data = { + "id": str(uuid.uuid4()), + "source": "data_emulator", + "type": "data_emulator", + "data_base64": simulation_data, # Data in JSON format + "time": str(datetime.now().isoformat()) + "Z", + "specversion": 1, + "subject": "topic/dataemulator" + } + event_data = EventData(json.dumps(payload_data)) + event_producer.send_batch([event_data]) + +if __name__ == "__main__": + + # Generate batch and continue with live data + user_option = input("Would you like generate past data (1) or current data (2) choose your option: ") + if user_option == "": + user_option = 1 + else: + user_option = int(user_option) + + if user_option == 1: + number_of_days = input("Enter number of days to generate past data (default is 7 days): ") + if number_of_days == "": + number_of_days = -7 + else: + number_of_days = (0 - int(number_of_days)) + + production_datetime = datetime.now() + timedelta(days=number_of_days) + + try: + if user_option == 1: + while production_datetime <= datetime.now(): + print('Generating for: ', production_datetime) + simulated_data = simulate_assembly_line_data(production_datetime) + send_product_to_event_hub(simulated_data) + + # Increment time + production_datetime += timedelta(minutes=1) + else: + # Generate live data + print('Now generating live date...') + while True: + current_time = datetime.now() + # Produce equipment telemetry data every 30 seconds + print('Generating for: ', current_time) + simulated_data = simulate_assembly_line_data(current_time) + send_product_to_event_hub(simulated_data) + + time.sleep(30) # send data every 30 seconds + finally: + event_producer.close() diff --git a/azure_jumpstart_ag/artifacts/icons/contoso-motors.png b/azure_jumpstart_ag/artifacts/icons/contoso-motors.png new file mode 100644 index 0000000000..8efc39e558 Binary files /dev/null and b/azure_jumpstart_ag/artifacts/icons/contoso-motors.png differ diff --git a/azure_jumpstart_ag/artifacts/icons/contoso-motors.svg b/azure_jumpstart_ag/artifacts/icons/contoso-motors.svg new file mode 100644 index 0000000000..010d622981 --- /dev/null +++ b/azure_jumpstart_ag/artifacts/icons/contoso-motors.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/azure_jumpstart_ag/artifacts/monitoring/arc-inventory-workbook.bicep b/azure_jumpstart_ag/artifacts/monitoring/arc-inventory-workbook.bicep new file mode 100644 index 0000000000..48c761318c --- /dev/null +++ b/azure_jumpstart_ag/artifacts/monitoring/arc-inventory-workbook.bicep @@ -0,0 +1,30 @@ +@description('The friendly name for the workbook that is used in the Gallery or Saved List. This name must be unique within a resource group.') +param workbookDisplayName string = 'Azure Arc-enabled resources inventory' + +@description('The gallery that the workbook will been shown under. Supported values include workbook, tsg, etc. Usually, this is \'workbook\'') +param workbookType string = 'workbook' + +@description('The id of resource instance to which the workbook will be associated') +param workbookSourceId string = 'azure monitor' + +@description('The unique guid for this workbook instance') +param workbookId string = 'c5c6a9e5-74fc-465a-9f11-1dd10aad501b' + +@description('The location to deploy the workbook to') +param location string = resourceGroup().location + +resource workbookId_resource 'microsoft.insights/workbooks@2022-04-01' = { + name: workbookId + location: location + kind: 'shared' + properties: { + displayName: workbookDisplayName + serializedData: '{"version":"Notebook/1.0","items":[{"type":9,"content":{"version":"KqlParameterItem/1.0","parameters":[{"id":"d8a4990e-5fd5-4c61-92a2-d07d04736a00","version":"KqlParameterItem/1.0","name":"Subscription","type":6,"isRequired":true,"typeSettings":{"additionalResourceOptions":[],"includeAll":false},"timeContext":{"durationMs":86400000},"value":"/subscriptions/00000000-0000-0000-0000-000000000000"},{"id":"b616a3a3-4271-4208-b1a9-a92a78efed08","version":"KqlParameterItem/1.0","name":"ResourceGroup","label":"Resource group","type":2,"isRequired":true,"query":"resourcecontainers \\r\\n| where type =~ \'microsoft.resources/subscriptions/resourcegroups\' \\r\\n| project name","crossComponentResources":["{Subscription}"],"typeSettings":{"additionalResourceOptions":[],"showDefault":false},"queryType":1,"resourceType":"microsoft.resourcegraph/resources","value":"rg-placeholder"},{"id":"05578175-fbe8-4dd2-9c6a-dec2f503d6cf","version":"KqlParameterItem/1.0","name":"Location","type":8,"isRequired":true,"multiSelect":true,"quote":"\'","delimiter":",","value":["value::all"],"typeSettings":{"additionalResourceOptions":["value::all"],"includeAll":true},"defaultValue":"value::all"},{"id":"3f095357-8b61-4bd5-bb16-bb03da10f44c","version":"KqlParameterItem/1.0","name":"ResourceType","type":7,"isRequired":true,"multiSelect":true,"quote":"\'","delimiter":",","value":["microsoft.hybridcompute/machines","microsoft.compute/virtualmachines"],"typeSettings":{"additionalResourceOptions":[],"showDefault":false},"jsonData":" [\\r\\n { \\"value\\": \\"microsoft.compute/virtualmachines\\", \\"label\\": \\"Azure Virtual Machine\\", \\"selected\\":true}, \\r\\n \\r\\n { \\"value\\": \\"microsoft.hybridcompute/machines\\", \\"label\\": \\"Arc enabled server\\", \\"selected\\":true }]"}],"style":"pills","queryType":0,"resourceType":"microsoft.operationalinsights/workspaces"},"name":"parameters - 4"},{"type":1,"content":{"json":"# Azure Arc-enabled resources inventory\\n"},"name":"text - 1"},{"type":12,"content":{"version":"NotebookGroup/1.0","groupType":"editable","title":"Machines overall status & configurations","expandable":true,"expanded":true,"items":[{"type":3,"content":{"version":"KqlItem/1.0","query":"resources \\r\\n| where subscriptionId == \'{Subscription:subscriptionid}\' and resourceGroup == tolower(\'{ResourceGroup}\')\\r\\n| extend osType = coalesce(tostring(properties.osName), tostring(properties.osType), tostring(properties.storageProfile.osDisk.osType))\\r\\n| summarize\\r\\nazureLinux = countif(type =~ \\"microsoft.compute/virtualmachines\\" and osType =~ \\"Linux\\"),\\r\\narcLinux = countif(type =~ \\"microsoft.hybridcompute/machines\\" and osType =~ \\"Linux\\"),\\r\\nazureWindows = countif(type =~ \\"microsoft.compute/virtualmachines\\" and osType =~ \\"Windows\\"),\\r\\narcWindows = countif(type =~ \\"microsoft.hybridcompute/machines\\" and osType =~ \\"Windows\\")\\r\\n| project machinePack = pack(\\"Azure virtual machines-Linux\\", azureLinux, \\"Arc enabled servers-Linux\\", arcLinux, \\"Azure virtual machines-Windows\\", azureWindows, \\"Arc enabled servers-Windows\\", arcWindows)\\r\\n| mv-expand machinePack\\r\\n| extend machine = tostring(bag_keys(machinePack)[0])\\r\\n| extend count_ = tolong(machinePack[machine])\\r\\n| project machine, count_ ","size":3,"title":"Total machines","queryType":1,"resourceType":"microsoft.resourcegraph/resources","crossComponentResources":["{Subscription}"],"visualization":"piechart","tileSettings":{"showBorder":false},"graphSettings":{"type":0},"mapSettings":{"locInfo":"LatLong","sizeSettings":"azureLinux","sizeAggregation":"Sum","legendMetric":"azureLinux","legendAggregation":"Sum","itemColorSettings":{"type":"heatmap","colorAggregation":"Sum","nodeColorField":"azureLinux","heatmapPalette":"greenRed"}}},"customWidth":"50","name":"query - 0","styleSettings":{"maxWidth":"50%"}},{"type":3,"content":{"version":"KqlItem/1.0","query":"resources\\r\\n| where subscriptionId == \'{Subscription:subscriptionid}\' and resourceGroup == tolower(\'{ResourceGroup}\')\\r\\n| where type in~ ({ResourceType})\\r\\n| where location in~ ({Location})\\r\\n| extend statuso = iff(isnull(properties.extended.instanceView.powerState.displayStatus), (properties.status), (properties.extended.instanceView.powerState.displayStatus))\\r\\n| where isnotnull(statuso)\\r\\n| summarize count() by tostring(statuso)","size":0,"title":"Status of machines","queryType":1,"resourceType":"microsoft.resourcegraph/resources","crossComponentResources":["{Subscription}"],"visualization":"tiles","tileSettings":{"titleContent":{"columnMatch":"statuso","formatter":1},"leftContent":{"columnMatch":"count_","formatter":12,"formatOptions":{"palette":"auto"},"numberFormat":{"unit":17,"options":{"maximumSignificantDigits":3,"maximumFractionDigits":2}}},"showBorder":false,"sortCriteriaField":"statuso","sortOrderField":2}},"customWidth":"50","name":"query - 7","styleSettings":{"maxWidth":"50%"}}]},"name":"confGroup","styleSettings":{"showBorder":true}},{"type":3,"content":{"version":"KqlItem/1.0","query":"resources\\r\\n| where subscriptionId == \'{Subscription:subscriptionid}\' and resourceGroup == tolower(\'{ResourceGroup}\')\\r\\n| where type =~ \'microsoft.hybridcompute/machines\'\\r\\n| extend id = tolower(id)\\r\\n| join(policyresources\\r\\n| extend ComplianceState = tostring(properties[\'complianceState\'])\\r\\n| extend id = tolower(properties[\'resourceId\'])) on id\\r\\n| summarize compliantCount = countif(ComplianceState == \\"Compliant\\"), nonCompliantCount = countif(ComplianceState == \\"NonCompliant\\") by name, type, id\\r\\n| project id, name, type, compliantCount, nonCompliantCount\\r\\n| sort by name asc\\r\\n| join (\\r\\nresources\\r\\n| where resourceGroup =~ \'{ResourceGroup}\'\\r\\n| where type =~ \'microsoft.hybridcompute/machines\'\\r\\n| extend id = tolower(id)\\r\\n| extend state = properties.status\\r\\n| extend status = case(\\r\\n state =~ \'Connected\', \'Connected\',\\r\\n state =~ \'Disconnected\', \'Offline\',\\r\\n state =~ \'Error\', \'Error\',\\r\\n state =~ \'Expired\', \'Expired\',\\r\\n \'\')\\r\\n| extend agentVersion = properties.agentVersion\\r\\n| extend Application = tags.Application\\r\\n| extend operatingSystem = properties.osSku\\r\\n| extend resourceGroup = strcat(\\"/subscriptions/\\", subscriptionId, \\"/resourceGroups/\\", resourceGroup)\\r\\n| extend majorVersion = tostring(split(agentVersion, \'.\')[0])\\r\\n| extend minorVersion = tostring(split(agentVersion, \'.\')[1])\\r\\n| extend agentVersion = strcat(majorVersion, \'.\', minorVersion)\\r\\n| project id, status, agentVersion, operatingSystem, location, Application) on id\\r\\n| project id, status, agentVersion, operatingSystem, location, Application, CompliantPolicies=compliantCount, NonCompliantPolicies=nonCompliantCount\\r\\n","size":1,"title":"Azure Arc-enabled servers inventory","queryType":1,"resourceType":"microsoft.resourcegraph/resources","crossComponentResources":["value::all"],"gridSettings":{"formatters":[{"columnMatch":"status","formatter":18,"formatOptions":{"thresholdsOptions":"icons","thresholdsGrid":[{"operator":"==","thresholdValue":"Connected","representation":"success","text":"{0}{1}"},{"operator":"Default","thresholdValue":null,"representation":"warning","text":"{0}{1}"}]}},{"columnMatch":"CompliantPolicies","formatter":18,"formatOptions":{"thresholdsOptions":"icons","thresholdsGrid":[{"operator":"Default","thresholdValue":null,"representation":"success","text":"{0}{1}"}]}},{"columnMatch":"NonCompliantPolicies","formatter":18,"formatOptions":{"thresholdsOptions":"icons","thresholdsGrid":[{"operator":"Default","thresholdValue":null,"representation":"3","text":"{0}{1}"}]}},{"columnMatch":"resourceGroup","formatter":14,"formatOptions":{"linkTarget":null,"showIcon":true}},{"columnMatch":"subscriptionId","formatter":15,"formatOptions":{"linkTarget":null,"showIcon":true}}]}},"name":"query - 0"},{"type":3,"content":{"version":"KqlItem/1.0","query":"resources\\r\\n| where subscriptionId == \'{Subscription:subscriptionid}\' and resourceGroup == tolower(\'{ResourceGroup}\')\\r\\n| where type =~ \'microsoft.kubernetes/connectedclusters\'\\r\\n| extend id = tolower(id)\\r\\n| join(policyresources\\r\\n| extend ComplianceState = tostring(properties[\'complianceState\'])\\r\\n| extend id = tolower(properties[\'resourceId\'])) on id\\r\\n| summarize compliantCount = countif(ComplianceState == \\"Compliant\\"), nonCompliantCount = countif(ComplianceState == \\"NonCompliant\\") by name, type, id\\r\\n| project id, name, type, compliantCount, nonCompliantCount\\r\\n| sort by name asc\\r\\n| join (\\r\\nresources\\r\\n| where resourceGroup =~ \'{ResourceGroup}\'\\r\\n| where type =~ \'microsoft.kubernetes/connectedclusters\'\\r\\n| extend id = tolower(id)\\r\\n| extend state = properties.connectivityStatus\\r\\n| extend status = case(\\r\\n state =~ \'Connected\', \'Connected\',\\r\\n state =~ \'Disconnected\', \'Offline\',\\r\\n state =~ \'Error\', \'Error\',\\r\\n state =~ \'Expired\', \'Expired\',\\r\\n \'\')\\r\\n| extend resourceGroup = strcat(\\"/subscriptions/\\", subscriptionId, \\"/resourceGroups/\\", resourceGroup)\\r\\n| extend kubernetesVersion = properties.kubernetesVersion\\r\\n| project id, status, kubernetesVersion) on id\\r\\n| project id, status,kubernetesVersion,CompliantPolicies=compliantCount, NonCompliantPolicies=nonCompliantCount\\r\\n\\r\\n\\r\\n","size":1,"title":"Azure Arc-enabled Kubernetes clusters inventory","queryType":1,"resourceType":"microsoft.resourcegraph/resources","crossComponentResources":["value::all"],"gridSettings":{"formatters":[{"columnMatch":"status","formatter":18,"formatOptions":{"thresholdsOptions":"icons","thresholdsGrid":[{"operator":"==","thresholdValue":"Connected","representation":"success","text":"{0}{1}"},{"operator":"Default","thresholdValue":null,"representation":"warning","text":"{0}{1}"}]}},{"columnMatch":"CompliantPolicies","formatter":18,"formatOptions":{"thresholdsOptions":"icons","thresholdsGrid":[{"operator":"Default","thresholdValue":null,"representation":"success","text":"{0}{1}"}]}},{"columnMatch":"NonCompliantPolicies","formatter":18,"formatOptions":{"thresholdsOptions":"icons","thresholdsGrid":[{"operator":"Default","thresholdValue":null,"representation":"3","text":"{0}{1}"}]}},{"columnMatch":"resourceGroup","formatter":14,"formatOptions":{"linkTarget":null,"showIcon":true}},{"columnMatch":"subscriptionId","formatter":15,"formatOptions":{"linkTarget":null,"showIcon":true}}]}},"name":"query - 0 - Copy"},{"type":12,"content":{"version":"NotebookGroup/1.0","groupType":"editable","title":"Updates Data Overview","expandable":true,"expanded":true,"items":[{"type":3,"content":{"version":"KqlItem/1.0","query":"\\r\\n(resources //join of virtual machines, you can play with params as you see fit.\\r\\n| where type in~ ({ResourceType}\\r\\n)\\r\\n| where subscriptionId == \'{Subscription:subscriptionid}\' and resourceGroup == tolower(\'{ResourceGroup}\')\\r\\n| where location in ({Location})\\r\\n| extend os = iff(type =~ \\"microsoft.compute/virtualmachines\\", tolower(tostring(properties.storageProfile.osDisk.osType)), tolower(coalesce(tostring(properties.osName), tostring(properties.osType))))\\r\\n| extend id=tolower(id)\\r\\n| extend status=iff(type =~ \\"microsoft.compute/virtualmachines\\", properties.extended.instanceView.powerState.displayStatus, properties.status)\\r\\n| project id, name, os, status, resourceProperties=properties)\\r\\n| join kind=leftouter //finally, making a left outer join to fetch updates details from patchassessment\\r\\n((patchassessmentresources\\r\\n| where type in~ (\\"microsoft.compute/virtualmachines/patchassessmentresults\\", \\"microsoft.hybridcompute/machines/patchassessmentresults\\")\\r\\n| where location in ({Location})\\r\\n| where properties.status == \\"Succeeded\\"\\r\\n| parse id with resourceId \\"/patchAssessmentResults\\" *\\r\\n| extend resourceId=tolower(resourceId)\\r\\n| project resourceId, assessProperties=properties))\\r\\non $left.id == $right.resourceId //join on resources id & patchassessment id that is parsed.\\r\\n| summarize\\r\\ntotal = countif(1 == 1),\\r\\nnodata = countif(isnull(assessProperties) == true),\\r\\npendingReboot = countif(isnotnull(assessProperties) and assessProperties.rebootPending == \\"true\\"),\\r\\n//pendingUpdates - when any classification has > 0 updates\\r\\npendingUpdatesWindows = countif(isnotnull(assessProperties) and assessProperties.osType =~ \\"Windows\\" and (assessProperties.availablePatchCountByClassification.critical>0 or assessProperties.availablePatchCountByClassification.security>0 or assessProperties.availablePatchCountByClassification.updateRollup>0 or assessProperties.availablePatchCountByClassification.featurePack>0 or assessProperties.availablePatchCountByClassification.servicePack>0 or assessProperties.availablePatchCountByClassification.definition>0 or assessProperties.availablePatchCountByClassification.tools>0 or assessProperties.availablePatchCountByClassification.updates>0)),\\r\\npendingUpdatesLinux = countif(isnotnull(assessProperties) and assessProperties.osType =~ \\"Linux\\" and (assessProperties.availablePatchCountByClassification.security>0 or assessProperties.availablePatchCountByClassification.other>0)),\\r\\n//noPendingUpdates - when all classifications has 0 updates\\r\\nnoPendingUpdatesWindows = countif(isnotnull(assessProperties) and assessProperties.osType =~ \\"Windows\\" and (assessProperties.availablePatchCountByClassification.critical==0 and assessProperties.availablePatchCountByClassification.security==0 and assessProperties.availablePatchCountByClassification.updateRollup==0 and assessProperties.availablePatchCountByClassification.featurePack==0 and assessProperties.availablePatchCountByClassification.servicePack==0 and assessProperties.availablePatchCountByClassification.definition==0 and assessProperties.availablePatchCountByClassification.tools==0 and assessProperties.availablePatchCountByClassification.updates==0)),\\r\\nnoPendingUpdatesLinux = countif(isnotnull(assessProperties) and assessProperties.osType =~ \\"Linux\\" and (assessProperties.availablePatchCountByClassification.security==0 and assessProperties.availablePatchCountByClassification.other==0))\\r\\n| project machinePack = pack(\\"No updates available - Linux\\", noPendingUpdatesLinux, \\"No updates available - Windows\\", noPendingUpdatesWindows, \\"Updates available - Linux\\", pendingUpdatesLinux, \\"Updates available - Windows\\", pendingUpdatesWindows, \\"Reboot required\\", pendingReboot, \\"No updates data\\", nodata, \\"Total machines\\", total)\\r\\n| mv-expand machinePack\\r\\n| extend machine = tostring(bag_keys(machinePack)[0])\\r\\n| extend count_ = tolong(machinePack[machine])\\r\\n| project machine, count_ \\r\\n","size":4,"title":"Updates status of machines","queryType":1,"resourceType":"microsoft.resourcegraph/resources","crossComponentResources":["{Subscription}"],"visualization":"tiles","tileSettings":{"titleContent":{"columnMatch":"machine","formatter":1},"leftContent":{"columnMatch":"count_","formatter":12,"formatOptions":{"palette":"auto"},"numberFormat":{"unit":17,"options":{"maximumSignificantDigits":3,"maximumFractionDigits":2}}},"showBorder":false,"sortCriteriaField":"count_","sortOrderField":2,"size":"auto"},"graphSettings":{"type":0,"topContent":{"columnMatch":"machine","formatter":1},"centerContent":{"columnMatch":"count_","formatter":1,"numberFormat":{"unit":17,"options":{"maximumSignificantDigits":3,"maximumFractionDigits":2}}}},"mapSettings":{"locInfo":"LatLong","sizeSettings":"count_","sizeAggregation":"Sum","legendMetric":"count_","legendAggregation":"Sum","itemColorSettings":{"type":"heatmap","colorAggregation":"Sum","nodeColorField":"count_","heatmapPalette":"greenRed"}}},"name":"query - 5"},{"type":3,"content":{"version":"KqlItem/1.0","query":"resources\\r\\n| where type in~ ({ResourceType})\\r\\n| where subscriptionId == \'{Subscription:subscriptionid}\' and resourceGroup == tolower(\'{ResourceGroup}\')\\r\\n| extend joinId = tolower(id)\\r\\n| project joinId\\r\\n| join kind=leftouter\\r\\n(\\r\\npatchassessmentresources\\r\\n| where type in~ (\\"microsoft.compute/virtualmachines/patchassessmentresults\\", \\"microsoft.hybridcompute/machines/patchassessmentresults\\")\\r\\n| extend assessment = properties.availablePatchCountByClassification\\r\\n| where isnotnull(assessment)\\r\\n| parse id with resourceId \\"/patchAssessmentResults\\" *\\r\\n| extend joinId=tolower(resourceId)\\r\\n) on $left.joinId == $right.joinId\\r\\n| summarize\\r\\ntotal = 0,\\r\\nsecurityWindowsUpdates = sumif(toint(assessment.security), (isnotnull(properties) and properties.osType =~ \\"Windows\\" and (assessment.security>0))),\\r\\ncriticalWindowsUpdates = sumif(toint(assessment.critical), (isnotnull(properties) and properties.osType =~ \\"Windows\\" and (assessment.critical>0))),\\r\\nsecurityLinuxUpdates = sumif(toint(assessment.security), (isnotnull(properties) and properties.osType =~ \\"Linux\\" and (assessment.security>0))),\\r\\notherLinuxUpdates = sumif(toint(assessment.other), (isnotnull(properties) and properties.osType =~ \\"Linux\\" and (assessment.other>0))),\\r\\notherWindowsUpdates = sumif(toint(assessment.updateRollup) + toint(assessment.featurePack) + toint(assessment.servicePack) + toint(assessment.definition) +\\r\\ntoint(assessment.tools) + toint(assessment.updates), isnotnull(properties) and properties.osType =~ \\"Windows\\" and\\r\\n(assessment.updateRollup>0 or assessment.featurePack>0 or assessment.servicePack>0 or assessment.definition>0 or assessment.tools>0 or assessment.updates>0))","size":0,"title":"Pending Windows and Linux updates by classification ","queryType":1,"resourceType":"microsoft.resourcegraph/resources","crossComponentResources":["{Subscription}"],"visualization":"unstackedbar","tileSettings":{"showBorder":false},"chartSettings":{"seriesLabelSettings":[{"seriesName":"criticalWindowsUpdates","label":"Critical updates - Windows","color":"orange"},{"seriesName":"securityLinuxUpdates","label":"Security updates - Linux","color":"redBright"},{"seriesName":"otherLinuxUpdates","label":"Other updates - Linux","color":"turquoise"},{"seriesName":"otherWindowsUpdates","label":"Other updates - Windows","color":"gray"},{"seriesName":"securityWindowsUpdates","label":"Security updates - Windows","color":"redBright"}]}},"customWidth":"50","name":"query - 4","styleSettings":{"maxWidth":"50%"}},{"type":3,"content":{"version":"KqlItem/1.0","query":"resources\\r\\n| where type in~ ({ResourceType})\\r\\n| where subscriptionId == \'{Subscription:subscriptionid}\' and resourceGroup == tolower(\'{ResourceGroup}\')\\r\\n| extend joinId = tolower(id)\\r\\n| project joinId\\r\\n| join kind=leftouter\\r\\n(\\r\\npatchassessmentresources\\r\\n| where type in~ (\\"microsoft.compute/virtualmachines/patchassessmentresults\\", \\"microsoft.hybridcompute/machines/patchassessmentresults\\")\\r\\n| extend assessment = properties.availablePatchCountByClassification\\r\\n| where isnotnull(assessment)\\r\\n| parse id with resourceId \\"/patchAssessmentResults\\" *\\r\\n| extend joinId=tolower(resourceId)\\r\\n) on $left.joinId == $right.joinId\\r\\n| summarize\\r\\ntotal = 0,\\r\\nsecurityWindowsMachines = countif(isnotnull(properties) and properties.osType =~ \\"Windows\\" and (assessment.security>0)),\\r\\ncriticalWindowsMachines = countif(isnotnull(properties) and properties.osType =~ \\"Windows\\" and (assessment.critical>0)),\\r\\notherWindowsMachines = countif(isnotnull(properties) and properties.osType =~ \\"Windows\\" and (assessment.updateRollup>0 or assessment.featurePack>0 or assessment.servicePack>0 or assessment.definition>0 or assessment.tools>0 or assessment.updates>0)),\\r\\nsecurityLinuxMachines = countif(isnotnull(properties) and properties.osType =~ \\"Linux\\" and (assessment.security>0)),\\r\\notherLinuxMachines = countif(isnotnull(properties) and properties.osType =~ \\"Linux\\" and (assessment.other>0))\\r\\n","size":0,"title":"Machines with Pending Updates by classification","queryType":1,"resourceType":"microsoft.resourcegraph/resources","crossComponentResources":["{Subscription}"],"visualization":"unstackedbar","chartSettings":{"seriesLabelSettings":[{"seriesName":"criticalWindowsMachines","label":"Critical - Windows","color":"orange"},{"seriesName":"otherWindowsMachines","label":"Other - Windows","color":"turquoise"},{"seriesName":"securityLinuxMachines","label":"Security - Linux","color":"redBright"},{"seriesName":"otherLinuxMachines","label":"Other - Linux","color":"turquoise"},{"seriesName":"securityWindowsMachines","label":"Security - Windows","color":"redBright"}]}},"customWidth":"50","name":"query - 3","styleSettings":{"maxWidth":"50%"}},{"type":3,"content":{"version":"KqlItem/1.0","query":"resources\\r\\n| where type in~ ({ResourceType})\\r\\n| where subscriptionId == \'{Subscription:subscriptionid}\' and resourceGroup == tolower(\'{ResourceGroup}\')\\r\\n| extend joinId = tolower(id)\\r\\n| project joinId\\r\\n| join kind=inner \\r\\n(\\r\\npatchassessmentresources\\r\\n| where type in~ (\\"microsoft.compute/virtualmachines/patchassessmentresults/softwarepatches\\", \\"microsoft.hybridcompute/machines/patchassessmentresults/softwarepatches\\")\\r\\n| extend id = tolower(id)\\r\\n| parse id with resourceId \\"/patchassessmentresults\\" *\\r\\n| extend joinId=tolower(resourceId)\\r\\n| where isnotnull(properties.kbId)\\r\\n| extend MissingUpdate = tostring(properties.patchName)\\r\\n| extend Classification = tostring(properties.classifications[0])\\r\\n| project joinId, MissingUpdate, Classification\\r\\n) \\r\\non $left.joinId == $right.joinId\\r\\n| summarize Machines = count() by MissingUpdate, Classification\\r\\n| order by Machines desc\\r\\n| take 10\\r\\n","size":0,"title":"Top 10 Pending Windows Updates (by machine count)","queryType":1,"resourceType":"microsoft.resourcegraph/resources","crossComponentResources":["{Subscription}"],"visualization":"table","gridSettings":{"sortBy":[{"itemKey":"Classification","sortOrder":2}],"labelSettings":[{"columnId":"MissingUpdate","label":"Missing update"}]},"sortBy":[{"itemKey":"Classification","sortOrder":2}],"tileSettings":{"showBorder":false,"titleContent":{"columnMatch":"properties_patchName","formatter":1},"leftContent":{"columnMatch":"count_","formatter":12,"formatOptions":{"palette":"auto"},"numberFormat":{"unit":17,"options":{"maximumSignificantDigits":3,"maximumFractionDigits":2}}}},"graphSettings":{"type":0,"topContent":{"columnMatch":"properties_patchName","formatter":1},"centerContent":{"columnMatch":"count_","formatter":1,"numberFormat":{"unit":17,"options":{"maximumSignificantDigits":3,"maximumFractionDigits":2}}},"nodeIdField":"properties_patchName","sourceIdField":"properties_patchName","targetIdField":"count_","graphOrientation":3,"showOrientationToggles":false,"nodeSize":null,"staticNodeSize":100,"colorSettings":null,"hivesMargin":5},"mapSettings":{"locInfo":"LatLong","sizeSettings":"count_","sizeAggregation":"Sum","legendMetric":"count_","legendAggregation":"Sum","itemColorSettings":{"type":"heatmap","colorAggregation":"Sum","nodeColorField":"count_","heatmapPalette":"greenRed"}}},"customWidth":"50","name":"query - 9","styleSettings":{"maxWidth":"50%"}},{"type":3,"content":{"version":"KqlItem/1.0","query":"resources\\r\\n| where type in~ ({ResourceType})\\r\\n| where subscriptionId == \'{Subscription:subscriptionid}\' and resourceGroup == tolower(\'{ResourceGroup}\')\\r\\n| extend joinId = tolower(id)\\r\\n| project joinId\\r\\n| join kind=inner \\r\\n(\\r\\npatchassessmentresources\\r\\n| where type in~ (\\"microsoft.compute/virtualmachines/patchassessmentresults/softwarepatches\\", \\"microsoft.hybridcompute/machines/patchassessmentresults/softwarepatches\\")\\r\\n| extend id = tolower(id)\\r\\n| parse id with resourceId \\"/patchassessmentresults\\" *\\r\\n| extend joinId=tolower(resourceId)\\r\\n| where isnull(properties.kbId)\\r\\n| extend MissingUpdate = tostring(properties.patchName)\\r\\n| extend Classification = tostring(properties.classifications[0])\\r\\n| project joinId, MissingUpdate, Classification\\r\\n) \\r\\non $left.joinId == $right.joinId\\r\\n| summarize Machines = count() by MissingUpdate, Classification\\r\\n| order by Machines desc\\r\\n| take 10","size":0,"title":"Top 10 Pending Linux Updates (by machine count)","queryType":1,"resourceType":"microsoft.resourcegraph/resources","crossComponentResources":["{Subscription}"],"visualization":"table","gridSettings":{"sortBy":[{"itemKey":"Machines","sortOrder":2}],"labelSettings":[{"columnId":"MissingUpdate","label":"Missing update"}]},"sortBy":[{"itemKey":"Machines","sortOrder":2}]},"customWidth":"50","name":"query - 10","styleSettings":{"maxWidth":"50%"}}]},"name":"updatesGroup","styleSettings":{"showBorder":true}},{"type":3,"content":{"version":"KqlItem/1.0","query":"securityresources\\r\\n| where type == \\"microsoft.security/locations/alerts\\"\\r\\n| where subscriptionId == \'{Subscription:subscriptionid}\' and resourceGroup == tolower(\'{ResourceGroup}\')\\r\\n| project-rename P= properties\\r\\n| extend Details = parse_json(P)\\r\\n| extend IsIncident = Details.[\\"IsIncident\\"]\\r\\n| extend AlertDisplayName = Details.[\\"AlertDisplayName\\"]\\r\\n| extend SystemAlertId = Details.[\\"SystemAlertId\\"]\\r\\n| extend Severity = tostring(Details.[\\"Severity\\"])\\r\\n| where Severity == \\"High\\"\\r\\n| extend AlertUri = Details.[\\"AlertUri\\"]\\r\\n| extend Status = tostring(Details.[\\"Status\\"])\\r\\n| extend Tactics = tostring(Details.[\\"Intent\\"])\\r\\n| extend ResourceIdentifiers = Details.[\\"ResourceIdentifiers\\"]\\r\\n| mv-expand ResourceIdentifiers\\r\\n| extend ResourceId = parse_json(ResourceIdentifiers).[\\"AzureResourceId\\"]\\r\\n| where Status == \\"Active\\"\\r\\n| extend SeverityRank = case(\\r\\n Severity == \'High\', 3,\\r\\n Severity == \'Medium\', 2,\\r\\n Severity == \'Low\', 1,\\r\\n 0\\r\\n )\\r\\n| parse AlertUri with * \'/subscriptionId/\' SubscriptionId \'/\' *\\r\\n| parse AlertUri with * \'/resourceGroup/\' ResourceGroup \'/\' *\\r\\n| parse AlertUri with * \'/location/\' Location \\r\\n| project\\r\\n Severity,\\r\\n SystemAlertId,\\r\\n AlertDisplayName,\\r\\n IsIncident = iif(IsIncident == \\"true\\", \\"Incident\\", \\"Alert\\"),\\r\\n AlertUri,\\r\\n Tactics,\\r\\n SeverityRank,\\r\\n SubscriptionId,\\r\\n ResourceGroup,\\r\\n Location,\\r\\n ResourceId\\r\\n| sort by SeverityRank","size":0,"title":"Defender for Cloud {$rowCount} Active Alerts ","noDataMessage":"No active alerts","noDataMessageStyle":3,"exportedParameters":[{"fieldName":"ResourceId","parameterName":"Resource","parameterType":1},{"fieldName":"AlertUri","parameterName":"AlertUri","parameterType":1},{"fieldName":"SystemAlertId","parameterName":"SystemAlertId","parameterType":1},{"fieldName":"SubscriptionId","parameterName":"SubscriptionId","parameterType":1},{"fieldName":"ResourceGroup","parameterName":"ResourceGroup","parameterType":1},{"fieldName":"Location","parameterName":"Location","parameterType":1}],"queryType":1,"resourceType":"microsoft.resourcegraph/resources","crossComponentResources":["{Subscription}"],"gridSettings":{"formatters":[{"columnMatch":"Severity","formatter":18,"formatOptions":{"thresholdsOptions":"colors","thresholdsGrid":[{"operator":"contains","thresholdValue":"High","representation":"redBright","text":"{0}{1}"},{"operator":"contains","thresholdValue":"Medium","representation":"orange","text":"{0}{1}"},{"operator":"contains","thresholdValue":"Low","representation":"yellow","text":"{0}{1}"},{"operator":"contains","thresholdValue":"Informational ","representation":"gray","text":"{0}{1}"},{"operator":"Default","thresholdValue":null,"representation":null,"text":"{0}{1}"}]}},{"columnMatch":"SystemAlertId","formatter":5},{"columnMatch":"AlertDisplayName","formatter":1,"formatOptions":{"linkTarget":"OpenBlade","bladeOpenContext":{"bladeName":"AlertBlade","extensionName":"Microsoft_Azure_Security","bladeParameters":[{"name":"alertId","source":"column","value":"SystemAlertId"},{"name":"subscriptionId","source":"column","value":"SubscriptionId"},{"name":"resourceGroup","source":"column","value":"ResourceGroup"},{"name":"referencedFrom","source":"static","value":"activeAlertsWorkbook"},{"name":"location","source":"column","value":"Location"}]}}},{"columnMatch":"IsIncident","formatter":1},{"columnMatch":"AlertUri","formatter":5},{"columnMatch":"Tactics","formatter":1},{"columnMatch":"SubscriptionId","formatter":15,"formatOptions":{"linkTarget":"Resource","showIcon":true}},{"columnMatch":"Location","formatter":17},{"columnMatch":"ResourceId","formatter":13,"formatOptions":{"linkTarget":"Resource","showIcon":true}},{"columnMatch":"TenantId","formatter":5},{"columnMatch":"AlertName","formatter":5},{"columnMatch":"Description","formatter":5},{"columnMatch":"ProviderName","formatter":5},{"columnMatch":"VendorName","formatter":5},{"columnMatch":"VendorOriginalId","formatter":5},{"columnMatch":"SourceComputerId","formatter":5},{"columnMatch":"AlertType","formatter":5},{"columnMatch":"ConfidenceLevel","formatter":5},{"columnMatch":"ConfidenceScore","formatter":5},{"columnMatch":"StartTime","formatter":5},{"columnMatch":"EndTime","formatter":5},{"columnMatch":"ProcessingEndTime","formatter":5},{"columnMatch":"RemediationSteps","formatter":5},{"columnMatch":"ExtendedProperties","formatter":5},{"columnMatch":"Entities","formatter":5},{"columnMatch":"SourceSystem","formatter":5},{"columnMatch":"WorkspaceSubscriptionId","formatter":5},{"columnMatch":"WorkspaceResourceGroup","formatter":5},{"columnMatch":"ExtendedLinks","formatter":5},{"columnMatch":"ProductName","formatter":5},{"columnMatch":"ProductComponentName","formatter":5},{"columnMatch":"AlertLink","formatter":7,"formatOptions":{"linkTarget":"Url"}},{"columnMatch":"SystemIncidentId","formatter":5},{"columnMatch":"SystemAlertId1","formatter":5}],"labelSettings":[{"columnId":"SystemAlertId","label":"Alert ID"},{"columnId":"AlertDisplayName","label":"Alert name"},{"columnId":"IsIncident","label":"Incident/alert"},{"columnId":"SeverityRank","label":"Severity"},{"columnId":"SubscriptionId","label":"Subscription"},{"columnId":"ResourceGroup","label":"Resource group"},{"columnId":"ResourceId","label":"Resource"}]},"sortBy":[]},"showPin":true,"name":"SecurityIncidents - FilterbyResourceId","styleSettings":{"showBorder":true}}],"isLocked":false,"fallbackResourceIds":["azure monitor"]}' + version: '1.0' + sourceId: workbookSourceId + category: workbookType + } + dependsOn: [] +} + +output workbookId string = workbookId_resource.id diff --git a/azure_jumpstart_ag/artifacts/monitoring/arc-osperformance-workbook.bicep b/azure_jumpstart_ag/artifacts/monitoring/arc-osperformance-workbook.bicep new file mode 100644 index 0000000000..cb91ea5128 --- /dev/null +++ b/azure_jumpstart_ag/artifacts/monitoring/arc-osperformance-workbook.bicep @@ -0,0 +1,1225 @@ +@description('The friendly name for the workbook that is used in the Gallery or Saved List. This name must be unique within a resource group.') +param workbookDisplayName string = 'Azure Arc-enabled servers OS Performance' +@description('The gallery that the workbook will been shown under. Supported values include workbook, tsg, etc. Usually, this is \'workbook\'') +param workbookType string = 'workbook' +@description('The id of resource instance to which the workbook will be associated') +param workbookSourceId string = 'azure monitor' +@description('The unique guid for this workbook instance') +param workbookId string = guid('OSPerformance') +@description('The location to deploy the workbook to') +param location string = resourceGroup().location +@description('Workbook content') +var workbookContent = { + version: 'Notebook/1.0' + items: [ + { + type: 1 + content: { + json: '# Operating System - Performance and capacity' + } + name: 'text - 0' + } + { + type: 9 + content: { + version: 'KqlParameterItem/1.0' + crossComponentResources: [ + '{Workspace}' + ] + parameters: [ + { + id: 'b82b64ff-f991-4f44-ac88-aee7c086cc48' + version: 'KqlParameterItem/1.0' + name: 'TimeRange' + type: 4 + isRequired: true + value: { + durationMs: 86400000 + } + typeSettings: { + selectableValues: [ + { + durationMs: 3600000 + } + { + durationMs: 43200000 + } + { + durationMs: 86400000 + } + { + durationMs: 259200000 + } + { + durationMs: 604800000 + } + { + durationMs: 1209600000 + } + { + durationMs: 2592000000 + } + ] + allowCustom: true + } + } + { + id: '23e3bd37-240d-492a-99c1-5b4f3d79d75e' + version: 'KqlParameterItem/1.0' + name: 'Subscription' + type: 6 + isRequired: true + multiSelect: true + quote: '\'' + delimiter: ',' + value: [ + '/subscriptions/00000000-0000-0000-0000-000000000000' + ] + query: 'where type =~ \'microsoft.compute/virtualmachines\' or type =~ \'microsoft.hybridcompute/machines\' \r\n| summarize Count = count() by subscriptionId\r\n\t| order by Count desc\r\n\t| extend Rank = row_number()\r\n\t| project value = subscriptionId, label = subscriptionId, selected = Rank == 1' + crossComponentResources: [ + 'value::all' + ] + typeSettings: { + limitSelectTo: 100 + additionalResourceOptions: [ + 'value::all' + ] + showDefault: false + } + queryType: 1 + resourceType: 'microsoft.resourcegraph/resources' + } + { + id: 'e1ecac91-1691-4f48-b4c0-803e39e00f43' + version: 'KqlParameterItem/1.0' + name: 'Workspace' + type: 5 + isRequired: true + multiSelect: true + quote: '\'' + delimiter: ',' + query: 'where type =~ \'microsoft.operationalinsights/workspaces\'\r\n| summarize by id, name\r\n' + crossComponentResources: [ + '{Subscription}' + ] + typeSettings: { + additionalResourceOptions: [] + showDefault: false + } + timeContext: { + durationMs: 0 + } + timeContextFromParameter: 'TimeRange' + queryType: 1 + resourceType: 'microsoft.resourcegraph/resources' + value: [ + '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/xxxx/providers/Microsoft.OperationalInsights/workspaces/xxxx' + ] + } + { + id: '98c624e3-84f5-43e2-8be3-56b84c75bbb2' + version: 'KqlParameterItem/1.0' + name: 'ResourceGroup' + type: 2 + isRequired: true + multiSelect: true + quote: '\'' + delimiter: ',' + query: 'Heartbeat\r\n| distinct RGName = tolower(ResourceGroup)' + crossComponentResources: [ + '{Workspace}' + ] + typeSettings: { + limitSelectTo: 500 + additionalResourceOptions: [ + 'value::all' + ] + showDefault: false + } + timeContext: { + durationMs: 0 + } + timeContextFromParameter: 'TimeRange' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + value: [ + 'value::all' + ] + } + ] + style: 'pills' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + } + name: 'parameters - 1' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'let trend = (Heartbeat\r\n | extend RGName = tolower(split(_ResourceId, "/")[4])\r\n | where RGName in ({ResourceGroup})\r\n | make-series InternalTrend=iff(count() > 0, 1, 0) default = 0 on TimeGenerated from ago(3d) to now() step 15m by _ResourceId\r\n | extend Trend=array_slice(InternalTrend, array_length(InternalTrend) - 30, array_length(InternalTrend) - 1)); \r\nlet PerfCPU = (InsightsMetrics\r\n | where Origin == "vm.azm.ms"\r\n | extend RGName = tolower(split(_ResourceId, "/")[4])\r\n | where RGName in ({ResourceGroup})\r\n | where Namespace == "Processor" and Name == "UtilizationPercentage"\r\n | summarize AvgCPU=round(avg(Val), 2), MaxCPU=round(max(Val), 2) by _ResourceId\r\n | extend StatusCPU = case (\r\n AvgCPU > 80,\r\n 2,\r\n AvgCPU > 50,\r\n 1,\r\n AvgCPU <= 50,\r\n 0,\r\n -1\r\n )\r\n );\r\nlet PerfMemory = (InsightsMetrics\r\n | where Origin == "vm.azm.ms"\r\n | extend RGName = tolower(split(_ResourceId, "/")[4])\r\n | where RGName in ({ResourceGroup})\r\n | where Namespace == "Memory" and Name == "AvailableMB"\r\n | summarize AvgMEM=round(avg(Val), 2), MaxMEM=round(max(Val), 2) by _ResourceId\r\n | extend StatusMEM = case (\r\n AvgMEM > 4,\r\n 0,\r\n AvgMEM >= 1,\r\n 1,\r\n AvgMEM < 1,\r\n 2,\r\n -1\r\n )\r\n );\r\nlet PerfDisk = (InsightsMetrics\r\n | where Origin == "vm.azm.ms"\r\n | extend RGName = tolower(split(_ResourceId, "/")[4])\r\n | where RGName in ({ResourceGroup})\r\n | where Namespace == "LogicalDisk" and Name == "FreeSpaceMB"\r\n | extend Disk=tostring(todynamic(Tags)["vm.azm.ms/mountId"])\r\n | where (Disk =~ "C:" or Disk == "/")\r\n | summarize\r\n AvgDisk=round(avg(Val), 2),\r\n (TimeGenerated, LastDisk)=arg_max(TimeGenerated, round(Val, 2))\r\n by _ResourceId\r\n | extend StatusDisk = case (\r\n AvgDisk < 5000,\r\n 2,\r\n AvgDisk < 30000,\r\n 1,\r\n AvgDisk >= 30000,\r\n 0,\r\n -1\r\n )\r\n | project _ResourceId, AvgDisk, LastDisk, StatusDisk\r\n );\r\nPerfCPU\r\n| join (PerfMemory) on _ResourceId\r\n| join (PerfDisk) on _ResourceId\r\n| join (trend) on _ResourceId\r\n| project\r\n _ResourceId,\r\n StatusCPU,\r\n AvgCPU,\r\n MaxCPU,\r\n StatusMEM,\r\n AvgMEM,\r\n MaxMEM,\r\n StatusDisk,\r\n AvgDisk,\r\n LastDisk,\r\n ["Heartbeat Trend"] = Trend\r\n| sort by StatusCPU,StatusDisk desc' + size: 0 + showAnalytics: true + title: 'Top servers (data aggregated based on TimeRange)' + timeContextFromParameter: 'TimeRange' + exportFieldName: '_ResourceId' + exportParameterName: '_ResourceId' + exportDefaultValue: 'All' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + crossComponentResources: [ + '{Workspace}' + ] + gridSettings: { + formatters: [ + { + columnMatch: 'StatusCPU' + formatter: 18 + formatOptions: { + thresholdsOptions: 'icons' + thresholdsGrid: [ + { + operator: '==' + thresholdValue: '0' + representation: 'success' + text: '{1}' + } + { + operator: '==' + thresholdValue: '1' + representation: '2' + text: '{1}' + } + { + operator: '==' + thresholdValue: '2' + representation: '4' + text: '{1}' + } + { + operator: 'Default' + thresholdValue: null + representation: 'Unknown' + text: '{1}' + } + ] + } + } + { + columnMatch: 'AvgCPU' + formatter: 0 + numberFormat: { + unit: 1 + options: { + style: 'decimal' + } + } + } + { + columnMatch: 'MaxCPU' + formatter: 0 + numberFormat: { + unit: 1 + options: { + style: 'decimal' + } + } + } + { + columnMatch: 'StatusMEM' + formatter: 18 + formatOptions: { + thresholdsOptions: 'icons' + thresholdsGrid: [ + { + operator: '==' + thresholdValue: '0' + representation: 'success' + text: '{1}' + } + { + operator: '==' + thresholdValue: '1' + representation: '2' + text: '{1}' + } + { + operator: '==' + thresholdValue: '2' + representation: 'critical' + text: '{1}' + } + { + operator: 'Default' + thresholdValue: null + representation: 'unknown' + text: '{1}' + } + ] + } + } + { + columnMatch: 'AvgMEM' + formatter: 0 + numberFormat: { + unit: 38 + options: { + style: 'decimal' + maximumFractionDigits: 2 + } + } + } + { + columnMatch: 'MaxMEM' + formatter: 0 + numberFormat: { + unit: 38 + options: { + style: 'decimal' + maximumFractionDigits: 2 + } + } + } + { + columnMatch: 'StatusDisk' + formatter: 18 + formatOptions: { + thresholdsOptions: 'icons' + thresholdsGrid: [ + { + operator: '==' + thresholdValue: '0' + representation: 'success' + text: '{1}' + } + { + operator: '==' + thresholdValue: '1' + representation: '2' + text: '{1}' + } + { + operator: '==' + thresholdValue: '2' + representation: '4' + text: '{1}' + } + { + operator: 'Default' + thresholdValue: null + representation: 'success' + text: '{1}' + } + ] + } + } + { + columnMatch: 'AvgDisk' + formatter: 0 + numberFormat: { + unit: 38 + options: { + style: 'decimal' + maximumFractionDigits: 2 + } + } + } + { + columnMatch: 'LastDisk' + formatter: 0 + numberFormat: { + unit: 4 + options: { + style: 'decimal' + maximumFractionDigits: 2 + } + } + } + { + columnMatch: 'Trend' + formatter: 10 + formatOptions: { + palette: 'blue' + } + } + { + columnMatch: 'Max' + formatter: 0 + numberFormat: { + unit: 0 + options: { + style: 'decimal' + } + } + } + { + columnMatch: 'Average' + formatter: 8 + formatOptions: { + palette: 'yellowOrangeRed' + } + numberFormat: { + unit: 0 + options: { + style: 'decimal' + useGrouping: false + } + } + } + { + columnMatch: 'Min' + formatter: 8 + formatOptions: { + palette: 'yellowOrangeRed' + aggregation: 'Min' + } + numberFormat: { + unit: 0 + options: { + style: 'decimal' + } + } + } + ] + filter: true + labelSettings: [ + { + columnId: '_ResourceId' + label: 'Computer' + } + ] + } + sortBy: [] + } + showPin: true + name: 'query - 2' + styleSettings: { + showBorder: true + } + } + { + type: 1 + content: { + json: '# Top Performance' + } + name: 'text - 8' + } + { + type: 1 + content: { + json: '## Processor(_Total)\\% Processor Time' + } + name: 'text - 10' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'let TopComputers = InsightsMetrics \r\n| where Origin == "vm.azm.ms"\r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "Processor" and Name == "UtilizationPercentage"\r\n| summarize AvgCPU = avg(Val) by Computer \r\n| top 10 by AvgCPU desc\r\n| project Computer; \r\nInsightsMetrics \r\n| where Origin == "vm.azm.ms"\r\n| where Computer in (TopComputers) \r\n| where Namespace == "Processor" and Name == "UtilizationPercentage"\r\n| summarize Used_CPU = round(avg(Val),1) by Computer, bin(TimeGenerated, ({TimeRange:end} - {TimeRange:start})/100)\r\n| render timechart' + size: 0 + aggregation: 3 + showAnalytics: true + title: '% Processor Time - Top 10 Computers' + timeContextFromParameter: 'TimeRange' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + crossComponentResources: [ + '{Workspace}' + ] + chartSettings: { + showLegend: true + } + } + customWidth: '50' + name: 'query - 4' + styleSettings: { + showBorder: true + } + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'let trend = \r\nInsightsMetrics\r\n| where Origin == "vm.azm.ms" \r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where RGName in ({ResourceGroup})\r\n| where Namespace == "Processor" and Name == "UtilizationPercentage"\r\n| make-series Average = round(avg(Val), 3) default = 0 on TimeGenerated from {TimeRange:start} to {TimeRange:end} step totimespan(\'00:30:00\') by _ResourceId \r\n| project _ResourceId, [\'Trend\'] = Average; \r\n\r\nInsightsMetrics\r\n| where Origin == "vm.azm.ms"\r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "Processor" and Name == "UtilizationPercentage"\r\n| summarize Average=round(avg(Val),3) by _ResourceId\r\n| join (trend) on _ResourceId\r\n| extend Status = case (\r\n Average > 80, "Critical",\r\n Average > 50, "Warning",\r\n Average <= 50, "Healthy", "Unknown"\r\n)\r\n\r\n| project Status, _ResourceId, Average, Trend\r\n| sort by Status desc' + size: 0 + showAnalytics: true + title: 'Thresholds (Warning=50; Critical=80) - All Computers' + timeContextFromParameter: 'TimeRange' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + crossComponentResources: [ + '{Workspace}' + ] + gridSettings: { + formatters: [ + { + columnMatch: 'Status' + formatter: 18 + formatOptions: { + thresholdsOptions: 'icons' + thresholdsGrid: [ + { + operator: '==' + thresholdValue: 'Healthy' + representation: 'success' + text: '{1}' + } + { + operator: '==' + thresholdValue: 'Warning' + representation: '2' + text: '{1}' + } + { + operator: '==' + thresholdValue: 'Critical' + representation: 'critical' + text: '{1}' + } + { + operator: 'Default' + thresholdValue: null + representation: 'unknown' + text: '{1}' + } + ] + } + } + { + columnMatch: 'Average' + formatter: 8 + formatOptions: { + min: 0 + max: 100 + palette: 'greenRed' + } + } + { + columnMatch: 'Trend' + formatter: 21 + formatOptions: { + palette: 'green' + } + } + ] + filter: true + sortBy: [ + { + itemKey: '$gen_link__ResourceId_1' + sortOrder: 2 + } + ] + labelSettings: [ + { + columnId: 'Status' + label: 'Status' + } + { + columnId: '_ResourceId' + label: 'Computer' + } + ] + } + sortBy: [ + { + itemKey: '$gen_link__ResourceId_1' + sortOrder: 2 + } + ] + } + customWidth: '50' + name: 'query - 9' + styleSettings: { + showBorder: true + } + } + { + type: 1 + content: { + json: '## Memory: _Available MBytes_ and _% Committed Bytes in Use_' + } + name: 'text - 11' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'let TopComputers = InsightsMetrics \r\n| where Origin == "vm.azm.ms"\r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "Memory" and Name == "AvailableMB"\r\n| summarize AvailableGBytes = round(avg(Val)/1024,2) by Computer\r\n| top 10 by AvailableGBytes asc\r\n| project Computer; \r\nInsightsMetrics \r\n| where Origin == "vm.azm.ms"\r\n| where Computer in (TopComputers) \r\n| where Namespace == "Memory" and Name == "AvailableMB"\r\n| summarize AvailableGBytes = round(avg(Val)/1024,2) by Computer, bin(TimeGenerated, ({TimeRange:end} - {TimeRange:start})/100)\r\n| render timechart' + size: 0 + aggregation: 3 + showAnalytics: true + title: 'Available MBytes - Top 10 Computers' + timeContextFromParameter: 'TimeRange' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + crossComponentResources: [ + '{Workspace}' + ] + visualization: 'timechart' + gridSettings: { + formatters: [ + { + columnMatch: 'AvailableMBytes' + formatter: 0 + formatOptions: { + showIcon: true + } + numberFormat: { + unit: 4 + options: { + style: 'decimal' + useGrouping: false + } + } + } + ] + } + chartSettings: { + createOtherGroup: 0 + showLegend: true + ySettings: { + unit: 5 + min: null + max: null + } + } + } + customWidth: '50' + name: 'query - 5' + styleSettings: { + showBorder: true + } + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'let trend = \r\nInsightsMetrics\r\n| where Origin == "vm.azm.ms" \r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "Memory" and Name == "AvailableMB"\r\n| make-series Average = round(avg(Val), 3) default = 0 on TimeGenerated from {TimeRange:start} to {TimeRange:end} step totimespan(\'00:30:00\') by _ResourceId \r\n| project _ResourceId, [\'Trend\'] = Average; \r\n\r\nInsightsMetrics\r\n| where Origin == "vm.azm.ms"\r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "Memory" and Name == "AvailableMB"\r\n| summarize ["Available GBytes"]=round(avg(Val)/1024,2) by _ResourceId\r\n| join (trend) on _ResourceId\r\n| extend Status = case (\r\n ["Available GBytes"] > 4, "Healthy",\r\n ["Available GBytes"] >= 1, "Warning",\r\n ["Available GBytes"] < 1, "Critical", "Unknown"\r\n)\r\n| project Status, _ResourceId, ["Available GBytes"], Trend\r\n| sort by ["Available GBytes"] asc' + size: 0 + showAnalytics: true + title: 'Thresholds (Warning < 4 GB; Critical < 1 GB) - All Computers' + timeContextFromParameter: 'TimeRange' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + crossComponentResources: [ + '{Workspace}' + ] + gridSettings: { + formatters: [ + { + columnMatch: 'Status' + formatter: 18 + formatOptions: { + thresholdsOptions: 'icons' + thresholdsGrid: [ + { + operator: '==' + thresholdValue: 'Healthy' + representation: 'success' + text: '{1}' + } + { + operator: '==' + thresholdValue: 'Warning' + representation: '2' + text: '{1}' + } + { + operator: '==' + thresholdValue: 'Critical' + representation: 'critical' + text: '{1}' + } + { + operator: 'Default' + thresholdValue: null + representation: 'Unknown' + text: '{1}' + } + ] + } + } + { + columnMatch: 'Available GBytes' + formatter: 8 + formatOptions: { + min: 0 + max: 20 + palette: 'redGreen' + } + } + { + columnMatch: 'Trend' + formatter: 21 + formatOptions: { + palette: 'green' + } + } + ] + filter: true + sortBy: [ + { + itemKey: '$gen_link__ResourceId_1' + sortOrder: 2 + } + ] + labelSettings: [ + { + columnId: 'Status' + label: 'Status' + } + { + columnId: '_ResourceId' + label: 'Computer' + } + ] + } + sortBy: [ + { + itemKey: '$gen_link__ResourceId_1' + sortOrder: 2 + } + ] + } + customWidth: '50' + name: 'query - 9 - Copy' + styleSettings: { + showBorder: true + } + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'let TopComputers = InsightsMetrics \r\n| where Origin == "vm.azm.ms"\r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "Memory" and Name == "AvailableMB"\r\n| extend TotalMemory = toreal(todynamic(Tags)["vm.azm.ms/memorySizeMB"]) \r\n| extend CommittedMemoryPercentage = 100-((toreal(Val) / TotalMemory) * 100.0)\r\n| summarize PctCommittedBytes = round(avg(CommittedMemoryPercentage),2) by Computer\r\n| top 10 by PctCommittedBytes desc\r\n| project Computer; \r\nInsightsMetrics \r\n| where Origin == "vm.azm.ms"\r\n| where Computer in (TopComputers) \r\n| where Namespace == "Memory" and Name == "AvailableMB"\r\n| extend TotalMemory = toreal(todynamic(Tags)["vm.azm.ms/memorySizeMB"]) \r\n| extend CommittedMemoryPercentage = 100-((toreal(Val) / TotalMemory) * 100.0)\r\n| summarize PctCommittedBytes = round(avg(CommittedMemoryPercentage),2) by Computer, bin(TimeGenerated, ({TimeRange:end} - {TimeRange:start})/100)\r\n| render timechart' + size: 0 + aggregation: 3 + showAnalytics: true + title: '% Committed Bytes In Use - Top 10 Computers' + timeContextFromParameter: 'TimeRange' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + crossComponentResources: [ + '{Workspace}' + ] + chartSettings: { + group: 'Computer' + createOtherGroup: 0 + showLegend: true + ySettings: { + unit: 1 + min: 0 + max: 100 + } + } + } + customWidth: '50' + name: 'query - 9' + styleSettings: { + showBorder: true + } + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'let trend = \r\nInsightsMetrics \r\n| where Origin == "vm.azm.ms" \r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "Memory" and Name == "AvailableMB"\r\n| extend TotalMemory = toreal(todynamic(Tags)["vm.azm.ms/memorySizeMB"]) \r\n| extend CommittedMemoryPercentage = 100-((toreal(Val) / TotalMemory) * 100.0)\r\n| make-series Average = round(avg(CommittedMemoryPercentage), 3) default = 0 on TimeGenerated from {TimeRange:start} to {TimeRange:end} step totimespan(\'00:30:00\') by _ResourceId \r\n| project _ResourceId, [\'Trend\'] = Average; \r\n\r\nInsightsMetrics\r\n| where Origin == "vm.azm.ms"\r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "Memory" and Name == "AvailableMB"\r\n| extend TotalMemory = toreal(todynamic(Tags)["vm.azm.ms/memorySizeMB"]) \r\n| extend CommittedMemoryPercentage = 100-((toreal(Val) / TotalMemory) * 100.0)\r\n| summarize Average=round(avg(CommittedMemoryPercentage),3) by _ResourceId\r\n| join (trend) on _ResourceId\r\n| extend Status = case (\r\n Average > 90, "Critical",\r\n Average > 60, "Warning",\r\n Average <= 60, "Healthy", "Unknown"\r\n)\r\n\r\n| project Status, _ResourceId, Average, Trend\r\n| sort by Average ' + size: 0 + showAnalytics: true + title: 'Thresholds (Warning>60; Critical>90) - All Computers' + timeContextFromParameter: 'TimeRange' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + crossComponentResources: [ + '{Workspace}' + ] + gridSettings: { + formatters: [ + { + columnMatch: 'Status' + formatter: 18 + formatOptions: { + thresholdsOptions: 'icons' + thresholdsGrid: [ + { + operator: '==' + thresholdValue: 'Healthy' + representation: 'success' + text: '{1}' + } + { + operator: '==' + thresholdValue: 'Warning' + representation: '2' + text: '{1}' + } + { + operator: '==' + thresholdValue: 'Critical' + representation: 'critical' + text: '{1}' + } + { + operator: 'Default' + thresholdValue: null + representation: 'unknown' + text: '{1}' + } + ] + } + } + { + columnMatch: 'Average' + formatter: 8 + formatOptions: { + min: 0 + max: 100 + palette: 'greenRed' + } + } + { + columnMatch: 'Trend' + formatter: 21 + formatOptions: { + palette: 'green' + } + } + ] + filter: true + sortBy: [ + { + itemKey: '$gen_link__ResourceId_1' + sortOrder: 2 + } + ] + labelSettings: [ + { + columnId: 'Status' + label: 'Status' + } + { + columnId: '_ResourceId' + label: 'Computer' + } + ] + } + sortBy: [ + { + itemKey: '$gen_link__ResourceId_1' + sortOrder: 2 + } + ] + } + customWidth: '50' + name: 'query - 9 - Copy' + styleSettings: { + showBorder: true + } + } + { + type: 1 + content: { + json: '## Logical Disk: _Free Megabytes_ and _Avg read/write per sec_ ' + } + name: 'text - 12' + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'let TopDiscos = InsightsMetrics \r\n| where Origin == "vm.azm.ms"\r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "LogicalDisk" and Name == "FreeSpaceMB"\r\n| extend Disk = tostring(todynamic(Tags)["vm.azm.ms/mountId"])\r\n| where (strlen(Disk) ==2 and Disk contains ":") or Disk=="/"\r\n| extend Disco = strcat(Disk, " - ",Computer )\r\n| summarize FreeSpace = round(avg(Val),2) by Disco\r\n| top 10 by FreeSpace asc\r\n| project Disco; \r\nInsightsMetrics \r\n| where Origin == "vm.azm.ms"\r\n| where Namespace == "LogicalDisk" and Name == "FreeSpaceMB"\r\n| extend Disk = tostring(todynamic(Tags)["vm.azm.ms/mountId"])\r\n| where (strlen(Disk) ==2 and Disk contains ":") or Disk=="/"\r\n| extend Disco = strcat(Disk, " - ",Computer )\r\n| where Disco in (TopDiscos) \r\n| summarize FreeSpace = round(avg(Val),2) by Disco, bin(TimeGenerated, ({TimeRange:end} - {TimeRange:start})/100)\r\n\r\n\r\n' + size: 0 + aggregation: 3 + showAnalytics: true + title: 'Free Megabytes - Top 10 Computers-Volumes' + timeContextFromParameter: 'TimeRange' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + crossComponentResources: [ + '{Workspace}' + ] + visualization: 'timechart' + chartSettings: { + showLegend: true + ySettings: { + numberFormatSettings: { + unit: 4 + options: { + style: 'decimal' + useGrouping: true + } + } + } + } + } + customWidth: '50' + name: 'query - 6' + styleSettings: { + showBorder: true + } + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'let trend = \r\nInsightsMetrics \r\n| where Origin == "vm.azm.ms"\r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup}) \r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "LogicalDisk" and Name == "FreeSpaceMB"\r\n| extend Disk=tostring(todynamic(Tags)["vm.azm.ms/mountId"])\r\n| where (strlen(Disk) ==2 and Disk contains ":") or Disk=="/"\r\n| extend Disco = strcat(Disk, " - ", _ResourceId)\r\n| make-series Average = round(avg(Val), 3) default = 0 on TimeGenerated from {TimeRange:start} to {TimeRange:end} step totimespan(\'00:30:00\') by Disco \r\n| project Disco, [\'Trend\'] = Average; \r\n\r\nInsightsMetrics\r\n| where Origin == "vm.azm.ms"\r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "LogicalDisk" and Name == "FreeSpaceMB"\r\n| extend Disk=tostring(todynamic(Tags)["vm.azm.ms/mountId"])\r\n| where (strlen(Disk) ==2 and Disk contains ":") or Disk=="/"\r\n| extend Disco = strcat(Disk, " - ",_ResourceId )\r\n| summarize Average=round(avg(Val),2) by Disco,_ResourceId,Disk\r\n| join (trend) on Disco\r\n| extend Status = case (\r\n Average < 5000, "Critical",\r\n Average < 30000, "Warning",\r\n Average >= 30000, "Healthy", "Unknown"\r\n)\r\n\r\n| project Status, _ResourceId,Disk, Average, Trend\r\n| sort by Average asc ' + size: 0 + showAnalytics: true + title: 'Thresholds (Warning < 30GB; Critical < 5GB) - All Computers-Volumes' + timeContextFromParameter: 'TimeRange' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + crossComponentResources: [ + '{Workspace}' + ] + gridSettings: { + formatters: [ + { + columnMatch: 'Status' + formatter: 18 + formatOptions: { + thresholdsOptions: 'icons' + thresholdsGrid: [ + { + operator: '==' + thresholdValue: 'Healthy' + representation: 'success' + text: '{1}' + } + { + operator: '==' + thresholdValue: 'Warning' + representation: '2' + text: '{1}' + } + { + operator: '==' + thresholdValue: 'Critical' + representation: 'critical' + text: '{1}' + } + { + operator: 'Default' + thresholdValue: null + representation: 'unknown' + text: '{1}' + } + ] + } + } + { + columnMatch: 'Average' + formatter: 8 + formatOptions: { + min: 0 + max: 40000 + palette: 'redGreen' + } + numberFormat: { + unit: 4 + options: { + style: 'decimal' + useGrouping: false + minimumFractionDigits: 2 + maximumFractionDigits: 2 + } + } + } + { + columnMatch: 'Trend' + formatter: 21 + formatOptions: { + palette: 'green' + } + } + ] + filter: true + sortBy: [ + { + itemKey: '$gen_link__ResourceId_1' + sortOrder: 2 + } + ] + labelSettings: [ + { + columnId: 'Status' + label: 'Status' + } + { + columnId: '_ResourceId' + label: 'Computer' + } + ] + } + sortBy: [ + { + itemKey: '$gen_link__ResourceId_1' + sortOrder: 2 + } + ] + } + customWidth: '50' + name: 'query - 9 - Copy - Copy' + styleSettings: { + showBorder: true + } + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'let TopDiscos = InsightsMetrics \r\n| where Origin == "vm.azm.ms"\r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "LogicalDisk" and Name == "ReadsPerSecond"\r\n| extend Disk=tostring(todynamic(Tags)["vm.azm.ms/mountId"])\r\n| where (strlen(Disk) ==2 and Disk contains ":") or ( Disk == "/")\r\n| extend Disco = strcat(Disk, " - ",Computer )\r\n| summarize MilliSeconds = avg(Val) by Disco\r\n| top 10 by MilliSeconds desc\r\n| project Disco; \r\nInsightsMetrics \r\n| where Origin == "vm.azm.ms"\r\n| where Namespace == "LogicalDisk" and Name == "ReadsPerSecond"\r\n| extend Disk=tostring(todynamic(Tags)["vm.azm.ms/mountId"])\r\n| where (strlen(Disk) ==2 and Disk contains ":") or ( Disk == "/")\r\n| extend Disco = strcat(Disk, " - ",Computer )\r\n| where Disco in (TopDiscos) \r\n| summarize MilliSeconds = avg(Val) by Disco, bin(TimeGenerated, ({TimeRange:end} - {TimeRange:start})/100)\r\n| render timechart\r\n\r\n\r\n' + size: 0 + aggregation: 3 + showAnalytics: true + title: 'Disk Reads/sec - Top 10 Computers-Volume' + timeContextFromParameter: 'TimeRange' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + crossComponentResources: [ + '{Workspace}' + ] + visualization: 'timechart' + chartSettings: { + group: 'Disco' + createOtherGroup: 22 + showLegend: true + } + } + customWidth: '50' + name: 'query - 6 - Copy' + styleSettings: { + showBorder: true + } + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'let trend = \r\nInsightsMetrics\r\n| where Origin == "vm.azm.ms" \r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup}) \r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All" \r\n| where Namespace == "LogicalDisk" and Name == "ReadsPerSecond"\r\n| extend Disk=tostring(todynamic(Tags)["vm.azm.ms/mountId"])\r\n| where ((strlen(Disk) == 2 and Disk contains ":") or (Disk == "/"))\r\n| extend Disco = strcat(Disk, " - ",_ResourceId )\r\n| make-series AVGReads = round(avg(Val),2) default = 0 on TimeGenerated from {TimeRange:start} to {TimeRange:end} step totimespan(\'00:30:00\') by Disco \r\n| project Disco, [\'Trend\'] = AVGReads; \r\n\r\nInsightsMetrics\r\n| where Origin == "vm.azm.ms"\r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "LogicalDisk" and Name == "ReadsPerSecond"\r\n| extend Disk=tostring(todynamic(Tags)["vm.azm.ms/mountId"])\r\n| where ((strlen(Disk) == 2 and Disk contains ":") or (Disk == "/"))\r\n| extend Disco = strcat(Disk, " - ",_ResourceId )\r\n| summarize AVGReads=round(avg(Val),2) by Disco,_ResourceId,Disk\r\n| join (trend) on Disco\r\n| extend Status = case (\r\n AVGReads > 25, "Critical",\r\n AVGReads > 15, "Warning",\r\n AVGReads <= 15, "Healthy", "Unknown"\r\n)\r\n| project _ResourceId,Disk, ["Reads"]=AVGReads, Trend\r\n| sort by ["Reads"] desc ' + size: 0 + showAnalytics: true + timeContextFromParameter: 'TimeRange' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + crossComponentResources: [ + '{Workspace}' + ] + gridSettings: { + formatters: [ + { + columnMatch: 'Trend' + formatter: 21 + formatOptions: { + palette: 'green' + } + } + { + columnMatch: 'Status' + formatter: 18 + formatOptions: { + thresholdsOptions: 'icons' + thresholdsGrid: [ + { + operator: '==' + thresholdValue: 'Healthy' + representation: 'success' + text: '{1}' + } + { + operator: '==' + thresholdValue: 'Warning' + representation: '2' + text: '{1}' + } + { + operator: '==' + thresholdValue: 'Critical' + representation: 'critical' + text: '{1}' + } + { + operator: 'Default' + thresholdValue: null + representation: 'unknown' + text: '{1}' + } + ] + } + } + { + columnMatch: 'Average' + formatter: 8 + formatOptions: { + min: 0 + max: 100 + palette: 'blue' + } + } + ] + filter: true + sortBy: [ + { + itemKey: '$gen_link__ResourceId_0' + sortOrder: 2 + } + ] + labelSettings: [ + { + columnId: '_ResourceId' + label: 'Computer' + } + ] + } + sortBy: [ + { + itemKey: '$gen_link__ResourceId_0' + sortOrder: 2 + } + ] + } + customWidth: '50' + name: 'query - 9 - Copy - Copy - Copy' + styleSettings: { + showBorder: true + } + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'let TopDiscos= InsightsMetrics \r\n| where Origin == "vm.azm.ms"\r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "LogicalDisk" and Name == "WritesPerSecond"\r\n| extend Disk=tostring(todynamic(Tags)["vm.azm.ms/mountId"])\r\n| where (strlen(Disk) ==2 and Disk contains ":") or Disk=="/"\r\n| extend Disco = strcat(Disk, " - ",Computer )\r\n| summarize MilliSeconds = avg(Val) by Disco\r\n| top 10 by MilliSeconds desc\r\n| project Disco; \r\nInsightsMetrics \r\n| where Origin == "vm.azm.ms"\r\n| where Namespace == "LogicalDisk" and Name == "WritesPerSecond"\r\n| extend Disk=tostring(todynamic(Tags)["vm.azm.ms/mountId"])\r\n| where (strlen(Disk) ==2 and Disk contains ":") or Disk=="/"\r\n| extend Disco = strcat(Disk, " - ",Computer )\r\n| where Disco in (TopDiscos) \r\n| summarize MilliSeconds = avg(Val) by Disco, bin(TimeGenerated, ({TimeRange:end} - {TimeRange:start})/100)\r\n| render timechart\r\n\r\n\r\n' + size: 0 + showAnalytics: true + title: 'Disk Writes/sec - Top 10 Computers-Volume' + timeContextFromParameter: 'TimeRange' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + crossComponentResources: [ + '{Workspace}' + ] + visualization: 'timechart' + chartSettings: { + group: 'Disco' + createOtherGroup: 22 + showLegend: true + } + } + customWidth: '50' + name: 'query - 6 - Copy - Copy' + styleSettings: { + showBorder: true + } + } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'let trend = \r\nInsightsMetrics \r\n| where Origin == "vm.azm.ms" \r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "LogicalDisk" and Name == "WritesPerSecond"\r\n| extend Disk=tostring(todynamic(Tags)["vm.azm.ms/mountId"])\r\n| where (strlen(Disk) ==2 and Disk contains ":") or Disk=="/"\r\n| extend Disco = strcat(Disk, " - ",_ResourceId )\r\n| make-series AVGWrites = round(avg(Val),2) default = 0 on TimeGenerated from {TimeRange:start} to {TimeRange:end} step totimespan(\'00:30:00\') by Disco \r\n| project Disco, [\'Trend\'] = AVGWrites; \r\n\r\nInsightsMetrics\r\n| where Origin == "vm.azm.ms"\r\n| extend RGName = tolower(split(_ResourceId, "/")[4])\r\n| where RGName in ({ResourceGroup})\r\n| where _ResourceId contains "{_ResourceId}" or "{_ResourceId}"=="All"\r\n| where Namespace == "LogicalDisk" and Name == "WritesPerSecond"\r\n| extend Disk=tostring(todynamic(Tags)["vm.azm.ms/mountId"])\r\n| where (strlen(Disk) ==2 and Disk contains ":") or Disk=="/"\r\n| extend Disco = strcat(Disk, " - ",_ResourceId )\r\n| summarize AVGWrites=round(avg(Val),2) by Disco,_ResourceId,Disk\r\n| join (trend) on Disco\r\n| extend Status = case (\r\n AVGWrites > 25, "Critical",\r\n AVGWrites > 15, "Warning",\r\n AVGWrites <= 15, "Healthy", "Unknown"\r\n)\r\n\r\n| project _ResourceId,Disk, ["Writes"]=AVGWrites, Trend\r\n| sort by ["Writes"] desc ' + size: 0 + showAnalytics: true + timeContextFromParameter: 'TimeRange' + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + crossComponentResources: [ + '{Workspace}' + ] + gridSettings: { + formatters: [ + { + columnMatch: 'Trend' + formatter: 21 + formatOptions: { + palette: 'green' + } + } + { + columnMatch: 'Status' + formatter: 18 + formatOptions: { + thresholdsOptions: 'icons' + thresholdsGrid: [ + { + operator: '==' + thresholdValue: 'Healthy' + representation: 'success' + text: '{1}' + } + { + operator: '==' + thresholdValue: 'Warning' + representation: '2' + text: '{1}' + } + { + operator: '==' + thresholdValue: 'Critical' + representation: 'critical' + text: '{1}' + } + { + operator: 'Default' + thresholdValue: null + representation: 'unknown' + text: '{1}' + } + ] + } + } + { + columnMatch: 'Average' + formatter: 8 + formatOptions: { + min: 0 + max: 100 + palette: 'blue' + } + } + ] + filter: true + sortBy: [ + { + itemKey: '$gen_link__ResourceId_0' + sortOrder: 2 + } + ] + labelSettings: [ + { + columnId: '_ResourceId' + label: 'Computer' + } + ] + } + sortBy: [ + { + itemKey: '$gen_link__ResourceId_0' + sortOrder: 2 + } + ] + } + customWidth: '50' + name: 'query - 9 - Copy - Copy - Copy - Copy' + styleSettings: { + showBorder: true + } + } + ] + fallbackResourceIds: [ + 'azure monitor' + ] + '$schema': 'https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json' +} +resource workbookId_resource 'microsoft.insights/workbooks@2022-04-01' = { + name: workbookId + location: location + kind: 'shared' + properties: { + displayName: workbookDisplayName + serializedData: string(workbookContent) + version: '1.0' + sourceId: workbookSourceId + category: workbookType + } + dependsOn: [] +} +output workbookId string = workbookId_resource.id diff --git a/azure_jumpstart_ag/artifacts/settings/Bookmarks-manufacturing b/azure_jumpstart_ag/artifacts/settings/Bookmarks-manufacturing new file mode 100644 index 0000000000..9ba8dea075 --- /dev/null +++ b/azure_jumpstart_ag/artifacts/settings/Bookmarks-manufacturing @@ -0,0 +1,125 @@ +{ + "checksum": "d77f9db622cff666aa1ae0f899c3b4ec", + "roots": { + "bookmark_bar": { + "children": [ + { + "children": [ { + "id": "18", + "name": "Control center Detroit", + "show_icon": false, + "source": "unknown", + "type": "url", + "url": "Flask-Detroit-URL" + }, { + "id": "19", + "name": "Control center Monterrey", + "show_icon": false, + "source": "unknown", + "type": "url", + "url": "Flask-Monterrey-URL" + }], + "id": "16", + "name": "Control centers", + "source": "unknown", + "type": "folder" + }, + { + "children": [ { + "id": "16", + "name": "Influxdb Detroit", + "show_icon": false, + "source": "unknown", + "type": "url", + "url": "Influxdb-Detroit-URL" + }, { + "id": "17", + "name": "Influxdb Monterrey", + "show_icon": false, + "source": "unknown", + "type": "url", + "url": "Influxdb-Monterrey-URL" + }], + "id": "15", + "name": "Influxdb", + "source": "unknown", + "type": "folder" + }, + { + "children": [ { + "id": "20", + "name": "Grafana", + "show_icon": false, + "source": "unknown", + "type": "url", + "url": "http://localhost:3000" + }], + "id": "17", + "name": "Grafana", + "source": "unknown", + "type": "folder" + }, + { + "children": [ { + "id": "30", + "name": "Prometheus Detroit", + "show_icon": false, + "source": "unknown", + "type": "url", + "url": "Prometheus-Detroit-URL" + }, { + "id": "31", + "name": "Prometheus Monterrey", + "show_icon": false, + "source": "unknown", + "type": "url", + "url": "Prometheus-Monterrey-URL" + }], + "id": "29", + "name": "Prometheus", + "source": "unknown", + "type": "folder" + }, { + "id": "22", + "name": "ADX Dashboards", + "show_icon": false, + "source": "unknown", + "type": "url", + "url": "https://dataexplorer.azure.com/dashboards/" + }, { + "id": "23", + "name": "Arc Jumpstart", + "show_icon": false, + "source": "unknown", + "type": "url", + "url": "https://aka.ms/ArcJumpstart/" + }, { + "id": "24", + "name": "Azure Portal", + "show_icon": false, + "source": "unknown", + "type": "url", + "url": "https://portal.azure.com/" + } ], + "id": "1", + "name": "Favorites bar", + "source": "unknown", + "type": "folder" + }, + "other": { + "children": [ ], + "id": "25", + "name": "Other favorites", + "source": "unknown", + "type": "folder" + }, + "synced": { + "children": [ ], + "id": "26", + "name": "Mobile favorites", + "source": "unknown", + "type": "folder" + } + }, + "version": 1 +} \ No newline at end of file diff --git a/azure_jumpstart_ag/artifacts/settings/Bookmarks b/azure_jumpstart_ag/artifacts/settings/Bookmarks-retail similarity index 99% rename from azure_jumpstart_ag/artifacts/settings/Bookmarks rename to azure_jumpstart_ag/artifacts/settings/Bookmarks-retail index 120ebcc514..3445f225f2 100644 --- a/azure_jumpstart_ag/artifacts/settings/Bookmarks +++ b/azure_jumpstart_ag/artifacts/settings/Bookmarks-retail @@ -178,7 +178,7 @@ "url": "https://dataexplorer.azure.com/dashboards/" }, { "id": "23", - "name": "Azure Arc Jumpstart", + "name": "Arc Jumpstart", "show_icon": false, "source": "unknown", "type": "url", diff --git a/azure_jumpstart_ag/artifacts/settings/DockerDesktopSettings.json b/azure_jumpstart_ag/artifacts/settings/DockerDesktopSettings.json index 2633662e24..d8c604aed9 100644 --- a/azure_jumpstart_ag/artifacts/settings/DockerDesktopSettings.json +++ b/azure_jumpstart_ag/artifacts/settings/DockerDesktopSettings.json @@ -1,4 +1,5 @@ { + "IPv6Only": false, "acceptCanaryUpdates": false, "activeOrganizationName": "", "allowBetaFeatures": false, @@ -10,25 +11,40 @@ "autoStart": false, "backupData": false, "blockDockerLoad": false, + "composeAutoFileShares": null, "containerTerminal": "integrated", + "containersOverrideProxyExclude": "", + "containersOverrideProxyHttp": "", + "containersOverrideProxyHttps": "", + "containersOverrideProxyPAC": "", + "containersOverrideProxyTCP": "", + "containersOverrideProxyTransparentPorts": "80,443", + "containersProxyHttpMode": "", "cpus": 8, "credentialHelper": "docker-credential-wincred.exe", "customWslDistroDir": "", "dataFolder": "C:\\ProgramData\\DockerDesktop\\vm-data", + "defaultSnapshotter": "overlayfs", "deprecatedCgroupv1": false, + "devEnvironmentsEnabled": false, "disableHardwareAcceleration": false, "disableUpdate": false, - "diskFlush": "os", + "diskFlush": "", "diskSizeMiB": 65536, "diskStats": "", "diskTRIM": true, "displayRestartDialog": true, - "displaySwitchWinLinContainers": true, + "displaySwitchWinLinContainers": false, "displayed18362Deprecation": false, "displayedElectronPopup": [], "displayedOnboarding": false, "dockerAppLaunchPath": "", "dockerBinInstallPath": "system", + "dockerDebugDefaultEnabled": false, + "dogfoodFeatureFlagsEnabled": false, + "eciDockerSocketCmdList": [], + "eciDockerSocketCmdListType": "deny", + "eciDockerSocketImgList": [], "enableDefaultDockerSocket": true, "enableIntegrationWithDefaultWslDistro": true, "enableIntegrityCheck": true, @@ -40,9 +56,13 @@ "extensionsPrivateMarketplace": false, "extensionsPrivateMarketplaceAdminContactURL": "", "filesharingDirectories": [], - "integratedWslDistros": [], - "kernelForUDP": false, + "hostNetworkingEnabled": false, + "integratedWslDistros": [ + "Ubuntu" + ], + "kernelForUDP": true, "kubernetesEnabled": false, + "kubernetesImagesRepository": "", "kubernetesInitialInstallPerformed": false, "lastLoginDate": 0, "latestBannerKey": "", @@ -57,17 +77,22 @@ "overrideProxyHttp": "", "overrideProxyHttps": "", "overrideProxyPAC": "", + "overrideProxyTCP": "", "overrideWindowsDockerdPort": -1, "proxyHttpMode": "system", + "proxyLocalhostPort": 0, "requireVmnetd": true, "runWinServiceInWslMode": false, "sbomIndexing": true, - "settingsVersion": 35, + "scoutInAppNotificationsEnabled": false, + "scoutOsNotificationsEnabled": false, + "settingsVersion": 36, "showAnnouncementNotifications": false, "showExtensionsSystemContainers": false, "showGeneralNotifications": false, "showInstallScreen": false, "showKubernetesSystemContainers": false, + "showPromotionalNotifications": false, "showSurveyNotifications": false, "skipUpdateToWSLPrompt": true, "skipWSLMountPerfWarning": false, @@ -89,9 +114,9 @@ "useVirtualizationFrameworkVirtioFS": false, "useVpnkit": true, "useWindowsContainers": false, - "vpnKitAllowedBindAddresses": "0.0.0.0", - "vpnKitMTU": 1500, - "vpnKitMaxConnections": 2000, + "vpnKitAllowedBindAddresses": "", + "vpnKitMTU": 0, + "vpnKitMaxConnections": 0, "vpnKitMaxPortIdleTime": 300, "vpnKitTransparentProxy": true, "vpnkitCIDR": "192.168.65.0/24", diff --git a/azure_jumpstart_ag/artifacts/settings/influxdb-configmap.yml b/azure_jumpstart_ag/artifacts/settings/influxdb-configmap.yml new file mode 100644 index 0000000000..c2eb2e5342 --- /dev/null +++ b/azure_jumpstart_ag/artifacts/settings/influxdb-configmap.yml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: dashboard-config +data: + report_demo.json: | + [{"apiVersion":"influxdata.com/v2alpha1","kind":"Dashboard","metadata":{"name":"priceless-dubinsky-15a001"},"spec":{"charts":[{"colors":[{"id":"base","name":"laser","type":"background","hex":"#00C9FF"},{"id":"-OSk3ZHwI-9qzlZ2QfQPQ","name":"pineapple","type":"background","hex":"#FFB94A","value":80},{"id":"54eKypz7pFEJLV5zvkHci","name":"honeydew","type":"background","hex":"#7CE490","value":90}],"decimalPlaces":2,"height":1,"kind":"Single_Stat","name":"Overall Efficiency","queries":[{"query":"from(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/productionline\")\n |> filter(fn: (r) => r[\"_field\"] == \"OverallEfficiency\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")"}],"staticLegend":{},"width":3},{"colors":[{"id":"0","name":"curacao","type":"min","hex":"#F95F53"},{"id":"3BQRuxy21foOTRuynaBbT","name":"pineapple","type":"threshold","hex":"#FFB94A","value":80},{"id":"UmDKq3fT8NFnXrLCy0T2x","name":"honeydew","type":"threshold","hex":"#7CE490","value":90},{"id":"1","name":"honeydew","type":"max","hex":"#7CE490","value":100}],"decimalPlaces":2,"height":2,"kind":"Gauge","name":"Availability","queries":[{"query":"from(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/productionline\")\n |> filter(fn: (r) => r[\"_field\"] == \"Availability\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")"}],"staticLegend":{},"width":3,"yPos":1},{"axes":[{"base":"10","name":"x","scale":"linear"},{"base":"10","name":"y","scale":"linear"}],"colorizeRows":true,"colors":[{"id":"e9Cw-2YyDrKAIdLRHX-8r","name":"Delorean","type":"scale","hex":"#FD7A5D"},{"id":"QNncvwjZ-gxcEWK-n2Cdp","name":"Delorean","type":"scale","hex":"#5F1CF2"},{"id":"MHoU5w2iozIwXU4MI-v-z","name":"Delorean","type":"scale","hex":"#4CE09A"}],"geom":"step","height":3,"hoverDimension":"auto","kind":"Xy","legendColorizeRows":true,"legendOpacity":1,"legendOrientationThreshold":100000000,"name":"Downtime","opacity":1,"orientationThreshold":100000000,"position":"overlaid","queries":[{"query":"from(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/productionline\")\n |> filter(fn: (r) => r[\"_field\"] == \"DownTime\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")"}],"staticLegend":{"colorizeRows":true,"opacity":1,"orientationThreshold":100000000,"widthRatio":1},"width":7,"widthRatio":1,"xCol":"_time","yCol":"_value","yPos":3},{"axes":[{"base":"10","name":"x","scale":"linear"},{"base":"10","name":"y","scale":"linear"}],"colorizeRows":true,"colors":[{"id":"DCWSO9dpLF_BrvtRxnAR3","name":"Nineteen Eighty Four","type":"scale","hex":"#31C0F6"},{"id":"f3dDOAwqo7-DyaeujwnXr","name":"Nineteen Eighty Four","type":"scale","hex":"#A500A5"},{"id":"JDrOgEW8LlH9kbSz9On2Y","name":"Nineteen Eighty Four","type":"scale","hex":"#FF7E27"}],"geom":"line","height":3,"hoverDimension":"auto","kind":"Xy","legendColorizeRows":true,"legendOpacity":1,"legendOrientationThreshold":100000000,"name":"Oil temperature","opacity":1,"orientationThreshold":100000000,"position":"overlaid","queries":[{"query":"from(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/productionline\")\n |> filter(fn: (r) => r[\"_field\"] == \"TargetCutPerMinutes\" or r[\"_field\"] == \"CurrentCutPerMinutes\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")"}],"staticLegend":{"colorizeRows":true,"opacity":1,"orientationThreshold":100000000,"widthRatio":1},"width":7,"widthRatio":1,"xCol":"_time","yCol":"_value","yPos":6},{"colors":[{"id":"base","name":"laser","type":"text","hex":"#00C9FF"}],"decimalPlaces":2,"height":1,"kind":"Single_Stat","name":"Batch completed","queries":[{"query":"from(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/productionline\")\n |> filter(fn: (r) => r[\"_field\"] == \"CompletedDoughs\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n\nfrom(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/productionline\")\n |> filter(fn: (r) => r[\"_field\"] == \"CompletedDoughs\")\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\n |> yield(name: \"last\")"}],"staticLegend":{},"width":3,"xPos":3},{"colors":[{"id":"0","name":"fire","type":"min","hex":"#DC4E58"},{"id":"PDFCUrH43se0hP6kP6tNg","name":"thunder","type":"threshold","hex":"#FFD255","value":70},{"id":"GBy2gLjKsSc-v56Rwu0Va","name":"honeydew","type":"threshold","hex":"#7CE490","value":90},{"id":"1","name":"viridian","type":"max","hex":"#32B08C","value":100}],"decimalPlaces":2,"height":2,"kind":"Gauge","name":"Quality","queries":[{"query":"from(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/productionline\")\n |> filter(fn: (r) => r[\"_field\"] == \"Quality\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")"}],"staticLegend":{},"width":3,"xPos":3,"yPos":1},{"colors":[{"id":"base","name":"laser","type":"text","hex":"#00C9FF"}],"decimalPlaces":2,"height":1,"kind":"Single_Stat","name":"Current Shift","queries":[{"query":"from(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/productionline\")\n |> filter(fn: (r) => r[\"_field\"] == \"CurrentShift\")\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\n |> yield(name: \"last\")"}],"staticLegend":{},"width":3,"xPos":6},{"colors":[{"id":"0","name":"ruby","type":"min","hex":"#BF3D5E"},{"id":"CR2q8dxx6WKVcBfk6PyGM","name":"pineapple","type":"threshold","hex":"#FFB94A","value":80},{"id":"4fgeiiGAoga1-ctNbTrKW","name":"honeydew","type":"threshold","hex":"#7CE490","value":90},{"id":"1","name":"rainforest","type":"max","hex":"#4ED8A0","value":100}],"decimalPlaces":2,"height":2,"kind":"Gauge","name":"Performance","queries":[{"query":"from(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/productionline\")\n |> filter(fn: (r) => r[\"_field\"] == \"Performance\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")"}],"staticLegend":{},"width":3,"xPos":6,"yPos":1},{"colors":[{"id":"base","name":"laser","type":"text","hex":"#00C9FF"}],"decimalPlaces":2,"height":1,"kind":"Single_Stat","name":"fryer Humidity","queries":[{"query":"from(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/fryer\")\n |> filter(fn: (r) => r[\"_field\"] == \"Humidity\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")"}],"staticLegend":{},"width":2,"xPos":7,"yPos":3},{"axes":[{"base":"10","name":"x","scale":"linear"},{"base":"10","name":"y","scale":"linear"}],"colorizeRows":true,"colors":[{"id":"DCWSO9dpLF_BrvtRxnAR3","name":"Nineteen Eighty Four","type":"scale","hex":"#31C0F6"},{"id":"f3dDOAwqo7-DyaeujwnXr","name":"Nineteen Eighty Four","type":"scale","hex":"#A500A5"},{"id":"JDrOgEW8LlH9kbSz9On2Y","name":"Nineteen Eighty Four","type":"scale","hex":"#FF7E27"}],"geom":"line","height":2,"hoverDimension":"auto","kind":"Xy","legendColorizeRows":true,"legendOpacity":1,"legendOrientationThreshold":100000000,"name":"fryer Voltage","opacity":1,"orientationThreshold":100000000,"position":"overlaid","queries":[{"query":"from(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/fryer\")\n |> filter(fn: (r) => r[\"_field\"] == \"Voltage\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")"}],"staticLegend":{"colorizeRows":true,"opacity":1,"orientationThreshold":100000000,"widthRatio":1},"width":5,"widthRatio":1,"xCol":"_time","xPos":7,"yCol":"_value","yPos":4},{"axes":[{"base":"10","name":"x","scale":"linear"},{"base":"10","name":"y","scale":"linear"}],"colorizeRows":true,"colors":[{"id":"3Klw7kgHH0KzcILU2Qlk8","name":"Solid Green","type":"scale","hex":"#34BB55"},{"id":"nGMbaMg0WHO20E9BllgMQ","name":"Solid Green","type":"scale","hex":"#34BB55"},{"id":"zVZGa94dsEPNsx6gnqts2","name":"Solid Green","type":"scale","hex":"#34BB55"}],"geom":"monotoneX","height":2,"hoverDimension":"auto","kind":"Xy","legendColorizeRows":true,"legendOpacity":1,"legendOrientationThreshold":100000000,"name":"fryer Tank Level","opacity":1,"orientationThreshold":100000000,"position":"overlaid","queries":[{"query":"from(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/fryer\")\n |> filter(fn: (r) => r[\"_field\"] == \"Tank_Level\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")"}],"staticLegend":{"colorizeRows":true,"opacity":1,"orientationThreshold":100000000,"widthRatio":1},"width":5,"widthRatio":1,"xCol":"_time","xPos":7,"yCol":"_value","yPos":6},{"colors":[{"id":"base","name":"laser","type":"text","hex":"#00C9FF"}],"decimalPlaces":2,"height":1,"kind":"Single_Stat","name":"Current SKU","queries":[{"query":"from(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/productionline\")\n |> filter(fn: (r) => r[\"_field\"] == \"Product\")\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\n |> yield(name: \"last\")"}],"staticLegend":{},"width":3,"xPos":9},{"colors":[{"id":"base","name":"laser","type":"text","hex":"#00C9FF"}],"decimalPlaces":2,"height":2,"kind":"Single_Stat","name":"Lost time classification","queries":[{"query":"from(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/productionline\")\n |> filter(fn: (r) => r[\"_field\"] == \"LostTimeReason\")\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\n |> yield(name: \"last\")"}],"staticLegend":{},"width":3,"xPos":9,"yPos":1},{"colors":[{"id":"base","name":"laser","type":"text","hex":"#00C9FF"}],"decimalPlaces":2,"height":1,"kind":"Single_Stat","name":"fryer Temperature","queries":[{"query":"from(bucket: \"manufacturing\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"topic/fryer\")\n |> filter(fn: (r) => r[\"_field\"] == \"Temperature\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")"}],"staticLegend":{},"width":3,"xPos":9,"yPos":3}],"name":"Contoso Bakery Strawberry Donut production line"}}] + \ No newline at end of file diff --git a/azure_jumpstart_ag/artifacts/settings/influxdb-import-dashboard.yml b/azure_jumpstart_ag/artifacts/settings/influxdb-import-dashboard.yml new file mode 100644 index 0000000000..e3df9e2196 --- /dev/null +++ b/azure_jumpstart_ag/artifacts/settings/influxdb-import-dashboard.yml @@ -0,0 +1,34 @@ + +apiVersion: batch/v1 +kind: Job +metadata: + name: influxdb-import-dashboard +spec: + template: + spec: + restartPolicy: Never + containers: + - name: influxdb-import-dashboard + image: influxdb:latest + command: + - influx + args: + - apply + - -f + - "/etc/config/report_demo.json" + - --org + - InfluxData + - --token + - secret-token + - --host + - http://influxPlaceholder:8086 + - --force + - "yes" + volumeMounts: + - name: config-volume + mountPath: "/etc/config" + volumes: + - name: config-volume + configMap: + name: dashboard-config + diff --git a/azure_jumpstart_ag/artifacts/settings/influxdb-setup.yml b/azure_jumpstart_ag/artifacts/settings/influxdb-setup.yml new file mode 100644 index 0000000000..472968b00e --- /dev/null +++ b/azure_jumpstart_ag/artifacts/settings/influxdb-setup.yml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: influxdb +spec: + type: LoadBalancer + selector: + app: influxdb + ports: + - name: api + port: 9999 + protocol: TCP + targetPort: 9999 + - name: gui + port: 8086 + protocol: TCP + targetPort: 8086 \ No newline at end of file diff --git a/azure_jumpstart_ag/artifacts/settings/influxdb.yml b/azure_jumpstart_ag/artifacts/settings/influxdb.yml new file mode 100644 index 0000000000..d18d9f6ed0 --- /dev/null +++ b/azure_jumpstart_ag/artifacts/settings/influxdb.yml @@ -0,0 +1,100 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: contosoba +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: contosoba-clusterrole +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: contosoba + namespace: azure-iot-operations +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: contosoba-role +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: cluster-admin +subjects: +- kind: ServiceAccount + name: contosoba +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: influxdb +spec: + serviceName: "influxdb" + selector: + matchLabels: + app: influxdb + template: + metadata: + labels: + app: influxdb + spec: + serviceAccount: contosoba + containers: + - name: influxdb + image: influxdb:latest + resources: + limits: + memory: "1Gi" + cpu: "500m" + ports: + - name: api + containerPort: 9999 + - name: gui + containerPort: 8086 + volumeMounts: + - name: data + mountPath: /var/lib/influxdb2 + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: + - ReadWriteOnce + storageClassName: "local-path" + resources: + requests: + storage: 10Gi + volumeMode: Filesystem +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: influxdb-setup +spec: + template: + spec: + restartPolicy: Never + containers: + - name: create-credentials + image: influxdb:latest + command: + - influx + args: + - setup + - --host + - http://influxPlaceholder:8086 + - --bucket + - manufacturing + - --org + - InfluxData + - --password + - influxAdminPwdPlaceHolder + - --username + - influxAdminPlaceHolder + - --token + - secret-token + - --force diff --git a/azure_jumpstart_ag/artifacts/settings/mq_cloudConnector.yml b/azure_jumpstart_ag/artifacts/settings/mq_cloudConnector.yml new file mode 100644 index 0000000000..1b66e752b9 --- /dev/null +++ b/azure_jumpstart_ag/artifacts/settings/mq_cloudConnector.yml @@ -0,0 +1,40 @@ +apiVersion: connectivity.iotoperations.azure.com/v1beta1 +kind: DataflowEndpoint +metadata: + name: eventgrid +spec: + endpointType: mqtt + authentication: + method: systemAssignedManagedIdentity + systemAssignedManagedIdentitySettings: + audience: https://eventgrid.azure.net + mqttSettings: + host: eventGridPlaceholder:8883 + tls: + mode: Enabled +--- +apiVersion: connectivity.iotoperations.azure.com/v1beta1 +kind: Dataflow +metadata: + name: my-topic-map +spec: + profileRef: my-dataflow-profile + operations: + - operationType: source + name: source1 + sourceSettings: + endpointRef: mq + dataSources: + - "topic/#" + - operationType: destination + name: destination1 + destinationSettings: + endpointRef: eventgrid + dataDestination: factory-gateway- +--- +apiVersion: connectivity.iotoperations.azure.com/v1beta1 +kind: DataflowProfile +metadata: + name: my-dataflow-profile +spec: + instanceCount: 1 \ No newline at end of file diff --git a/azure_jumpstart_ag/artifacts/settings/mqtt_explorer_settings.json b/azure_jumpstart_ag/artifacts/settings/mqtt_explorer_settings.json new file mode 100644 index 0000000000..6c5d40f802 --- /dev/null +++ b/azure_jumpstart_ag/artifacts/settings/mqtt_explorer_settings.json @@ -0,0 +1,34 @@ +{ + "ConnectionManager_connections": { + "mqtt.eclipse.org": { + "certValidation": false, + "clientId": "mqtt-explorer-640c948e", + "encryption": false, + "host": "detroitIpPlaceholder", + "id": "mqtt.eclipse.org", + "name": "detroit", + "port": 1883, + "protocol": "mqtt", + "subscriptions": [ + "#", + "$SYS/#" + ], + "type": "mqtt" + }, + "mqtt.eclipse.org2": { + "certValidation": false, + "clientId": "mqtt-explorer-640c948e", + "encryption": false, + "host": "monterreyIpPlaceholder", + "id": "mqtt.eclipse.org2", + "name": "monterrey", + "port": 1883, + "protocol": "mqtt", + "subscriptions": [ + "#", + "$SYS/#" + ], + "type": "mqtt" + } + } +} \ No newline at end of file diff --git a/azure_jumpstart_ag/artifacts/settings/mqtt_listener.yml b/azure_jumpstart_ag/artifacts/settings/mqtt_listener.yml new file mode 100644 index 0000000000..5d82b71c8d --- /dev/null +++ b/azure_jumpstart_ag/artifacts/settings/mqtt_listener.yml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mqtt-listener-deployment + labels: + app: mqtt-listener +spec: + replicas: 1 + selector: + matchLabels: + app: mqtt-listener + template: + metadata: + labels: + app: mqtt-listener + spec: + containers: + - name: mqtt-listener + image: jumpstartprod.azurecr.io/mqtt-listener:latest + resources: + limits: + memory: "512Mi" + cpu: "500m" + env: + - name: MQTT_BROKER + value: "MQTTIpPlaceholder" + - name: MQTT_Port + value: "1883" + - name: MQTT_TOPIC1 + value: "topic/weldingrobot" + - name: MQTT_TOPIC2 + value: "topic/productionline" + - name: MQTT_TOPIC3 + value: "topic/assemblyline" + - name: INFLUX_URL + value: "http://influxPlaceholder:8086" + - name: INFLUX_TOKEN + value: "secret-token" + - name: INFLUX_ORG + value: "InfluxData" + - name: INFLUX_BUCKET + value: "manufacturing" diff --git a/azure_jumpstart_ag/artifacts/settings/mqtt_simulator.yml b/azure_jumpstart_ag/artifacts/settings/mqtt_simulator.yml new file mode 100644 index 0000000000..36b04431e4 --- /dev/null +++ b/azure_jumpstart_ag/artifacts/settings/mqtt_simulator.yml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mqtt-simulator-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: mqtt-simulator + template: + metadata: + labels: + app: mqtt-simulator + spec: + containers: + - name: mqtt-simulator + image: agoraarmbladev.azurecr.io/mqtt-simulator:latest + resources: + limits: + cpu: "1" + memory: "500Mi" + env: + - name: MQTT_BROKER + value: "MQTTIpPlaceholder" + - name: MQTT_PORT + value: "1883" + - name: FRECUENCY + value: "5" diff --git a/azure_jumpstart_ag/artifacts/workflows/pos-app-build-canary.yml b/azure_jumpstart_ag/artifacts/workflows/pos-app-build-canary.yml index 8ce0628db0..c6f4eaab92 100644 --- a/azure_jumpstart_ag/artifacts/workflows/pos-app-build-canary.yml +++ b/azure_jumpstart_ag/artifacts/workflows/pos-app-build-canary.yml @@ -16,7 +16,7 @@ jobs: steps: - name: 'Checkout repository' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Login to ACR' uses: azure/docker-login@v1 @@ -69,7 +69,7 @@ jobs: fi - name: 'Checkout canary branch' - uses: actions/checkout@v3 + uses: actions/checkout@v4 env: latest_tag: ${{ steps.latestImageTag.outputs.latest_tag }} canary_latest_tag: ${{ steps.canaryLatestImageTag.outputs.latest_tag }} diff --git a/azure_jumpstart_ag/artifacts/workflows/pos-app-build-production.yml b/azure_jumpstart_ag/artifacts/workflows/pos-app-build-production.yml index 3649a67638..4de810d7fb 100644 --- a/azure_jumpstart_ag/artifacts/workflows/pos-app-build-production.yml +++ b/azure_jumpstart_ag/artifacts/workflows/pos-app-build-production.yml @@ -16,7 +16,7 @@ jobs: steps: - name: 'Checkout repository' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Login to ACR' uses: azure/docker-login@v1 @@ -67,7 +67,7 @@ jobs: docker push ${{ secrets.ACR_NAME }}.azurecr.io/$namespace/contoso-supermarket/pos:$latest_tag - name: 'Checkout production branch' - uses: actions/checkout@v3 + uses: actions/checkout@v4 env: latest_tag: ${{ steps.latestImageTag.outputs.latest_tag }} prod_latest_tag: ${{ steps.prodLatestImageTag.outputs.latest_tag }} diff --git a/azure_jumpstart_ag/artifacts/workflows/pos-app-build-staging.yml b/azure_jumpstart_ag/artifacts/workflows/pos-app-build-staging.yml index 850a3a09a1..3762bc6d3d 100644 --- a/azure_jumpstart_ag/artifacts/workflows/pos-app-build-staging.yml +++ b/azure_jumpstart_ag/artifacts/workflows/pos-app-build-staging.yml @@ -23,7 +23,7 @@ jobs: steps: # checkout the repo - name: 'Checkout repository' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Login to ACR' uses: azure/docker-login@v1 @@ -92,7 +92,7 @@ jobs: gh pr merge $pr_number --merge --delete-branch - name: 'Checkout staging branch' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: 'staging' diff --git a/azure_jumpstart_ag/artifacts/workflows/pos-app-initial-images-build.yml b/azure_jumpstart_ag/artifacts/workflows/pos-app-initial-images-build.yml index 31b764be95..a3eb50fcfe 100644 --- a/azure_jumpstart_ag/artifacts/workflows/pos-app-initial-images-build.yml +++ b/azure_jumpstart_ag/artifacts/workflows/pos-app-initial-images-build.yml @@ -14,7 +14,7 @@ jobs: steps: # checkout the repo - name: 'Checkout repository' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Login to ACR' uses: azure/docker-login@v1 @@ -26,7 +26,7 @@ jobs: - name: 'Build and push pos v1.0 images' env: latest_tag: "v1.0" - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 25 retry_on: error @@ -45,7 +45,7 @@ jobs: - name: 'Build and push cloudSync v1.0 images' env: latest_tag: "v1.0" - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 25 retry_on: error @@ -64,7 +64,7 @@ jobs: - name: 'Build and push contosoAi v1.0 images' env: latest_tag: "v1.0" - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 25 retry_on: error @@ -83,7 +83,7 @@ jobs: - name: 'Build and push queue monitoring backend v1.0 images' env: latest_tag: "v1.0" - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 25 retry_on: error @@ -102,7 +102,7 @@ jobs: - name: 'Build and push queue monitoring frontend v1.0 images' env: latest_tag: "v1.0" - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 25 retry_on: error diff --git a/azure_jumpstart_ag/artifacts/workflows/update-files.yml b/azure_jumpstart_ag/artifacts/workflows/update-files.yml index 0ed2336fb2..4cf5217752 100644 --- a/azure_jumpstart_ag/artifacts/workflows/update-files.yml +++ b/azure_jumpstart_ag/artifacts/workflows/update-files.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Replace the correct ACR name continue-on-error: true diff --git a/azure_jumpstart_ag/.gitignore b/azure_jumpstart_ag/manufacturing/.gitignore similarity index 100% rename from azure_jumpstart_ag/.gitignore rename to azure_jumpstart_ag/manufacturing/.gitignore diff --git a/azure_jumpstart_ag/azure.yaml b/azure_jumpstart_ag/manufacturing/azure.yaml similarity index 100% rename from azure_jumpstart_ag/azure.yaml rename to azure_jumpstart_ag/manufacturing/azure.yaml diff --git a/azure_jumpstart_ag/manufacturing/bicep/clientVm/clientVm.bicep b/azure_jumpstart_ag/manufacturing/bicep/clientVm/clientVm.bicep new file mode 100644 index 0000000000..fb4816bd52 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/clientVm/clientVm.bicep @@ -0,0 +1,211 @@ +@description('The name of your Virtual Machine') +param vmName string = 'Ag-VM-Client' + +@description('Username for the Virtual Machine') +param windowsAdminUsername string = 'agora' + +@description('Password for Windows account. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. The value must be between 12 and 123 characters long') +@minLength(12) +@maxLength(123) +@secure() +param windowsAdminPassword string + +@description('The Windows version for the VM. This will pick a fully patched image of this given Windows version') +param windowsOSVersion string = '2022-datacenter-g2' + +@description('Location for all resources') +param location string = resourceGroup().location + + +@description('Name of the storage account') +param aioStorageAccountName string = 'aiostg${namingGuid}' + +@description('Resource tag for Jumpstart Agora') +param resourceTags object = { + Project: 'Jumpstart_Agora' +} + +@description('Resource Id of the subnet in the virtual network') +param subnetId string + +@description('Client id of the service principal') +param spnClientId string + +@description('Azure service principal object id') +param spnObjectId string + +@description('Client secret of the service principal') +@secure() +param spnClientSecret string +param spnAuthority string = environment().authentication.loginEndpoint + +@description('Tenant id of the service principal') +param spnTenantId string + +@description('Name for the environment Azure Log Analytics workspace') +param workspaceName string + +@description('The base URL used for accessing artifacts and automation artifacts.') +param templateBaseUrl string + +@description('Choice to deploy Bastion to connect to the client VM') +param deployBastion bool = false + +@description('Storage account used for staging file artifacts') +param storageAccountName string + +@description('The name of ESA container in Storage Account') +param stcontainerName string + +@description('The login server name of the Azure Container Registry') +param acrName string + +@description('The name of the Azure Data Explorer cluster') +param adxClusterName string + +@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('Target GitHub account') +param githubAccount string = 'microsoft' + +@description('Target GitHub branch') +param githubBranch string = 'main' + +//@description('GitHub Personal access token for the user account') +//@secure() +//param githubPAT string + +@description('Random GUID') +param namingGuid string + +@description('The custom location RPO ID') +param customLocationRPOID string + +@description('The agora industry to be deployed') +param industry string = 'retail' + +@description('The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing.') +param AKSEEPinnedSchemaVersion string = 'useLatest' + +var encodedPassword = base64(windowsAdminPassword) +var bastionName = 'Ag-Bastion' +var publicIpAddressName = deployBastion == false ? '${vmName}-PIP' : '${bastionName}-PIP' +var networkInterfaceName = '${vmName}-NIC' +var osDiskType = 'Premium_LRS' +var PublicIPNoBastion = { + id: publicIpAddress.id +} + +resource networkInterface 'Microsoft.Network/networkInterfaces@2023-02-01' = { + name: networkInterfaceName + location: location + tags: resourceTags + properties: { + ipConfigurations: [ + { + name: 'ipconfig1' + properties: { + subnet: { + id: subnetId + } + privateIPAllocationMethod: 'Dynamic' + publicIPAddress: deployBastion == false ? PublicIPNoBastion : null + } + } + ] + } +} + +resource publicIpAddress 'Microsoft.Network/publicIpAddresses@2023-02-01' = if (deployBastion == false) { + name: publicIpAddressName + location: location + tags: resourceTags + properties: { + publicIPAllocationMethod: 'Static' + publicIPAddressVersion: 'IPv4' + idleTimeoutInMinutes: 4 + } + sku: { + name: 'Basic' + } +} + +resource vm 'Microsoft.Compute/virtualMachines@2022-11-01' = { + name: vmName + location: location + tags: resourceTags + properties: { + hardwareProfile: { + vmSize: 'Standard_D32s_v5' + } + storageProfile: { + osDisk: { + name: '${vmName}-OSDisk' + caching: 'ReadWrite' + createOption: 'FromImage' + managedDisk: { + storageAccountType: osDiskType + } + diskSizeGB: 256 + } + imageReference: { + publisher: 'MicrosoftWindowsServer' + offer: 'WindowsServer' + sku: windowsOSVersion + version: 'latest' + } + dataDisks: [ + { + diskSizeGB: 1024 + lun: 0 + createOption: 'Empty' + caching: 'ReadWrite' + managedDisk: { + storageAccountType: 'Premium_LRS' + } + } + ] + } + networkProfile: { + networkInterfaces: [ + { + id: networkInterface.id + } + ] + } + osProfile: { + computerName: vmName + adminUsername: windowsAdminUsername + adminPassword: windowsAdminPassword + windowsConfiguration: { + provisionVMAgent: true + enableAutomaticUpdates: false + } + } + } +} + +resource vmBootstrap 'Microsoft.Compute/virtualMachines/extensions@2022-11-01' = { + parent: vm + name: 'Bootstrap' + location: location + tags: { + displayName: 'config-choco' + } + properties: { + publisher: 'Microsoft.Compute' + type: 'CustomScriptExtension' + typeHandlerVersion: '1.10' + autoUpgradeMinorVersion: true + protectedSettings: { + fileUris: [ + uri(templateBaseUrl, 'artifacts/PowerShell/Bootstrap.ps1') + ] + commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnObjectId ${spnObjectId} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -stagingStorageAccountName ${storageAccountName} -workspaceName ${workspaceName} -templateBaseUrl ${templateBaseUrl} -acrName ${acrName} -rdpPort ${rdpPort} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -namingGuid ${namingGuid} -adxClusterName ${adxClusterName} -customLocationRPOID ${customLocationRPOID} -industry ${industry} -aioStorageAccountName ${aioStorageAccountName} -stcontainerName ${stcontainerName} -AKSEEPinnedSchemaVersion ${AKSEEPinnedSchemaVersion}' + } + } +} + +output adminUsername string = windowsAdminUsername +output publicIP string = deployBastion == false ? concat(publicIpAddress.properties.ipAddress) : '' diff --git a/azure_jumpstart_ag/manufacturing/bicep/data/dataExplorer.bicep b/azure_jumpstart_ag/manufacturing/bicep/data/dataExplorer.bicep new file mode 100644 index 0000000000..9c6d824bd7 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/data/dataExplorer.bicep @@ -0,0 +1,109 @@ +@description('The name of the Azure Data Explorer cluster') +param adxClusterName string + +@description('The location of the Azure Data Explorer cluster') +param location string = resourceGroup().location + +@description('Resource tag for Jumpstart Agora') +param resourceTags object = { + Project: 'Jumpstart_azure_aio' +} + +@description('The name of the Azure Data Explorer cluster Sku') +param skuName string = 'Dev(No SLA)_Standard_E2a_v4' + +@description('The name of the Azure Data Explorer cluster Sku tier') +param skuTier string = 'Basic' + +@description('The name of the Event Hub') +param eventHubName string + +@description('The name of the Event Hub Namespace') +param eventHubNamespaceName string + +@description('The resource id of the Event Hub') +param eventHubResourceId string + +@description('The name of the Azure Data Explorer database') +param adxDBName string = 'manufacturing' + +@description('The name of the Azure Data Explorer Event Hub consumer group for staging data') +param stagingDataCGName string = 'mqttdataemulator' + +@description('# of nodes') +@minValue(1) +@maxValue(2) +param skuCapacity int = 1 + + +resource adxCluster 'Microsoft.Kusto/clusters@2023-05-02' = { + name: adxClusterName + location: location + tags: resourceTags + sku: { + name: skuName + tier: skuTier + capacity: skuCapacity + } + identity: { + type: 'SystemAssigned' + } +} + +resource manufacturingAdxDB 'Microsoft.Kusto/clusters/databases@2023-05-02' = { + parent: adxCluster + name: adxDBName + location: location + kind: 'ReadWrite' +} + +resource assemblylineScript 'Microsoft.Kusto/clusters/databases/scripts@2023-05-02' = { + name: 'assemblylineScript' + parent: manufacturingAdxDB + properties: { + continueOnErrors: false + forceUpdateTag: 'string' + scriptContent: loadTextContent('script.kql') + } +} + +resource azureEventHubsDataReceiverRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: 'a638d3c7-ab3a-418d-83e6-5f17a39d4fde' + scope: tenant() +} + +resource eventHub 'Microsoft.EventHub/namespaces/eventhubs@2023-01-01-preview' existing = { + name: '${eventHubNamespaceName}/${eventHubName}' +} + +resource eventHubRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('AzureEventHubsDataReceiverRole', adxCluster.id, eventHubResourceId) + scope: eventHub + properties: { + roleDefinitionId: azureEventHubsDataReceiverRole.id + principalId: adxCluster.identity.principalId + } +} + +resource stagingDataConnection 'Microsoft.Kusto/clusters/databases/dataConnections@2023-08-15' = { + name: 'stagingDataConnection' + kind: 'EventHub' + dependsOn: [ + assemblylineScript + ] + location: location + parent: manufacturingAdxDB + properties: { + managedIdentityResourceId: adxCluster.id + eventHubResourceId: eventHubResourceId + consumerGroup: stagingDataCGName + tableName: 'staging' + dataFormat: 'MULTIJSON' + mappingRuleName: 'staging_mapping' + eventSystemProperties: [] + compression: 'None' + databaseRouting: 'Single' + } +} + +output adxEndpoint string = adxCluster.properties.uri diff --git a/azure_jumpstart_ag/manufacturing/bicep/data/eventGrid.bicep b/azure_jumpstart_ag/manufacturing/bicep/data/eventGrid.bicep new file mode 100644 index 0000000000..d5d7e8b994 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/data/eventGrid.bicep @@ -0,0 +1,184 @@ +@description('The name of the EventGrid namespace') +param eventGridNamespaceName string = 'aioNamespace' + +@description('The location of the Azure Data Explorer cluster') +param location string = resourceGroup().location + +@maxLength(5) +@description('Random GUID') +param namingGuid string + +@description('EventGrid Sku') +param eventGridSku string = 'Standard' + +@description('EventGrid capacity') +param eventGridCapacity int = 1 + +@description('The name of the EventGrid client group') +param eventGridClientGroupName string = '$all' + +@description('The name of the EventGrid namespace') +param eventGridTopicSpaceName string = 'aiotopicSpace${namingGuid}' + +@description('The name of the EventGrid topic templates') +param eventGridTopicTemplates array = [ + '#' +] + +@description('Resource tag for Jumpstart Agora') +param resourceTags object = { + Project: 'Jumpstart_azure_aio' +} + +@description('The name of the EventGrid publisher binding name') +param publisherBindingName string = 'publisherBinding' + +@description('The name of the EventGrid subscription binding name') +param subscriberBindingName string = 'subscriberBindingName' + +@description('The name of the EventHub topic subscription') +param eventGridTopicSubscriptionName string = 'aioEventHubSubscription' + +@description('The name of the storage topic subscription') +param storageTopicSubscriptionName string = 'aioStorageSubscription' + +@description('The name of the EventGrid topic') +param eventGridTopicName string = 'aiotopic${namingGuid}' + +@description('The name of the EventGrid topic sku') +param eventGridTopicSku string = 'Basic' + +@description('The resource Id of the event hub') +param eventHubResourceId string + +@description('The resource Id of the storage account queue') +param storageAccountResourceId string + +@description('The name of the storage account queue') +param queueName string + +@description('The time to live of the storage account queue') +param queueTTL int = 604800 + +@description('The maximum number of client sessions per authentication name') +param maximumClientSessionsPerAuthenticationName int = 100 + +resource eventGrid 'Microsoft.EventGrid/namespaces@2023-12-15-preview' = { + name: eventGridNamespaceName + tags: resourceTags + location: location + sku: { + name: eventGridSku + capacity: eventGridCapacity + } + identity: { + type: 'SystemAssigned' + } + properties: { + topicSpacesConfiguration: { + state: 'Enabled' + maximumClientSessionsPerAuthenticationName: maximumClientSessionsPerAuthenticationName + clientAuthentication: { + alternativeAuthenticationNameSources: [ + 'ClientCertificateSubject' + ] + } + routeTopicResourceId: eventGridTopic.id + } + } +} + +resource eventGridTopicSpace 'Microsoft.EventGrid/namespaces/topicSpaces@2023-06-01-preview' = { + name: eventGridTopicSpaceName + parent: eventGrid + properties: { + topicTemplates: eventGridTopicTemplates + } +} + +resource eventGridPubisherBinding 'Microsoft.EventGrid/namespaces/permissionBindings@2023-06-01-preview' = { + name: publisherBindingName + parent: eventGrid + properties: { + clientGroupName: eventGridClientGroupName + permission: 'Publisher' + topicSpaceName: eventGridTopicSpace.name + } +} + +resource eventGridsubscriberBindingName 'Microsoft.EventGrid/namespaces/permissionBindings@2023-06-01-preview' = { + name: subscriberBindingName + parent: eventGrid + properties: { + clientGroupName: eventGridClientGroupName + permission: 'Subscriber' + topicSpaceName: eventGridTopicSpace.name + } +} + +resource eventGridTopic 'Microsoft.EventGrid/topics@2023-06-01-preview' = { + name: eventGridTopicName + location: location + tags: resourceTags + sku: { + name: eventGridTopicSku + } + identity: { + type: 'SystemAssigned' + } + properties: { + inputSchema: 'CloudEventSchemaV1_0' + } +} + + +resource eventHubTopicSubscription 'Microsoft.EventGrid/topics/eventSubscriptions@2023-06-01-preview' = { + name: eventGridTopicSubscriptionName + parent:eventGridTopic + properties: { + destination: { + endpointType: 'EventHub' + properties: { + resourceId: eventHubResourceId + } + } + filter: { + enableAdvancedFilteringOnArrays: true + } + eventDeliverySchema: 'CloudEventSchemaV1_0' + } +} + +resource storageTopicSubscription 'Microsoft.EventGrid/topics/eventSubscriptions@2023-06-01-preview' = { + name: storageTopicSubscriptionName + parent:eventGridTopic + properties: { + destination: { + endpointType: 'StorageQueue' + properties: { + resourceId: storageAccountResourceId + queueName: queueName + queueMessageTimeToLiveInSeconds: queueTTL + } + } + filter: { + enableAdvancedFilteringOnArrays: true + } + eventDeliverySchema: 'CloudEventSchemaV1_0' + } +} + +resource azureEventGridDataSenderRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: 'd5a91429-5739-47e2-a06b-3470a27159e7' + scope: tenant() +} + +resource eventGridTopicRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('azureEventGridDataSenderRole', eventGrid.id, eventGridTopic.id) + scope: eventGridTopic + properties: { + roleDefinitionId: azureEventGridDataSenderRole.id + principalId: eventGrid.identity.principalId + } +} + diff --git a/azure_jumpstart_ag/manufacturing/bicep/data/eventHub.bicep b/azure_jumpstart_ag/manufacturing/bicep/data/eventHub.bicep new file mode 100644 index 0000000000..9492b09cc4 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/data/eventHub.bicep @@ -0,0 +1,61 @@ +@description('The name of the EventHub namespace') +param eventHubNamespaceName string = 'aiohubns${uniqueString(resourceGroup().id)}' + +@description('The name of the EventHub') +param eventHubName string = 'aioEventHub' + +@description('EventHub Sku') +param eventHubSku string = 'Standard' + +@description('EventHub Tier') +param eventHubTier string = 'Standard' + +@description('EventHub capacity') +param eventHubCapacity int = 1 + +@description('Resource tag for Jumpstart Agora') +param resourceTags object = { + Project: 'Jumpstart_azure_aio' +} + +@description('The location of the Azure Data Explorer cluster') +param location string = resourceGroup().location + +@description('The name of the Azure Data Explorer Event Hub consumer group for mqttdataemulator') +param stagingDataCGName string = 'mqttdataemulator' + +resource eventHubNamespace 'Microsoft.EventHub/namespaces@2023-01-01-preview' = { + name: eventHubNamespaceName + tags: resourceTags + location: location + sku: { + name: eventHubSku + capacity: eventHubCapacity + tier: eventHubTier + } +} + +resource eventHub 'Microsoft.EventHub/namespaces/eventhubs@2023-01-01-preview' = { + name: eventHubName + parent: eventHubNamespace + properties: { + messageRetentionInDays: 1 + } +} + +resource eventHubAuthRule 'Microsoft.EventHub/namespaces/authorizationRules@2023-01-01-preview' = { + name: 'eventHubAuthRule' + parent: eventHubNamespace + properties: { + rights: [ + 'Listen' + ] + } +} + +resource weldingrobotCG 'Microsoft.EventHub/namespaces/eventhubs/consumergroups@2023-01-01-preview' = { + name: stagingDataCGName + parent: eventHub +} + +output eventHubResourceId string = eventHub.id diff --git a/azure_jumpstart_ag/manufacturing/bicep/data/keyVault.bicep b/azure_jumpstart_ag/manufacturing/bicep/data/keyVault.bicep new file mode 100644 index 0000000000..c0b7ad3428 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/data/keyVault.bicep @@ -0,0 +1,71 @@ +@description('Azure Key Vault name') +param akvNameSite1 string = 'aio-akv-01' + +@description('Azure Key Vault name') +param akvNameSite2 string = 'aio-akv-02' + +@description('Azure Key Vault location') +param location string = resourceGroup().location + +@description('Azure Key Vault SKU') +param akvSku string = 'standard' + +@description('Azure Key Vault tenant ID') +param tenantId string = subscription().tenantId + +@description('Secret name') +param aioPlaceHolder string = 'azure-iot-operations' + +@description('Secret value') +param aioPlaceHolderValue string = 'aioSecretValue' + +@description('Resource tag for Jumpstart Agora') +param resourceTags object = { + Project: 'Jumpstart_azure_aio' +} + +resource akv 'Microsoft.KeyVault/vaults@2023-02-01' = { + name: akvNameSite1 + location: location + tags: resourceTags + properties: { + sku: { + name: akvSku + family: 'A' + } + accessPolicies: [] + enableSoftDelete: false + tenantId: tenantId + } +} + +resource aioSecretPlaceholder 'Microsoft.KeyVault/vaults/secrets@2023-02-01' = { + name: aioPlaceHolder + parent: akv + properties: { + value: aioPlaceHolderValue + } +} + +resource akv2 'Microsoft.KeyVault/vaults@2023-02-01' = { + name: akvNameSite2 + location: location + tags: resourceTags + properties: { + sku: { + name: akvSku + family: 'A' + } + accessPolicies: [] + enableSoftDelete: false + tenantId: tenantId + } +} + +resource aioSecretPlaceholder2 'Microsoft.KeyVault/vaults/secrets@2023-02-01' = { + name: aioPlaceHolder + parent: akv2 + properties: { + value: aioPlaceHolderValue + } +} diff --git a/azure_jumpstart_ag/manufacturing/bicep/data/script.kql b/azure_jumpstart_ag/manufacturing/bicep/data/script.kql new file mode 100644 index 0000000000..941aa67dba --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/data/script.kql @@ -0,0 +1,212 @@ +// Create a landing table for manufacturing events +.create table assemblyline (date_time: datetime, plant_details: dynamic, shift: string, employees_on_shift: dynamic, cars_produced: dynamic, equipment_maintenance: dynamic, production_schedule: dynamic, actual_production: dynamic, equipment_telemetry: dynamic, performance_metrics: dynamic) + +// Create mapping from JSON ingestion to landing table +.create table assemblyline ingestion json mapping "assemblyline_mapping" +``` +[ + {"column":"date_time","path":"$['date_time']","datatype":""}, + {"column":"plant_details","path":"$['plant_details']","datatype":""}, + {"column":"shift","path":"$['shift']","datatype":""}, + {"column":"employees_on_shift","path":"$['employees_on_shift']","datatype":""}, + {"column":"cars_produced","path":"$['cars_produced']","datatype":""}, + {"column":"equipment_maintenance","path":"$['equipment_maintenance']","datatype":""}, + {"column":"production_schedule","path":"$['production_schedule']","datatype":""}, + {"column":"actual_production","path":"$['actual_production']","datatype":""}, + {"column":"equipment_telemetry","path":"$['equipment_telemetry']","datatype":""}, + {"column":"performance_metrics","path":"$['performance_metrics']","datatype":""} +] +``` + +// create staging table, ingest base64 encoded data received from MQTT +.create table staging (['id']: string, source: string, ['type']: string, data_base64: string, ['time']: dynamic, specversion: real, subject: string) + +.create table staging ingestion json mapping "staging_mapping" +``` +[ +{"column":"id","path":"$['id']","datatype":"string"}, +{"column":"source","path":"$['source']","datatype":"string"}, +{"column":"type","path":"$['type']","datatype":"string"}, +{"column":"data_base64","path":"$['data_base64']","datatype":""}, +{"column":"time","path":"$['time']","datatype":""}, +{"column":"specversion","path":"$['specversion']","datatype":"real"}, +{"column":"subject","path":"$['subject']","datatype":"string"}] +``` + +// Modify the ingestion batching policy to ingest data frequently +// THIS CONFIGURATION SHOULDN'T BE USED IN PRODUCTION: MaximumBatchingTimeSpan SHOULD BE AT LEAST 1 MINUTE +.alter table staging policy ingestionbatching "{'MaximumBatchingTimeSpan': '0:01:00', 'MaximumNumberOfItems': 10000}" + +// Create table to store data from base64 to json format +.create table weldingrobot (Timestamp: datetime, Heater_Outlet_Temp: real, Pump1_Flow_Totalizer: real, Pump2_Flow_Totalizer: real, Pump3_Flow_Totalizer: real, Pump1_Temperature_Flow: real, Pump2_Temperature_Flow: real, Pump3_Temperature_Flow: real, Pumps_Total_Flow: real, Pressure_Filter_Inlet: real, Pressure_Filter_Outlet: real, RobotPosition_J0: real, RobotPosition_J1: real, RobotPosition_J2: real, RobotPosition_J3: real, RobotPosition_J4: real, RobotPosition_J5: real, Tank_Level: real, Drive1_Current: real, Drive1_Frequency: int, Drive1_Speed: int, Drive1_Voltage: real, Drive2_Current: real, Drive2_Frequency: int, Drive2_Speed: int, Drive2_Voltage: real, Drive3_Current: real, Drive3_Frequency: int, Drive3_Speed: int, Drive3_Voltage: real, Cooler_Inlet_Temp: real, Cooler_Outlet_Temp: real, Dynamix_Ch1_Acceleration: real, Flow001: real, Pressure001: real, Pressure002: real, Heater_Inlet_Temp: real, Pump1_Conductivity: real, Valve_000_Pump1: boolean, Cooler_ON: boolean, Fan001_On: boolean, Heater_ON: boolean, Filter_Chg_Required: boolean, Filter_Reset: boolean, Filter_Override: boolean, UTC_Time: datetime, Current: real, Voltage: real, Temperature: real, Humidity: real, VacuumAlert: boolean, VacuumPressure: real, Oiltemperature: real, OiltemperatureTarget: real) + +// Create a table to store the data from the assemblybatteries topic +.create table assemblybatteries (Timestamp: datetime, MakeupArea: string, Line: string, Product: string, Process: string, Batch: int, CurrentShift: string, CurrentCellAssemblyPerMinutes: int, TargetCellAssemblyPerMinutes: int, StartTime: datetime, FinishTime: datetime, Waste: real, WasteReason: string, LostTime: string, LostTimeReason: string, LostTimeTimeCount: int, ScheduledBatteries: int, CompletedBatteries: int, ScheduledBatteriesPerHour: int, Temperature: real, ImpactTest: int, VibrationTest: int, CellTest: int, DownTime: int, Thruput: int, OverallEfficiency: int, Availability: int, Performance: int, Quality: int, PlannedProductionTime: int, ActualRuntime: int, UnplannedDowntime: int, PlannedDowntime: int, PlannedQuantity: int, ActualQuantity: int, RejectedQuantity: int, OEE_GoalbyPlant: real, OEE_Seattle: real, OEE_Detroit: real, OEE_Hannover: real, OEE_USA: real, OEE_Mexico: real, OEE_GoalbyProduct: real, OEE_BatteryA: real, OEE_BatteryB: real, OEE_BatteryC: real, OEE_GoalbyShift: real, OEE_MorningShift: real, OEE_DayShift: real, OEE_NightShift: real) + +// Create a function to parse the data from the assemblybatteries topic +.create-or-alter function Expand_assemblybatteries_Data() +{ + staging + | where subject == "topic/assemblybatteries" + | extend data = parse_json( base64_decode_tostring(data_base64) ) + | project + Timestamp = todatetime(data.data.Timestamp), + MakeupArea = tostring(data.data.MakeupArea), + Line = tostring(data.data.Line), + Product = tostring(data.data.Product), + Process = tostring(data.data.Process), + Batch = toint(data.data.Batch), + CurrentShift = tostring(data.data.CurrentShift), + CurrentCellAssemblyPerMinutes = toint(data.data.CurrentCellAssemblyPerMinutes), + TargetCellAssemblyPerMinutes = toint(data.data.TargetCellAssemblyPerMinutes), + StartTime = todatetime(data.data.StartTime), + FinishTime = todatetime(data.data.FinishTime), + Waste = toreal(data.data.Waste), + WasteReason = tostring(data.data.WasteReason), + LostTime = tostring(data.data.LostTime), + LostTimeReason = tostring(data.data.LostTimeReason), + LostTimeTimeCount = toint(data.data.LostTimeTimeCount), + ScheduledBatteries = toint(data.data.ScheduledBatteries), + CompletedBatteries = toint(data.data.CompletedBatteries), + ScheduledBatteriesPerHour = toint(data.data.ScheduledBatteriesPerHour), + Temperature = toreal(data.data.DoughTemperature), + ImpactTest = toint(data.data.ImpactTest), + VibrationTest = toint(data.data.VibrationTest), + CellTest = toint(data.data.CellTest), + DownTime = toint(data.data.DownTime), + Thruput = toint(data.data.Thruput), + OverallEfficiency = toint(data.data.OverallEfficiency), + Availability = toint(data.data.Availability), + Performance = toint(data.data.Performance), + Quality = toint(data.data.Quality), + PlannedProductionTime = toint(data.data.PlannedProductionTime), + ActualRuntime = toint(data.data.ActualRuntime), + UnplannedDowntime = toint(data.data.UnplannedDowntime), + PlannedDowntime = toint(data.data.PlannedDowntime), + PlannedQuantity = toint(data.data.PlannedQuantity), + ActualQuantity = toint(data.data.ActualQuantity), + RejectedQuantity = toint(data.data.RejectedQuantity), + OEE_GoalbyPlant = toreal(data.data.OEE_GoalbyPlant), + OEE_Seattle = toreal(data.data.OEE_Seattle), + OEE_Detroit = toreal(data.data.OEE_Detroit), + OEE_Hannover = toreal(data.data.OEE_Hannover), + OEE_USA = toreal(data.data.OEE_USA), + OEE_Mexico = toreal(data.data.OEE_Mexico), + OEE_GoalbyProduct = toreal(data.data.OEE_GoalbyProduct), + OEE_BatteryA = toreal(data.data.OEE_BatteryA), + OEE_BatteryB = toreal(data.data.OEE_BatteryB), + OEE_BatteryC = toreal(data.data.OEE_BatteryC), + OEE_GoalbyShift = toreal(data.data.OEE_GoalbyShift), + OEE_MorningShift = toreal(data.data.OEE_MorningShift), + OEE_DayShift = toreal(data.data.OEE_DayShift), + OEE_NightShift = toreal(data.data.OEE_NightShift) +} + +// Create policy +.alter table assemblybatteries policy update @'[{"Source": "staging", "Query": "Expand_assemblybatteries_Data()", "IsEnabled": "True"}]' + +// Create function to decode base64 to json format +.create-or-alter function Expand_weldingrobot_Data() +{ + staging + | where subject == "topic/weldingrobot" + | extend data = parse_json( base64_decode_tostring(data_base64) ) + | project + Timestamp = todatetime(data.data.Timestamp), + Heater_Outlet_Temp = toreal(data.data.Heater_Outlet_Temp), + Pump1_Flow_Totalizer = toreal(data.data.Pump1_Flow_Totalizer), + Pump2_Flow_Totalizer = toreal(data.data.Pump2_Flow_Totalizer), + Pump3_Flow_Totalizer = toreal(data.data.Pump3_Flow_Totalizer), + Pump1_Temperature_Flow = toreal(data.data.Pump1_Temperature_Flow), + Pump2_Temperature_Flow = toreal(data.data.Pump2_Temperature_Flow), + Pump3_Temperature_Flow = toreal(data.data.Pump3_Temperature_Flow), + Pumps_Total_Flow = toreal(data.data.Pumps_Total_Flow), + Pressure_Filter_Inlet = toreal(data.data.Pressure_Filter_Inlet), + Pressure_Filter_Outlet = toreal(data.data.Pressure_Filter_Outlet), + RobotPosition_J0 = toreal(data.data.RobotPosition_J0), + RobotPosition_J1 = toreal(data.data.RobotPosition_J1), + RobotPosition_J2 = toreal(data.data.RobotPosition_J2), + RobotPosition_J3 = toreal(data.data.RobotPosition_J3), + RobotPosition_J4 = toreal(data.data.RobotPosition_J4), + RobotPosition_J5 = toreal(data.data.RobotPosition_J5), + Tank_Level = toreal(data.data.Tank_Level), + Drive1_Current = toreal(data.data.Drive1_Current), + Drive1_Frequency = toint(data.data.Drive1_Frequency), + Drive1_Speed = toint(data.data.Drive1_Speed), + Drive1_Voltage = toreal(data.data.Drive1_Voltage), + Drive2_Current = toreal(data.data.Drive2_Current), + Drive2_Frequency = toint(data.data.Drive2_Frequency), + Drive2_Speed = toint(data.data.Drive2_Speed), + Drive2_Voltage = toreal(data.data.Drive2_Voltage), + Drive3_Current = toreal(data.data.Drive3_Current), + Drive3_Frequency = toint(data.data.Drive3_Frequency), + Drive3_Speed = toint(data.data.Drive3_Speed), + Drive3_Voltage = toreal(data.data.Drive3_Voltage), + Cooler_Inlet_Temp = toreal(data.data.Cooler_Inlet_Temp), + Cooler_Outlet_Temp = toreal(data.data.Cooler_Outlet_Temp), + Dynamix_Ch1_Acceleration = toreal(data.data.Dynamix_Ch1_Acceleration), + Flow001 = toreal(data.data.Flow001), + Pressure001 = toreal(data.data.Pressure001), + Pressure002 = toreal(data.data.Pressure002), + Heater_Inlet_Temp = toreal(data.data.Heater_Inlet_Temp), + Pump1_Conductivity = toreal(data.data.Pump1_Conductivity), + Valve_000_Pump1 = toboolean(data.data.Valve_000_Pump1), + Cooler_ON = toboolean(data.data.Cooler_ON), + Fan001_On = toboolean(data.data.Fan001_On), + Heater_ON = toboolean(data.data.Heater_ON), + Filter_Chg_Required = toboolean(data.data.Filter_Chg_Required), + Filter_Reset = toboolean(data.data.Filter_Reset), + Filter_Override = toboolean(data.data.Filter_Override), + UTC_Time = todatetime(data.data.UTC_Time), + Current = toreal(data.data.Current), + Voltage = toreal(data.data.Voltage), + Temperature = toreal(data.data.Temperature), + Humidity = toreal(data.data.Humidity), + VacuumAlert = toboolean(data.data.VacuumAlert), + VacuumPressure = toreal(data.data.VacuumPressure), + Oiltemperature = toreal(data.data.Oiltemperature), + OiltemperatureTarget = toreal(data.data.OiltemperatureTarget) +} + +// Create policy +.alter table weldingrobot policy update @'[{"Source": "staging", "Query": "Expand_weldingrobot_Data()", "IsEnabled": "True"}]' + +// Function extract assemblyline telemtry from staging table to assemblyline table +.create-or-alter function Expand_assemblyline_Data() { +let combinedData = ( + staging + | where subject == "topic/dataemulator" + | extend data = parse_json(data_base64) + | project + date_time = todatetime(data.date_time), + plant_details = data.plant_details, + shift = tostring(data.shift), + employees_on_shift = data.employees_on_shift, + cars_produced = data.cars_produced, + equipment_maintenance = data.equipment_maintenance, + production_schedule = data.production_schedule, + actual_production = data.actual_production, + equipment_telemetry = data.equipment_telemetry, + performance_metrics = data.performance_metrics + ) + | union ( + staging + | where subject == "topic/assemblyline" + | extend data = parse_json(base64_decode_tostring(data_base64)) + | project + date_time = todatetime(data.data.date_time), + plant_details = data.data.plant_details, + shift = tostring(data.data.shift), + employees_on_shift = data.data.employees_on_shift, + cars_produced = data.data.cars_produced, + equipment_maintenance = data.data.equipment_maintenance, + production_schedule = data.data.production_schedule, + actual_production = data.data.actual_production, + equipment_telemetry = data.data.equipment_telemetry, + performance_metrics = data.data.performance_metrics + ); + combinedData +} + +// Create policy +.alter table assemblyline policy update @'[{"Source": "staging", "Query": "Expand_assemblyline_Data()", "IsEnabled": "True"}]' diff --git a/azure_jumpstart_ag/manufacturing/bicep/kubernetes/acr.bicep b/azure_jumpstart_ag/manufacturing/bicep/kubernetes/acr.bicep new file mode 100644 index 0000000000..c864f1db59 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/kubernetes/acr.bicep @@ -0,0 +1,25 @@ +@description('The location of the Managed Cluster resource') +param location string = resourceGroup().location + +@description('Resource tag for Jumpstart Agora') +param resourceTags object = { + Project: 'Jumpstart_Agora' +} + +@description('Name of the Azure Container Registry') +param acrName string + +@description('Provide a tier of your Azure Container Registry.') +param acrSku string = 'Basic' + +resource acr 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' ={ + name: acrName + location: location + tags: resourceTags + sku: { + name: acrSku + } + properties: { + adminUserEnabled: true + } +} diff --git a/azure_jumpstart_ag/manufacturing/bicep/main.azd.bicep b/azure_jumpstart_ag/manufacturing/bicep/main.azd.bicep new file mode 100644 index 0000000000..f751cc8409 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/main.azd.bicep @@ -0,0 +1,238 @@ +targetScope = 'subscription' + +@description('Azure service principal client id') +param spnClientId string = '' + +@description('Azure service principal client secret') +@secure() +param spnClientSecret string = newGuid() + +@description('Azure AD tenant id for your service principal') +param spnTenantId string = '' + +@description('Azure service principal Object id') +param spnObjectId string = '' + +@minLength(1) +@maxLength(77) +@description('Prefix for resource group, i.e. {name}-rg') +param envName string = toLower(substring(newGuid(), 0, 5)) + +resource rg 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: '${envName}-rg' + location: location +} + +@description('Location for all resources') +param location string = '' + +@maxLength(5) +@description('Random GUID') +param namingGuid string = toLower(substring(newGuid(), 0, 5)) + +@description('Username for Windows account') +param windowsAdminUsername string = 'Agora' + +@description('Password for Windows account. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. The value must be between 12 and 123 characters long') +@minLength(12) +@maxLength(123) +@secure() +param windowsAdminPassword string + +@description('Name for your log analytics workspace') +param logAnalyticsWorkspaceName string = 'Ag-Workspace-${namingGuid}' + +@description('Target GitHub account') +param githubAccount string = 'microsoft' + +@description('Target GitHub branch') +param githubBranch string = 'main' + +@description('Choice to deploy Bastion to connect to the client VM') +param deployBastion bool = false + +@description('Name of the Cloud VNet') +param virtualNetworkNameCloud string = 'Ag-Vnet-Prod' + +@description('Name of the Staging AKS subnet in the cloud virtual network') +param subnetNameCloudAksStaging string = 'Ag-Subnet-Staging' + +@description('Name of the inner-loop AKS subnet in the cloud virtual network') +param subnetNameCloudAksInnerLoop string = 'Ag-Subnet-InnerLoop' + +@description('Name of the storage queue') +param storageQueueName string = 'aioqueue' + +@description('Name of the event hub') +param eventHubName string = 'aiohub${namingGuid}' + +@description('Name of the event hub namespace') +param eventHubNamespaceName string = 'aiohubns${namingGuid}' + +@description('Name of the event grid namespace') +param eventGridNamespaceName string = 'aioeventgridns${namingGuid}' + +@description('The name of the Key Vault for site 1') +param akvNameSite1 string = 'agakv1${namingGuid}' + +@description('The name of the Key Vault for site 2') +param akvNameSite2 string = 'agakv2${namingGuid}' + +@description('Name of the storage account') +param aioStorageAccountName string = 'aiostg${namingGuid}' + +@description('The name of ESA container in Storage Account') +param stcontainerName string = 'esacontainer' + +@description('The name of the Azure Data Explorer cluster') +param adxClusterName string = 'agadx${namingGuid}' + +@description('The custom location RPO ID') +param customLocationRPOID string = '' + +@minLength(5) +@maxLength(50) +@description('Name of the Azure Container Registry') +param acrName string = 'agacr${namingGuid}' + +@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('The agora industry to be deployed') +param industry string = 'manufacturing' + +var templateBaseUrl = 'https://raw.githubusercontent.com/${githubAccount}/azure_arc/${githubBranch}/azure_jumpstart_ag/' + +module mgmtArtifactsAndPolicyDeployment 'mgmt/mgmtArtifacts.bicep' = { + name: 'mgmtArtifactsAndPolicyDeployment' + scope: rg + params: { + workspaceName: logAnalyticsWorkspaceName + location: location + } +} + +module networkDeployment 'mgmt/network.bicep' = { + name: 'networkDeployment' + scope: rg + params: { + virtualNetworkNameCloud: virtualNetworkNameCloud + subnetNameCloudAksStaging: subnetNameCloudAksStaging + subnetNameCloudAksInnerLoop: subnetNameCloudAksInnerLoop + deployBastion: deployBastion + location: location + } +} + +module storageAccountDeployment 'mgmt/storageAccount.bicep' = { + name: 'storageAccountDeployment' + scope: rg + params: { + location: location + } +} + +module clientVmDeployment 'clientVm/clientVm.bicep' = { + name: 'clientVmDeployment' + scope: rg + params: { + windowsAdminUsername: windowsAdminUsername + windowsAdminPassword: windowsAdminPassword + spnClientId: spnClientId + spnClientSecret: spnClientSecret + spnObjectId: spnObjectId + spnTenantId: spnTenantId + workspaceName: logAnalyticsWorkspaceName + storageAccountName: storageAccountDeployment.outputs.storageAccountName + templateBaseUrl: templateBaseUrl + deployBastion: deployBastion + githubAccount: githubAccount + githubBranch: githubBranch + //githubPAT: githubPAT + location: location + subnetId: networkDeployment.outputs.innerLoopSubnetId + acrName: acrName + rdpPort: rdpPort + namingGuid: namingGuid + adxClusterName: adxClusterName + customLocationRPOID: customLocationRPOID + industry: industry + stcontainerName: stcontainerName + } +} + +module eventHub 'data/eventHub.bicep' = { + name: 'eventHubDeployment' + scope: rg + params: { + eventHubName: eventHubName + eventHubNamespaceName: eventHubNamespaceName + location: location + } +} + +module storageAccount 'storage/storageAccount.bicep' = { + name: 'aioStorageAccountDeployment' + scope: rg + params: { + storageAccountName: aioStorageAccountName + location: location + storageQueueName: storageQueueName + stcontainerName: stcontainerName + } +} + +module eventGrid 'data/eventGrid.bicep' = { + name: 'eventGridDeployment' + scope: rg + params: { + eventGridNamespaceName: eventGridNamespaceName + eventHubResourceId: eventHub.outputs.eventHubResourceId + queueName: storageQueueName + storageAccountResourceId: storageAccount.outputs.storageAccountId + namingGuid: namingGuid + location: location + } +} + +module keyVault 'data/keyVault.bicep' = { + name: 'keyVaultDeployment' + scope: rg + params: { + tenantId: spnTenantId + akvNameSite1: akvNameSite1 + akvNameSite2: akvNameSite2 + location: location + } +} + +module acr 'kubernetes/acr.bicep' = { + name: 'acrDeployment' + scope: rg + params: { + acrName: acrName + location: location + } +} + +module adx 'data/dataExplorer.bicep' = { + name: 'adxDeployment' + scope: rg + params: { + adxClusterName: adxClusterName + location: location + eventHubResourceId: eventHub.outputs.eventHubResourceId + eventHubName: eventHubName + eventHubNamespaceName: eventHubNamespaceName + } +} + +output AZURE_TENANT_ID string = tenant().tenantId +output AZURE_RESOURCE_GROUP string = rg.name + +output NAMING_GUID string = namingGuid +output RDP_PORT string = rdpPort + +output ADX_CLUSTER_NAME string = adxClusterName +output ACR_NAME string = acrName + diff --git a/azure_jumpstart_ag/manufacturing/bicep/main.azd.parameters.json b/azure_jumpstart_ag/manufacturing/bicep/main.azd.parameters.json new file mode 100644 index 0000000000..81d476bcf8 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/main.azd.parameters.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "envName": { + "value": "${AZURE_ENV_NAME}" + }, + "location": { + "value": "${AZURE_LOCATION}" + }, + "spnClientId": { + "value": "${SPN_CLIENT_ID}" + }, + "spnClientSecret": { + "value": "${SPN_CLIENT_SECRET}" + }, + "spnTenantId": { + "value": "${SPN_TENANT_ID}" + }, + "spnObjectId": { + "value": "${SPN_OBJECT_ID}" + }, + "windowsAdminUsername": { + "value": "${JS_WINDOWS_ADMIN_USERNAME}" + }, + "deployBastion": { + "value": "${JS_DEPLOY_BASTION}" + }, + "rdpPort": { + "value": "${JS_RDP_PORT}" + }, + "customLocationRPOID": { + "value": "${CUSTOM_LOCATION_RP_ID}" + } + } +} \ No newline at end of file diff --git a/azure_jumpstart_ag/manufacturing/bicep/main.bicep b/azure_jumpstart_ag/manufacturing/bicep/main.bicep new file mode 100644 index 0000000000..0945d98768 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/main.bicep @@ -0,0 +1,218 @@ +@description('Azure service principal client id') +param spnClientId string + +@description('Azure service principal client secret') +@secure() +param spnClientSecret string + +@description('Azure AD tenant id for your service principal') +param spnTenantId string + +@description('Azure service principal Object id') +param spnObjectId string + +@description('Location for all resources') +param location string = resourceGroup().location + +@maxLength(5) +@description('Random GUID') +param namingGuid string = toLower(substring(newGuid(), 0, 5)) + +@description('Username for Windows account') +param windowsAdminUsername string + +@description('Password for Windows account. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. The value must be between 12 and 123 characters long') +@minLength(12) +@maxLength(123) +@secure() +param windowsAdminPassword string + +@description('Name for your log analytics workspace') +param logAnalyticsWorkspaceName string = 'Ag-Workspace-${namingGuid}' + +@description('Target GitHub account') +param githubAccount string = 'microsoft' + +@description('Target GitHub branch') +param githubBranch string = 'main' + +@description('Choice to deploy Bastion to connect to the client VM') +param deployBastion bool = false + +@description('Name of the Cloud VNet') +param virtualNetworkNameCloud string = 'Ag-Vnet-Prod' + +@description('Name of the Staging AKS subnet in the cloud virtual network') +param subnetNameCloudAksStaging string = 'Ag-Subnet-Staging' + +@description('Name of the inner-loop AKS subnet in the cloud virtual network') +param subnetNameCloudAksInnerLoop string = 'Ag-Subnet-InnerLoop' + +@description('Name of the storage queue') +param storageQueueName string = 'aioqueue' + +@description('Name of the event hub') +param eventHubName string = 'aiohub${namingGuid}' + +@description('Name of the event hub namespace') +param eventHubNamespaceName string = 'aiohubns${namingGuid}' + +@description('Name of the event grid namespace') +param eventGridNamespaceName string = 'aioeventgridns${namingGuid}' + +@description('The name of the Key Vault for site 1') +param akvNameSite1 string = 'agakv1${namingGuid}' + +@description('The name of the Key Vault for site 2') +param akvNameSite2 string = 'agakv2${namingGuid}' + +@description('The name of the Azure Data Explorer Event Hub consumer group for assemblybatteries') +param stagingDataCGName string = 'mqttdataemulator' + +@description('Name of the storage account') +param aioStorageAccountName string = 'aiostg${namingGuid}' + +@description('The name of ESA container in Storage Account') +param stcontainerName string = 'esacontainer' + +@description('The name of the Azure Data Explorer cluster') +param adxClusterName string = 'agadx${namingGuid}' + +@description('The custom location RPO ID') +param customLocationRPOID string + +@minLength(5) +@maxLength(50) +@description('Name of the Azure Container Registry') +param acrName string = 'agacr${namingGuid}' + +@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('The agora industry to be deployed') +param industry string = 'manufacturing' + +@description('''The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing. +To pin a specific version, use the format '1.13'. To use the latest schema version, use 'useLatest'. +''') +param AKSEEPinnedSchemaVersion string = '1.13' + +var templateBaseUrl = 'https://raw.githubusercontent.com/${githubAccount}/azure_arc/${githubBranch}/azure_jumpstart_ag/' + +module mgmtArtifactsAndPolicyDeployment 'mgmt/mgmtArtifacts.bicep' = { + name: 'mgmtArtifactsAndPolicyDeployment' + params: { + workspaceName: logAnalyticsWorkspaceName + location: location + } +} + +module networkDeployment 'mgmt/network.bicep' = { + name: 'networkDeployment' + params: { + virtualNetworkNameCloud: virtualNetworkNameCloud + subnetNameCloudAksStaging: subnetNameCloudAksStaging + subnetNameCloudAksInnerLoop: subnetNameCloudAksInnerLoop + deployBastion: deployBastion + location: location + } +} + +module storageAccountDeployment 'mgmt/storageAccount.bicep' = { + name: 'storageAccountDeployment' + params: { + location: location + } +} + +module clientVmDeployment 'clientVm/clientVm.bicep' = { + name: 'clientVmDeployment' + params: { + windowsAdminUsername: windowsAdminUsername + windowsAdminPassword: windowsAdminPassword + spnClientId: spnClientId + spnClientSecret: spnClientSecret + spnObjectId: spnObjectId + spnTenantId: spnTenantId + workspaceName: logAnalyticsWorkspaceName + storageAccountName: storageAccountDeployment.outputs.storageAccountName + templateBaseUrl: templateBaseUrl + deployBastion: deployBastion + githubAccount: githubAccount + githubBranch: githubBranch + //githubPAT: githubPAT + location: location + subnetId: networkDeployment.outputs.innerLoopSubnetId + acrName: acrName + rdpPort: rdpPort + namingGuid: namingGuid + adxClusterName: adxClusterName + customLocationRPOID: customLocationRPOID + industry: industry + aioStorageAccountName: aioStorageAccountName + stcontainerName: stcontainerName + AKSEEPinnedSchemaVersion: AKSEEPinnedSchemaVersion + } +} + +module eventHub 'data/eventHub.bicep' = { + name: 'eventHubDeployment' + params: { + eventHubName: eventHubName + eventHubNamespaceName: eventHubNamespaceName + location: location + stagingDataCGName: stagingDataCGName + } +} + +module storageAccount 'storage/storageAccount.bicep' = { + name: 'aioStorageAccountDeployment' + params: { + storageAccountName: aioStorageAccountName + location: location + storageQueueName: storageQueueName + stcontainerName: stcontainerName + } +} + +module eventGrid 'data/eventGrid.bicep' = { + name: 'eventGridDeployment' + params: { + eventGridNamespaceName: eventGridNamespaceName + eventHubResourceId: eventHub.outputs.eventHubResourceId + queueName: storageQueueName + storageAccountResourceId: storageAccount.outputs.storageAccountId + namingGuid: namingGuid + location: location + } +} + +module keyVault 'data/keyVault.bicep' = { + name: 'keyVaultDeployment' + params: { + tenantId: spnTenantId + akvNameSite1: akvNameSite1 + akvNameSite2: akvNameSite2 + location: location + } +} + +module acr 'kubernetes/acr.bicep' = { + name: 'acrDeployment' + params: { + acrName: acrName + location: location + } +} + +module adx 'data/dataExplorer.bicep' = { + name: 'adxDeployment' + params: { + adxClusterName: adxClusterName + location: location + eventHubResourceId: eventHub.outputs.eventHubResourceId + eventHubName: eventHubName + eventHubNamespaceName: eventHubNamespaceName + stagingDataCGName: stagingDataCGName + } +} diff --git a/azure_jumpstart_ag/manufacturing/bicep/main.parameters.json b/azure_jumpstart_ag/manufacturing/bicep/main.parameters.json new file mode 100644 index 0000000000..c37a48de68 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/main.parameters.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "spnClientId": { + "value": "" + }, + "spnClientSecret": { + "value": "" + }, + "spnTenantId": { + "value": "" + }, + "spnObjectId": { + "value": "" + }, + "windowsAdminUsername": { + "value": "" + }, + "windowsAdminPassword": { + "value": "" + }, + "deployBastion": { + "value": false + }, + "customLocationRPOID": { + "value": "" + } + } +} diff --git a/azure_jumpstart_ag/manufacturing/bicep/mgmt/VMInsightsDCR.bicep b/azure_jumpstart_ag/manufacturing/bicep/mgmt/VMInsightsDCR.bicep new file mode 100644 index 0000000000..6e904cec50 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/mgmt/VMInsightsDCR.bicep @@ -0,0 +1,56 @@ +@description('This is the name of the AMA-VMI Data Collection Rule(DCR)') +@metadata({ displayName: 'Name of the Data Collection Rule(DCR)' }) +param DcrName string + +@description('Workspace Location.') +param WorkspaceLocation string + +@description('Workspace Resource ID.') +param WorkspaceResourceId string + +resource MSVMI_PerfandDa_Dcr 'Microsoft.Insights/dataCollectionRules@2021-04-01' = { + name: 'MSVMI-PerfandDa-${DcrName}' + location: WorkspaceLocation + properties: { + description: 'Data collection rule for VM Insights.' + dataSources: { + performanceCounters: [ + { + name: 'VMInsightsPerfCounters' + streams: ['Microsoft-InsightsMetrics'] + scheduledTransferPeriod: 'PT1M' + samplingFrequencyInSeconds: 60 + counterSpecifiers: ['\\VmInsights\\DetailedMetrics'] + } + ] + extensions: [ + { + streams: ['Microsoft-ServiceMap'] + extensionName: 'DependencyAgent' + extensionSettings: {} + name: 'DependencyAgentDataSource' + } + ] + } + destinations: { + logAnalytics: [ + { + workspaceResourceId: WorkspaceResourceId + name: 'VMInsightsPerf-Logs-Dest' + } + ] + } + dataFlows: [ + { + streams: ['Microsoft-InsightsMetrics'] + destinations: ['VMInsightsPerf-Logs-Dest'] + } + { + streams: ['Microsoft-ServiceMap'] + destinations: ['VMInsightsPerf-Logs-Dest'] + } + ] + } +} + +output id string = MSVMI_PerfandDa_Dcr.id diff --git a/azure_jumpstart_ag/manufacturing/bicep/mgmt/mgmtArtifacts.bicep b/azure_jumpstart_ag/manufacturing/bicep/mgmt/mgmtArtifacts.bicep new file mode 100644 index 0000000000..94d8e25876 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/mgmt/mgmtArtifacts.bicep @@ -0,0 +1,64 @@ +@description('Name for your log analytics workspace') +param workspaceName string + +@description('Azure Region to deploy the Log Analytics Workspace') +param location string = resourceGroup().location + +@description('Resource tag for Jumpstart Agora') +param resourceTags object = { + Project: 'Jumpstart_Agora' +} + +@description('SKU, leave default pergb2018') +param sku string = 'pergb2018' + +@description('Suffix of Data Collection Rule for VM Insights: MSVMI-PerfandDa-"suffix"') +param VMIDCRName string = 'Agora' + +var security = { + name: 'Security(${workspaceName})' + galleryName: 'Security' +} + +resource workspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { + name: workspaceName + location: location + tags: resourceTags + properties: { + sku: { + name: sku + } + } +} + +resource securityGallery 'Microsoft.OperationsManagement/solutions@2015-11-01-preview' = { + name: security.name + location: location + tags: resourceTags + properties: { + workspaceResourceId: workspace.id + } + plan: { + name: security.name + promotionCode: '' + product: 'OMSGallery/${security.galleryName}' + publisher: 'Microsoft' + } +} + +module policyDeploymentRGScope './policyAzureArcRGScope.bicep' = { + name: 'policyDeployment' + params: { + azureLocation: location + VMInsightsDCRId: VMI_DCR_Deployment.outputs.id + } +} + +module VMI_DCR_Deployment './VMInsightsDCR.bicep' = { + name: 'VMI-DCR-Deployment-${uniqueString(VMIDCRName)}' + params: { + DcrName: VMIDCRName + WorkspaceLocation: location + WorkspaceResourceId: workspace.id + } +} diff --git a/azure_jumpstart_ag/manufacturing/bicep/mgmt/network.bicep b/azure_jumpstart_ag/manufacturing/bicep/mgmt/network.bicep new file mode 100644 index 0000000000..dbf41bcbb4 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/mgmt/network.bicep @@ -0,0 +1,368 @@ +@description('Name of the Cloud VNet') +param virtualNetworkNameCloud string + +@description('Name of the Staging AKS subnet in the cloud virtual network') +param subnetNameCloudAksStaging string + +@description('Name of the inner-loop AKS subnet in the cloud virtual network') +param subnetNameCloudAksInnerLoop string + +@description('Azure Region to deploy the Log Analytics Workspace') +param location string = resourceGroup().location + +@description('Resource tag for Jumpstart Agora') +param resourceTags object = { + Project: 'Jumpstart_Agora' +} + +@description('Choice to deploy Bastion to connect to the client VM') +param deployBastion bool = false + +@description('Name of the prod Network Security Group') +param networkSecurityGroupNameCloud string = 'Ag-NSG-Prod' + +@description('Name of the Bastion Network Security Group') +param bastionNetworkSecurityGroupName string = 'Ag-NSG-Bastion' + +var addressPrefixCloud = '10.16.0.0/16' +var subnetAddressPrefixAksDev = '10.16.80.0/21' +var subnetAddressPrefixInnerLoop = '10.16.64.0/21' +var bastionSubnetIpPrefix = '10.16.3.64/26' +var bastionSubnetName = 'AzureBastionSubnet' +var bastionSubnetRef = '${cloudVirtualNetwork.id}/subnets/${bastionSubnetName}' +var bastionName = 'Ag-Bastion' +var bastionPublicIpAddressName = '${bastionName}-PIP' + + +var bastionSubnet = [ + { + name: 'AzureBastionSubnet' + properties: { + addressPrefix: bastionSubnetIpPrefix + networkSecurityGroup: { + id: bastionNetworkSecurityGroup.id + } + } + } +] +var cloudAKSDevSubnet = [ + { + name: subnetNameCloudAksStaging + properties: { + addressPrefix: subnetAddressPrefixAksDev + privateEndpointNetworkPolicies: 'Enabled' + privateLinkServiceNetworkPolicies: 'Enabled' + networkSecurityGroup: { + id: networkSecurityGroupCloud.id + } + } + } +] + +var cloudAKSInnerLoopSubnet = [ + { + name: subnetNameCloudAksInnerLoop + properties: { + addressPrefix: subnetAddressPrefixInnerLoop + privateEndpointNetworkPolicies: 'Enabled' + privateLinkServiceNetworkPolicies: 'Enabled' + networkSecurityGroup: { + id: networkSecurityGroupCloud.id + } + } + } +] + +resource cloudVirtualNetwork 'Microsoft.Network/virtualNetworks@2022-07-01' = { + name: virtualNetworkNameCloud + location: location + tags: resourceTags + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefixCloud + ] + } + subnets: (deployBastion == false) ? union (cloudAKSDevSubnet,cloudAKSInnerLoopSubnet) : union(cloudAKSDevSubnet,cloudAKSInnerLoopSubnet,bastionSubnet) + } +} + +resource publicIpAddress 'Microsoft.Network/publicIPAddresses@2023-02-01' = if (deployBastion == true) { + name: bastionPublicIpAddressName + location: location + tags: resourceTags + properties: { + publicIPAllocationMethod: 'Static' + publicIPAddressVersion: 'IPv4' + idleTimeoutInMinutes: 4 + } + sku: { + name: 'Standard' + } +} + +resource networkSecurityGroupCloud 'Microsoft.Network/networkSecurityGroups@2023-02-01' = { + name: networkSecurityGroupNameCloud + location: location + tags: resourceTags + properties: { + securityRules: [ + { + name: 'allow_k8s_80' + properties: { + priority: 1003 + protocol: 'TCP' + access: 'Allow' + direction: 'Inbound' + sourceAddressPrefix: '*' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '80' + } + } + { + name: 'allow_k8s_8080' + properties: { + priority: 1004 + protocol: 'TCP' + access: 'Allow' + direction: 'Inbound' + sourceAddressPrefix: '*' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '8080' + } + } + { + name: 'allow_k8s_443' + properties: { + priority: 1005 + protocol: 'TCP' + access: 'Allow' + direction: 'Inbound' + sourceAddressPrefix: '*' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '443' + } + } + { + name: 'allow_pos_5000' + properties: { + priority: 1006 + protocol: 'TCP' + access: 'Allow' + direction: 'Inbound' + sourceAddressPrefix: '*' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '5000' + } + } + { + name: 'allow_pos_81' + properties: { + priority: 1007 + protocol: 'TCP' + access: 'Allow' + direction: 'Inbound' + sourceAddressPrefix: '*' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '81' + } + } + { + name: 'allow_prometheus_9090' + properties: { + priority: 1008 + protocol: 'TCP' + access: 'Allow' + direction: 'Inbound' + sourceAddressPrefix: '*' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '9090' + } + } + { + name: 'allow_MQ_8883' + properties: { + priority: 1009 + protocol: 'TCP' + access: 'Allow' + direction: 'Inbound' + sourceAddressPrefix: '*' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '8883' + } + } + { + name: 'allow_MQ_1883' + properties: { + priority: 1010 + protocol: 'TCP' + access: 'Allow' + direction: 'Inbound' + sourceAddressPrefix: '*' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '1883' + } + } + ] + } +} + +resource bastionNetworkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2023-02-01' = if (deployBastion == true) { + name: bastionNetworkSecurityGroupName + location: location + tags: resourceTags + properties: { + securityRules: [ + { + name: 'bastion_allow_https_inbound' + properties: { + priority: 1010 + protocol: 'TCP' + access: 'Allow' + direction: 'Inbound' + sourceAddressPrefix: 'Internet' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '443' + } + } + { + name: 'bastion_allow_gateway_manager_inbound' + properties: { + priority: 1011 + protocol: 'TCP' + access: 'Allow' + direction: 'Inbound' + sourceAddressPrefix: 'GatewayManager' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '443' + } + } + { + name: 'bastion_allow_load_balancer_inbound' + properties: { + priority: 1012 + protocol: 'TCP' + access: 'Allow' + direction: 'Inbound' + sourceAddressPrefix: 'AzureLoadBalancer' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '443' + } + } + { + name: 'bastion_allow_host_comms' + properties: { + priority: 1013 + protocol: '*' + access: 'Allow' + direction: 'Inbound' + sourceAddressPrefix: 'VirtualNetwork' + sourcePortRange: '*' + destinationAddressPrefix: 'VirtualNetwork' + destinationPortRanges: [ + '8080' + '5701' + ] + } + } + { + name: 'bastion_allow_ssh_rdp_outbound' + properties: { + priority: 1014 + protocol: '*' + access: 'Allow' + direction: 'Outbound' + sourceAddressPrefix: '*' + sourcePortRange: '*' + destinationAddressPrefix: 'VirtualNetwork' + destinationPortRanges: [ + '22' + '3389' + ] + } + } + { + name: 'bastion_allow_azure_cloud_outbound' + properties: { + priority: 1015 + protocol: 'TCP' + access: 'Allow' + direction: 'Outbound' + sourceAddressPrefix: '*' + sourcePortRange: '*' + destinationAddressPrefix: 'AzureCloud' + destinationPortRange: '443' + } + } + { + name: 'bastion_allow_bastion_comms' + properties: { + priority: 1016 + protocol: '*' + access: 'Allow' + direction: 'Outbound' + sourceAddressPrefix: 'VirtualNetwork' + sourcePortRange: '*' + destinationAddressPrefix: 'VirtualNetwork' + destinationPortRanges: [ + '8080' + '5701' + ] + } + } + { + name: 'bastion_allow_get_session_info' + properties: { + priority: 1017 + protocol: '*' + access: 'Allow' + direction: 'Outbound' + sourceAddressPrefix: '*' + sourcePortRange: '*' + destinationAddressPrefix: 'Internet' + destinationPortRanges: [ + '80' + '443' + ] + } + } + ] + } +} + +resource bastionHost 'Microsoft.Network/bastionHosts@2023-02-01' = if (deployBastion == true) { + name: bastionName + location: location + tags: resourceTags + properties: { + ipConfigurations: [ + { + name: 'IpConf' + properties: { + publicIPAddress: { + id: publicIpAddress.id + } + subnet: { + id: bastionSubnetRef + } + } + } + ] + } +} + +output vnetId string = cloudVirtualNetwork.id +output devSubnetId string = cloudVirtualNetwork.properties.subnets[0].id +output innerLoopSubnetId string = cloudVirtualNetwork.properties.subnets[1].id +output virtualNetworkNameCloud string = cloudVirtualNetwork.name diff --git a/azure_jumpstart_ag/manufacturing/bicep/mgmt/policyAzureArcRGScope.bicep b/azure_jumpstart_ag/manufacturing/bicep/mgmt/policyAzureArcRGScope.bicep new file mode 100644 index 0000000000..7f550c7418 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/mgmt/policyAzureArcRGScope.bicep @@ -0,0 +1,125 @@ +@description('Location of your Azure resources') +param azureLocation string + +@description('Resource ID of Data Collection Rule for VM Insights') +param VMInsightsDCRId string + +var policies = [ + { + name: '(Ag) Enable Azure Monitor for Hybrid VMs with AMA' + definitionId: '/providers/Microsoft.Authorization/policySetDefinitions/2b00397d-c309-49c4-aa5a-f0b2c5bc6321' + roleDefinition: [ + '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293' + '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/cd570a14-e51a-42ad-bac8-bafd67325302' + '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa' + ] + scope: resourceGroup().id + parameters: { + dcrResourceId: { + value: VMInsightsDCRId + } + enableProcessesAndDependencies: { + value: true + } + } + } + { + name: '(Ag) Deploy Azure Security agent on Windows Arc machines' + definitionId: '/providers/Microsoft.Authorization/policyDefinitions/d01f3018-de9f-4d75-8dae-d12c1875da9f' + roleDefinition: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293' + parameters: {} + } + { + name: '(Ag) Deploy Azure Security agent on Linux Arc machines' + definitionId: '/providers/Microsoft.Authorization/policyDefinitions/2f47ec78-4301-4655-b78e-b29377030cdc' + roleDefinition: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293' + parameters: {} + } + { + name: '(Ag) Deploy MDE agent on Windows Arc machines' + definitionId: '/providers/Microsoft.Authorization/policyDefinitions/37c043a6-6d64-656d-6465-b362dfeb354a' + roleDefinition: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + parameters: {} + } + { + name: '(Ag) Deploy MDE agent on Linux Arc machines' + definitionId: '/providers/Microsoft.Authorization/policyDefinitions/4eb909e7-6d64-656d-6465-2eeb297a1625' + roleDefinition: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + parameters: {} + } +] + +resource policies_name 'Microsoft.Authorization/policyAssignments@2022-06-01' = [for item in policies: { + name: item.name + location: azureLocation + identity: { + type: 'SystemAssigned' + } + properties: { + policyDefinitionId: item.definitionId + parameters: item.parameters + } +}] + +resource policy_AMA_role_0 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid( policies[0].name, policies[0].roleDefinition[0],resourceGroup().id) + properties: { + roleDefinitionId: policies[0].roleDefinition[0] + principalId: policies_name[0].identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource policy_AMA_role_1 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid( policies[0].name, policies[0].roleDefinition[1],resourceGroup().id) + properties: { + roleDefinitionId: policies[0].roleDefinition[1] + principalId: policies_name[0].identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource policy_AMA_role_2 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid( policies[0].name, policies[0].roleDefinition[2],resourceGroup().id) + properties: { + roleDefinitionId: policies[0].roleDefinition[2] + principalId: policies_name[0].identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource policy_arc_windows_azure_security_agent 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid( policies[1].name, policies[1].roleDefinition,resourceGroup().id) + properties: { + roleDefinitionId: policies[1].roleDefinition + principalId: policies_name[1].identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource policy_arc_linux_azure_security_agent 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid( policies[2].name, policies[2].roleDefinition,resourceGroup().id) + properties: { + roleDefinitionId: policies[2].roleDefinition + principalId: policies_name[2].identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource policy_arc_windows_mde 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid( policies[3].name, policies[3].roleDefinition,resourceGroup().id) + properties: { + roleDefinitionId: policies[3].roleDefinition + principalId: policies_name[3].identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource policy_arc_linux_mde 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid( policies[4].name, policies[4].roleDefinition,resourceGroup().id) + properties: { + roleDefinitionId: policies[4].roleDefinition + principalId: policies_name[4].identity.principalId + principalType: 'ServicePrincipal' + } +} diff --git a/azure_jumpstart_ag/bicep/mgmt/storageAccount.bicep b/azure_jumpstart_ag/manufacturing/bicep/mgmt/storageAccount.bicep similarity index 100% rename from azure_jumpstart_ag/bicep/mgmt/storageAccount.bicep rename to azure_jumpstart_ag/manufacturing/bicep/mgmt/storageAccount.bicep diff --git a/azure_jumpstart_ag/manufacturing/bicep/storage/storageAccount.bicep b/azure_jumpstart_ag/manufacturing/bicep/storage/storageAccount.bicep new file mode 100644 index 0000000000..35d095bce9 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/bicep/storage/storageAccount.bicep @@ -0,0 +1,48 @@ +@description('Storage account name') +param storageAccountName string + +@description('Storage account location') +param location string = resourceGroup().location + +@description('Storage account kind') +param kind string = 'StorageV2' + +@description('Storage account sku') +param skuName string = 'Standard_LRS' + +param storageQueueName string = 'aioQueue' + +@description('The name of ESA container in Storage Account') +param stcontainerName string + +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { + name: storageAccountName + location: location + kind: kind + sku: { + name: skuName + } + properties: { + supportsHttpsTrafficOnly: true + } +} + +resource storageQueueServices 'Microsoft.Storage/storageAccounts/queueServices@2023-01-01' = { + parent: storageAccount + name: 'default' +} + +resource storageQueue 'Microsoft.Storage/storageAccounts/queueServices/queues@2023-01-01' = { + parent: storageQueueServices + name: storageQueueName +} + +resource storageAccountName_default_container 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-04-01' = { + name: '${storageAccountName}/default/${stcontainerName}' + dependsOn: [ + storageAccount + ] +} + +output queueName string = storageQueueName +output storageAccountId string = storageAccount.id diff --git a/azure_jumpstart_ag/manufacturing/scripts/postprovision.ps1 b/azure_jumpstart_ag/manufacturing/scripts/postprovision.ps1 new file mode 100644 index 0000000000..2ab82ed85f --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/scripts/postprovision.ps1 @@ -0,0 +1,86 @@ +if ($null -ne $env:AZURE_RESOURCE_GROUP){ + $resourceGroup = $env:AZURE_RESOURCE_GROUP + $adxClusterName = $env:ADX_CLUSTER_NAME + Select-AzSubscription -SubscriptionId $env:AZURE_SUBSCRIPTION_ID | out-null + $rdpPort = $env:JS_RDP_PORT + $deployBastion = $env:JS_DEPLOY_BASTION +} + +######################################################################## +# ADX Dashboards +######################################################################## + +Write-Host "Importing Azure Data Explorer dashboards..." + +# Get the ADX/Kusto cluster info +$kustoCluster = Get-AzKustoCluster -ResourceGroupName $resourceGroup -Name $adxClusterName +$adxEndPoint = $kustoCluster.Uri + +# Update the dashboards files with the new ADX cluster name and URI +$templateBaseUrl = "https://raw.githubusercontent.com/microsoft/azure_arc/main/azure_jumpstart_ag/" +$ordersDashboardBody = (Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/adx_dashboards/adx-dashboard-orders-payload.json").Content -replace '{{ADX_CLUSTER_URI}}', $adxEndPoint -replace '{{ADX_CLUSTER_NAME}}', $adxClusterName +$iotSensorsDashboardBody = (Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/adx_dashboards/adx-dashboard-iotsensor-payload.json") -replace '{{ADX_CLUSTER_URI}}', $adxEndPoint -replace '{{ADX_CLUSTER_NAME}}', $adxClusterName + +# Get access token to make REST API call to Azure Data Explorer Dashabord API. Replace double quotes surrounding access token +$token = (az account get-access-token --scope "https://rtd-metadata.azurewebsites.net/user_impersonation openid profile offline_access" --query "accessToken") -replace "`"", "" + +# Prepare authorization header with access token +$httpHeaders = @{"Authorization" = "Bearer $token"; "Content-Type" = "application/json" } + +# Make REST API call to the dashboard endpoint. +$dashboardApi = "https://dashboards.kusto.windows.net/dashboards" + +# Import orders dashboard report +$httpResponse = Invoke-WebRequest -Method Post -Uri $dashboardApi -Body $ordersDashboardBody -Headers $httpHeaders +if ($httpResponse.StatusCode -ne 200){ + Write-Host "ERROR: Failed import orders dashboard report into Azure Data Explorer" -ForegroundColor Red +} + +# Import IoT Sensor dashboard report +$httpResponse = Invoke-WebRequest -Method Post -Uri $dashboardApi -Body $iotSensorsDashboardBody -Headers $httpHeaders +if ($httpResponse.StatusCode -ne 200){ + Write-Host "ERROR: Failed import IoT Sensor dashboard report into Azure Data Explorer" -ForegroundColor Red +} + + +######################################################################## +# RDP Port +######################################################################## + +# Configure NSG Rule for RDP (if needed) +If ($rdpPort -ne "3389" -and !$deployBastion) { + + Write-Host "Configuring NSG Rule for RDP..." + $nsg = Get-AzNetworkSecurityGroup -ResourceGroupName $resourceGroup -Name Ag-NSG-Prod + + Add-AzNetworkSecurityRuleConfig ` + -NetworkSecurityGroup $nsg ` + -Name "RDP-$rdpPort" ` + -Description "Allow RDP" ` + -Access Allow ` + -Protocol Tcp ` + -Direction Inbound ` + -Priority 100 ` + -SourceAddressPrefix * ` + -SourcePortRange * ` + -DestinationAddressPrefix * ` + -DestinationPortRange $rdpPort ` + | Out-Null + + Set-AzNetworkSecurityGroup -NetworkSecurityGroup $nsg | Out-Null + # az network nsg rule create -g $resourceGroup --nsg-name Ag-NSG-Prod --name "RDC-$rdpPort" --priority 100 --source-address-prefixes * --destination-port-ranges $rdpPort --access Allow --protocol Tcp +} + + +# Client VM IP address +if(!$deployBastion){ + $ip = (Get-AzPublicIpAddress -ResourceGroupName $resourceGroup -Name "Ag-VM-Client-PIP").IpAddress + Write-Host "You can now connect to the client VM using the following command: " -NoNewline + Write-Host "mstsc /v:$($ip):$($rdpPort)" -ForegroundColor Green -BackgroundColor Black + Write-Host "Remember to use the Windows admin user name [$env:JS_WINDOWS_ADMIN_USERNAME] and the password you specified." +}else{ + Write-Host "You can now connect to the client VM using the Azure Bastion service." -ForegroundColor Green + Write-Host "Remember to use the Windows admin user name [$env:JS_WINDOWS_ADMIN_USERNAME] and the password you specified." +} + + diff --git a/azure_jumpstart_ag/manufacturing/scripts/predown.ps1 b/azure_jumpstart_ag/manufacturing/scripts/predown.ps1 new file mode 100644 index 0000000000..6c23a3078e --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/scripts/predown.ps1 @@ -0,0 +1,6 @@ +######################################################################## +# Delete service principal +######################################################################## +$spnObjectId = $env:SPN_OBJECT_ID +Remove-AzRoleAssignment -ObjectId $spnObjectId -RoleDefinitionName "Owner" +Remove-AzADServicePrincipal -ObjectId $spnObjectId \ No newline at end of file diff --git a/azure_jumpstart_ag/manufacturing/scripts/preprovision.ps1 b/azure_jumpstart_ag/manufacturing/scripts/preprovision.ps1 new file mode 100644 index 0000000000..4ff2ec9955 --- /dev/null +++ b/azure_jumpstart_ag/manufacturing/scripts/preprovision.ps1 @@ -0,0 +1,227 @@ +######################################################################## +# Connect to Azure +######################################################################## + +Write-Host "Connecting to Azure..." + +# Install Azure module if not already installed +if (-not (Get-Command -Name Get-AzContext)) { + Write-Host "Installing Azure module..." + Install-Module -Name Az -AllowClobber -Scope CurrentUser -ErrorAction Stop +} + +# If not signed in, run the Connect-AzAccount cmdlet +if (-not (Get-AzContext)) { + Write-Host "Logging in to Azure..." + If (-not (Connect-AzAccount -SubscriptionId $env:AZURE_SUBSCRIPTION_ID -ErrorAction Stop)){ + Throw "Unable to login to Azure. Please check your credentials and try again." + } +} + +# Write-Host "Getting Azure Tenant Id..." +$tenantId = (Get-AzSubscription -SubscriptionId $env:AZURE_SUBSCRIPTION_ID).TenantId + +# Write-Host "Setting Azure context..." +$context = Set-AzContext -SubscriptionId $env:AZURE_SUBSCRIPTION_ID -Tenant $tenantId -ErrorAction Stop + +# Write-Host "Setting az subscription..." +$azLogin = az account set --subscription $env:AZURE_SUBSCRIPTION_ID + + +######################################################################## +# Check for available capacity in region +######################################################################## +#region Functions +Function Get-AzAvailableCores ($location, $skuFriendlyNames, $minCores = 0) { + # using az command because there is currently a bug in various versions of PowerShell that affects Get-AzVMUsage + $usage = (az vm list-usage --location $location --output json --only-show-errors) | ConvertFrom-Json + + $usage = $usage | + Where-Object {$_.localname -match $skuFriendlyNames} + + $enhanced = $usage | + ForEach-Object { + $_ | Add-Member -MemberType NoteProperty -Name available -Value 0 -Force -PassThru + $_.available = $_.limit - $_.currentValue + } + + $enhanced = $enhanced | + ForEach-Object { + $_ | Add-Member -MemberType NoteProperty -Name usableLocation -Value $false -Force -PassThru + If ($_.available -ge $minCores) { + $_.usableLocation = $true + } + else { + $_.usableLocation = $false + } + } + + $enhanced + +} + +Function Get-AzAvailableLocations ($location, $skuFriendlyNames, $minCores = 0) { + $allLocations = get-AzLocation + $geographyGroup = ($allLocations | Where-Object {$_.location -eq $location}).GeographyGroup + $locations = $allLocations | Where-Object { ` + $_.GeographyGroup -eq $geographyGroup ` + -and $_.Location -ne $location ` + -and $_.RegionCategory -eq "Recommended" ` + -and $_.PhysicalLocation -ne "" + } + + $usableLocations = $locations | + ForEach-Object { + $available = Get-AzAvailableCores -location $_.location -skuFriendlyNames $skuFriendlyNames -minCores $minCores | + Where-Object {$_.localName -ne "Total Regional vCPUs"} + If ($available.usableLocation) { + $_ | Add-Member -MemberType NoteProperty -Name TotalCores -Value $available.limit -Force + $_ | Add-Member -MemberType NoteProperty -Name AvailableCores -Value $available.available -Force + $_ | Add-Member -MemberType NoteProperty -Name usableLocation -Value $available.usableLocation -Force -PassThru + } + } + + $usableLocations +} + +Function Get-AzAvailablePublicIpAddress ($location, $subscriptionId, $minPublicIP = 0) { + + $accessToken = az account get-access-token --query accessToken -o tsv + $headers = @{ + "Authorization" = "Bearer $accessToken" + } + + $uri = "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/locations/$location/usages?api-version=2023-02-01" + + $publicIpCount = (Get-AzPublicIpAddress | where-object {$_.location -eq $location} | measure-object).count + $response = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get + + $limit = ($response.value | where-object { $_.name.value -eq "PublicIPAddresses"}).limit + + $availableIP = $limit - $publicIpCount + + $availableIP + +} + +#endregion Functions + +$location = $env:AZURE_LOCATION +$subscriptionId = $env:AZURE_SUBSCRIPTION_ID +$minCores = 32 +$minPublicIP = 10 +$skuFriendlyNames = "Standard DSv5 Family vCPUs|Total Regional vCPUs" + +Write-Host "`nChecking for available capacity in $location region..." + +$available = Get-AzAvailableCores -location $location -skuFriendlyNames $skuFriendlyNames -minCores $minCores + +If ($available.usableLocation -contains $false) { + Write-Host "`n`u{274C} There is not enough VM capacity in the $location region to deploy the Jumpstart environment." -ForegroundColor Red + + Write-Host "`nChecking other regions in the same geography with enough capacity ($minCores cores)...`n" + + $locations = Get-AzAvailableLocations -location $location -skuFriendlyNames $skuFriendlyNames -minCores $minCores | + Format-Table Location, DisplayName, TotalCores, AvailableCores, UsableLocation -AutoSize | Out-String + + Write-Host $locations + + Write-Host "Please run ``azd env --new`` to create a new environment and select the new location.`n" + + $message = "Not enough capacity in $location region." + Throw $message + +} else { + $availableIP = Get-AzAvailablePublicIpAddress -location $location -subscriptionId $subscriptionId -minPublicIP $minPublicIP + + If ($availableIP -le $minPublicIP) { + $requiredIp = $minPublicIP - $availableIP + Write-Host "`n`u{274C} There is not enough Public IP in the $location region to deploy the Jumpstart environment. Need addtional $requiredIp Public IP." -ForegroundColor Red + + $message = "Not enough capacity in $location region." + Throw $message + } else { + Write-Host "`n`u{2705} There is enough VM and Public IP capacity in the $location region to deploy the Jumpstart environment.`n" + } +} + +######################################################################## +# Get Windows Admin Username and Password +######################################################################## +$JS_WINDOWS_ADMIN_USERNAME = 'agora' +if ($promptOutput = Read-Host "Enter the Windows Admin Username [$JS_WINDOWS_ADMIN_USERNAME]") { $JS_WINDOWS_ADMIN_USERNAME = $promptOutput } + +# set the env variable +azd env set JS_WINDOWS_ADMIN_USERNAME -- $JS_WINDOWS_ADMIN_USERNAME + +######################################################################## +# Use Azure Bastion? +######################################################################## +$promptOutput = Read-Host "Configure Azure Bastion for accessing Agora host [Y/N]?" +$JS_DEPLOY_BASTION = $false +if ($promptOutput -like 'y') +{ + $JS_DEPLOY_BASTION = $true +} + +# set the env variable +azd env set JS_DEPLOY_BASTION $JS_DEPLOY_BASTION + +######################################################################## +# RDP Port +######################################################################## +$JS_RDP_PORT = '3389' +If ($env:JS_RDP_PORT) { + $JS_RDP_PORT = $env:JS_RDP_PORT +} +if ($promptOutput -notlike 'y') { + if ($promptOutput = Read-Host "Enter the RDP Port for remote desktop connection [$JS_RDP_PORT]") + { + $JS_RDP_PORT = $promptOutput + } +} +# set the env variable +azd env set JS_RDP_PORT $JS_RDP_PORT + +######################################################################## +# Get custom locations RP Id +######################################################################## +$customLocationRPOID=(Get-AzADServicePrincipal -DisplayName 'Custom Locations RP').Id + +# Set environment variables +azd env set CUSTOM_LOCATION_RP_ID $customLocationRPOID + + +######################################################################## +# Create Azure Service Principal +######################################################################## +Write-Host "Checking for existing stored Azure service principal..." +if ($null -ne $env:SPN_CLIENT_ID) { + Write-Host "Re-using existing service principal..." +} else { + Write-Host "Attempting to create new service principal with scope /subscriptions/$($env:AZURE_SUBSCRIPTION_ID)..." + $user = (Get-AzContext).Account.Id.split("@")[0] + $uniqueSpnName = "$user-jumpstart-spn-$(Get-Random -Minimum 1000 -Maximum 9999)" + try { + $spn = New-AzADServicePrincipal -DisplayName $uniqueSpnName -Role "Owner" -Scope "/subscriptions/$($env:AZURE_SUBSCRIPTION_ID)" -ErrorAction Stop + $SPN_CLIENT_ID = $spn.AppId + $SPN_CLIENT_SECRET = $spn.PasswordCredentials.SecretText + $SPN_TENANT_ID = (Get-AzContext).Tenant.Id + $SPN_OBJECT_ID = $spn.Id + # Set environment variables + azd env set SPN_CLIENT_ID -- $SPN_CLIENT_ID + azd env set SPN_CLIENT_SECRET -- $SPN_CLIENT_SECRET + azd env set SPN_TENANT_ID -- $SPN_TENANT_ID + azd env set SPN_OBJECT_ID -- $SPN_OBJECT_ID + } + catch { + + If ($error[0].ToString() -match "Forbidden"){ + Throw "You do not have permission to create a service principal. Please contact your Azure subscription administrator to grant you the Owner role on the subscription." + } + else { + Throw "An error occurred creating the service principal. Error:" + $error[0].ToString() + } + } + +} diff --git a/azure_jumpstart_ag/retail/.gitignore b/azure_jumpstart_ag/retail/.gitignore new file mode 100644 index 0000000000..6297a3b672 --- /dev/null +++ b/azure_jumpstart_ag/retail/.gitignore @@ -0,0 +1,2 @@ +.azure +js_rsa* \ No newline at end of file diff --git a/azure_jumpstart_ag/retail/azure.yaml b/azure_jumpstart_ag/retail/azure.yaml new file mode 100644 index 0000000000..200ea17596 --- /dev/null +++ b/azure_jumpstart_ag/retail/azure.yaml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json + +name: azure_jumpstart_ag +metadata: + template: azure_jumpstart_agora@0.0.1-beta +infra: + provider: "bicep" + path: "bicep" + module: "main.azd" +hooks: + preprovision: + shell: pwsh + run: ./scripts/preprovision.ps1 + continueOnError: false + interactive: true + postprovision: + shell: pwsh + run: ./scripts/postprovision.ps1 + continueOnError: false + interactive: true \ No newline at end of file diff --git a/azure_jumpstart_ag/bicep/clientVm/clientVm.bicep b/azure_jumpstart_ag/retail/bicep/clientVm/clientVm.bicep similarity index 94% rename from azure_jumpstart_ag/bicep/clientVm/clientVm.bicep rename to azure_jumpstart_ag/retail/bicep/clientVm/clientVm.bicep index 156ee2b863..a7bf32626b 100644 --- a/azure_jumpstart_ag/bicep/clientVm/clientVm.bicep +++ b/azure_jumpstart_ag/retail/bicep/clientVm/clientVm.bicep @@ -84,6 +84,12 @@ param adxClusterName string @description('Random GUID') param namingGuid string +@description('The agora industry to be deployed') +param industry string = 'retail' + +@description('The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing.') +param AKSEEPinnedSchemaVersion string = 'useLatest' + var encodedPassword = base64(windowsAdminPassword) var bastionName = 'Ag-Bastion' var publicIpAddressName = deployBastion == false ? '${vmName}-PIP' : '${bastionName}-PIP' @@ -198,7 +204,7 @@ resource vmBootstrap 'Microsoft.Compute/virtualMachines/extensions@2022-11-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} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -stagingStorageAccountName ${storageAccountName} -workspaceName ${workspaceName} -templateBaseUrl ${templateBaseUrl} -githubUser ${githubUser} -aksStagingClusterName ${aksStagingClusterName} -iotHubHostName ${iotHubHostName} -acrName ${acrName} -cosmosDBName ${cosmosDBName} -cosmosDBEndpoint ${cosmosDBEndpoint} -rdpPort ${rdpPort} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -githubPAT ${githubPAT} -adxClusterName ${adxClusterName} -namingGuid ${namingGuid}' + commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -spnAuthority ${spnAuthority} -subscriptionId ${subscription().subscriptionId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -stagingStorageAccountName ${storageAccountName} -workspaceName ${workspaceName} -templateBaseUrl ${templateBaseUrl} -githubUser ${githubUser} -aksStagingClusterName ${aksStagingClusterName} -iotHubHostName ${iotHubHostName} -acrName ${acrName} -cosmosDBName ${cosmosDBName} -cosmosDBEndpoint ${cosmosDBEndpoint} -rdpPort ${rdpPort} -githubAccount ${githubAccount} -githubBranch ${githubBranch} -githubPAT ${githubPAT} -adxClusterName ${adxClusterName} -namingGuid ${namingGuid} -industry ${industry} -AKSEEPinnedSchemaVersion ${AKSEEPinnedSchemaVersion}' } } } diff --git a/azure_jumpstart_ag/bicep/data/cosmosDB.bicep b/azure_jumpstart_ag/retail/bicep/data/cosmosDB.bicep similarity index 100% rename from azure_jumpstart_ag/bicep/data/cosmosDB.bicep rename to azure_jumpstart_ag/retail/bicep/data/cosmosDB.bicep diff --git a/azure_jumpstart_ag/bicep/data/dataExplorer.bicep b/azure_jumpstart_ag/retail/bicep/data/dataExplorer.bicep similarity index 100% rename from azure_jumpstart_ag/bicep/data/dataExplorer.bicep rename to azure_jumpstart_ag/retail/bicep/data/dataExplorer.bicep diff --git a/azure_jumpstart_ag/bicep/data/iotHub.bicep b/azure_jumpstart_ag/retail/bicep/data/iotHub.bicep similarity index 100% rename from azure_jumpstart_ag/bicep/data/iotHub.bicep rename to azure_jumpstart_ag/retail/bicep/data/iotHub.bicep diff --git a/azure_jumpstart_ag/bicep/data/script.kql b/azure_jumpstart_ag/retail/bicep/data/script.kql similarity index 100% rename from azure_jumpstart_ag/bicep/data/script.kql rename to azure_jumpstart_ag/retail/bicep/data/script.kql diff --git a/azure_jumpstart_ag/bicep/kubernetes/aks.bicep b/azure_jumpstart_ag/retail/bicep/kubernetes/aks.bicep similarity index 98% rename from azure_jumpstart_ag/bicep/kubernetes/aks.bicep rename to azure_jumpstart_ag/retail/bicep/kubernetes/aks.bicep index 120789182e..e54098af6c 100644 --- a/azure_jumpstart_ag/bicep/kubernetes/aks.bicep +++ b/azure_jumpstart_ag/retail/bicep/kubernetes/aks.bicep @@ -62,7 +62,7 @@ param osType string = 'Linux' var tier = 'free' @description('The version of Kubernetes') -param kubernetesVersion string = '1.27.3' +param kubernetesVersion string = '1.28.5' var serviceCidr_staging = '10.21.64.0/19' var dnsServiceIP_staging = '10.21.64.10' diff --git a/azure_jumpstart_ag/bicep/main.azd.bicep b/azure_jumpstart_ag/retail/bicep/main.azd.bicep similarity index 92% rename from azure_jumpstart_ag/bicep/main.azd.bicep rename to azure_jumpstart_ag/retail/bicep/main.azd.bicep index a4ebe3dc70..cb6680b118 100644 --- a/azure_jumpstart_ag/bicep/main.azd.bicep +++ b/azure_jumpstart_ag/retail/bicep/main.azd.bicep @@ -1,36 +1,38 @@ @description('Azure service principal client id') -param spnClientId string +param spnClientId string = '' @description('Azure service principal client secret') +@minLength(12) +@maxLength(123) @secure() -param spnClientSecret string +param spnClientSecret string = newGuid() @description('Azure AD tenant id for your service principal') -param spnTenantId string +param spnTenantId string = '' @minLength(1) @maxLength(77) @description('Prefix for resource group, i.e. {name}-rg') -param envName string +param envName string = toLower(substring(newGuid(), 0, 5)) @description('Location for all resources') -param location string +param location string = '' @maxLength(5) @description('Random GUID') param namingGuid string = toLower(substring(newGuid(), 0, 5)) @description('Username for Windows account') -param windowsAdminUsername string +param windowsAdminUsername string = 'Agora' @description('Password for Windows account. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. The value must be between 12 and 123 characters long') @minLength(12) @maxLength(123) @secure() -param windowsAdminPassword string +param windowsAdminPassword string = newGuid() @description('Configure all linux machines with the SSH RSA public key string. Your key should include three parts, for example \'ssh-rsa AAAAB...snip...UcyupgH azureuser@linuxvm\'') -param sshRSAPublicKey string +param sshRSAPublicKey string = '' @description('Name for your log analytics workspace') param logAnalyticsWorkspaceName string = 'Ag-Workspace-${namingGuid}' @@ -46,12 +48,12 @@ param deployBastion bool = false @description('User github account where they have forked the repo https://github.com/microsoft/jumpstart-agora-apps') @minLength(1) -param githubUser string +param githubUser string = 'sampleUser' @description('GitHub Personal access token for the user account') @minLength(1) @secure() -param githubPAT string +param githubPAT string = newGuid() @description('Name of the Cloud VNet') param virtualNetworkNameCloud string = 'Ag-Vnet-Prod' @@ -85,6 +87,9 @@ param acrName string = 'agacr${namingGuid}' @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('The agora industry to be deployed') +param industry string = 'retail' + var templateBaseUrl = 'https://raw.githubusercontent.com/${githubAccount}/azure_arc/${githubBranch}/azure_jumpstart_ag/' targetScope = 'subscription' @@ -133,8 +138,8 @@ module kubernetesDeployment 'kubernetes/aks.bicep' = { spnClientId: spnClientId spnClientSecret: spnClientSecret location: location - acrName: acrName sshRSAPublicKey: sshRSAPublicKey + acrName: acrName } } @@ -165,6 +170,7 @@ module clientVmDeployment 'clientVm/clientVm.bicep' = { rdpPort: rdpPort adxClusterName: adxClusterName namingGuid: namingGuid + industry: industry } } diff --git a/azure_jumpstart_ag/bicep/main.azd.parameters.json b/azure_jumpstart_ag/retail/bicep/main.azd.parameters.json similarity index 100% rename from azure_jumpstart_ag/bicep/main.azd.parameters.json rename to azure_jumpstart_ag/retail/bicep/main.azd.parameters.json diff --git a/azure_jumpstart_ag/bicep/main.bicep b/azure_jumpstart_ag/retail/bicep/main.bicep similarity index 92% rename from azure_jumpstart_ag/bicep/main.bicep rename to azure_jumpstart_ag/retail/bicep/main.bicep index 37f8b6b88a..0be0886226 100644 --- a/azure_jumpstart_ag/bicep/main.bicep +++ b/azure_jumpstart_ag/retail/bicep/main.bicep @@ -80,6 +80,14 @@ param acrName string = 'agacr${namingGuid}' @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('The agora industry to be deployed') +param industry string = 'retail' + +@description('''The AKS Edge Essentials schema version to be used. This is only used to pin the AKS Edge Essentials schema version for testing. +To pin a specific version, use the format '1.13'. To use the latest schema version, use 'useLatest'. +''') +param AKSEEPinnedSchemaVersion string = '1.13' + var templateBaseUrl = 'https://raw.githubusercontent.com/${githubAccount}/azure_arc/${githubBranch}/azure_jumpstart_ag/' module mgmtArtifactsAndPolicyDeployment 'mgmt/mgmtArtifacts.bicep' = { @@ -148,6 +156,8 @@ module clientVmDeployment 'clientVm/clientVm.bicep' = { rdpPort: rdpPort adxClusterName: adxClusterName namingGuid: namingGuid + industry: industry + AKSEEPinnedSchemaVersion: AKSEEPinnedSchemaVersion } } diff --git a/azure_jumpstart_ag/bicep/main.parameters.json b/azure_jumpstart_ag/retail/bicep/main.parameters.json similarity index 100% rename from azure_jumpstart_ag/bicep/main.parameters.json rename to azure_jumpstart_ag/retail/bicep/main.parameters.json diff --git a/azure_jumpstart_ag/bicep/mgmt/mgmtArtifacts.bicep b/azure_jumpstart_ag/retail/bicep/mgmt/mgmtArtifacts.bicep similarity index 100% rename from azure_jumpstart_ag/bicep/mgmt/mgmtArtifacts.bicep rename to azure_jumpstart_ag/retail/bicep/mgmt/mgmtArtifacts.bicep diff --git a/azure_jumpstart_ag/bicep/mgmt/network.bicep b/azure_jumpstart_ag/retail/bicep/mgmt/network.bicep similarity index 100% rename from azure_jumpstart_ag/bicep/mgmt/network.bicep rename to azure_jumpstart_ag/retail/bicep/mgmt/network.bicep diff --git a/azure_jumpstart_ag/bicep/mgmt/policyAzureArcRGScope.bicep b/azure_jumpstart_ag/retail/bicep/mgmt/policyAzureArcRGScope.bicep similarity index 100% rename from azure_jumpstart_ag/bicep/mgmt/policyAzureArcRGScope.bicep rename to azure_jumpstart_ag/retail/bicep/mgmt/policyAzureArcRGScope.bicep diff --git a/azure_jumpstart_ag/retail/bicep/mgmt/storageAccount.bicep b/azure_jumpstart_ag/retail/bicep/mgmt/storageAccount.bicep new file mode 100644 index 0000000000..b36fbbb9cf --- /dev/null +++ b/azure_jumpstart_ag/retail/bicep/mgmt/storageAccount.bicep @@ -0,0 +1,33 @@ +@description('Storage Account type') +@allowed([ + 'Standard_LRS' + 'Standard_GRS' + 'Standard_ZRS' + 'Premium_LRS' +]) +param storageAccountType string = 'Standard_LRS' + +@description('Location for all resources.') +param location string = resourceGroup().location + +@description('Resource tag for Jumpstart Agora') +param resourceTags object = { + Project: 'Jumpstart_Agora' +} + +var storageAccountName = 'agora${uniqueString(resourceGroup().id)}' + +resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = { + name: storageAccountName + location: location + tags: resourceTags + sku: { + name: storageAccountType + } + kind: 'StorageV2' + properties: { + supportsHttpsTrafficOnly: true + } +} + +output storageAccountName string = storageAccountName diff --git a/azure_jumpstart_ag/scripts/postprovision.ps1 b/azure_jumpstart_ag/retail/scripts/postprovision.ps1 similarity index 96% rename from azure_jumpstart_ag/scripts/postprovision.ps1 rename to azure_jumpstart_ag/retail/scripts/postprovision.ps1 index 210860b715..08698e13d0 100644 --- a/azure_jumpstart_ag/scripts/postprovision.ps1 +++ b/azure_jumpstart_ag/retail/scripts/postprovision.ps1 @@ -7,7 +7,7 @@ if ($null -ne $env:AZURE_RESOURCE_GROUP){ # This section is for testing only $resourceGroup = "charris-js-ag-43-rg" $adxClusterName = "agadx2827a" - Get-AzSubscription -SubscriptionName "Azure Arc Jumpstart Subscription" | Select-AzSubscription + Get-AzSubscription -SubscriptionName "Arc Jumpstart Subscription" | Select-AzSubscription } ######################################################################## @@ -21,7 +21,7 @@ $kustoCluster = Get-AzKustoCluster -ResourceGroupName $resourceGroup -Name $adxC $adxEndPoint = $kustoCluster.Uri # Update the dashboards files with the new ADX cluster name and URI -$templateBaseUrl = "https://raw.githubusercontent.com/microsoft/azure_arc/main/azure_jumpstart_ag" +$templateBaseUrl = "https://raw.githubusercontent.com/microsoft/azure_arc/main/azure_jumpstart_ag/retail/" $ordersDashboardBody = (Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/adx_dashboards/adx-dashboard-orders-payload.json").Content -replace '{{ADX_CLUSTER_URI}}', $adxEndPoint -replace '{{ADX_CLUSTER_NAME}}', $adxClusterName $iotSensorsDashboardBody = (Invoke-WebRequest -Method Get -Uri "$templateBaseUrl/artifacts/adx_dashboards/adx-dashboard-iotsensor-payload.json") -replace '{{ADX_CLUSTER_URI}}', $adxEndPoint -replace '{{ADX_CLUSTER_NAME}}', $adxClusterName diff --git a/azure_jumpstart_ag/scripts/preprovision.ps1 b/azure_jumpstart_ag/retail/scripts/preprovision.ps1 similarity index 99% rename from azure_jumpstart_ag/scripts/preprovision.ps1 rename to azure_jumpstart_ag/retail/scripts/preprovision.ps1 index 43f7e24bf5..7d1be08c4b 100644 --- a/azure_jumpstart_ag/scripts/preprovision.ps1 +++ b/azure_jumpstart_ag/retail/scripts/preprovision.ps1 @@ -85,7 +85,7 @@ Function Get-AzAvailableLocations ($location, $skuFriendlyNames, $minCores = 0) } Function Get-AzAvailablePublicIpAddress ($location, $subscriptionId, $minPublicIP = 0) { - + $accessToken = az account get-access-token --query accessToken -o tsv $headers = @{ "Authorization" = "Bearer $accessToken" diff --git a/azure_jumpstart_arcbox/ARM/kubernetes/aks.json b/azure_jumpstart_arcbox/ARM/kubernetes/aks.json index 6d182be286..8c00c3da6b 100644 --- a/azure_jumpstart_arcbox/ARM/kubernetes/aks.json +++ b/azure_jumpstart_arcbox/ARM/kubernetes/aks.json @@ -107,7 +107,7 @@ "metadata": { "description": "The version of Kubernetes" }, - "defaultValue" : "1.26.6" + "defaultValue" : "1.28.5" } }, "variables": { diff --git a/azure_jumpstart_arcbox/artifacts/ArcServersLogonScript.ps1 b/azure_jumpstart_arcbox/artifacts/ArcServersLogonScript.ps1 index 59648049fa..ff7b3a341a 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://jumpstartprodsg.blob.core.windows.net/arcbox/*" # Archive exising log file and crate new one $logFilePath = "$Env:ArcBoxLogsDir\ArcServersLogonScript.log" @@ -69,7 +68,7 @@ if ($Env:flavor -ne "DevOps") { # Create an internal switch with NAT Write-Host "Creating Internal vSwitch" $switchName = 'InternalNATSwitch' - + # Verify if internal switch is already created, if not create a new switch $inernalSwitch = Get-VMSwitch if ($inernalSwitch.Name -ne $switchName) { @@ -125,6 +124,7 @@ if ($Env:flavor -ne "DevOps") { # Required for CLI commands Write-Header "Az CLI Login" az login --service-principal --username $spnClientId --password=$spnClientSecret --tenant $spnTenantId + az account set -s $Env:subscriptionId # Register Azure providers Write-Header "Registering Providers" @@ -172,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 @@ -291,25 +291,25 @@ if ($Env:flavor -ne "DevOps") { Write-Host "Enabling SQL server best practices assessment" $bpaDeploymentTemplateUrl = "$Env:templateBaseUrl/artifacts/sqlbpa.json" az deployment group create --resource-group $resourceGroup --template-uri $bpaDeploymentTemplateUrl --parameters workspaceName=$Env:workspaceName vmName=$SQLvmName arcSubscriptionId=$subscriptionId - + # Run Best practices assessment Write-Host "Execute SQL server best practices assessment" - + # Wait for a minute to finish everyting and run assessment Start-Sleep(60) - + # Get access token to make ARM REST API call for SQL server BPA $armRestApiEndpoint = "https://management.azure.com/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/Microsoft.HybridCompute/machines/$SQLvmName/extensions/WindowsAgent.SqlServer?api-version=2019-08-02-preview" $token = (az account get-access-token --subscription $subscriptionId --query accessToken --output tsv) $headers = @{"Authorization" = "Bearer $token"; "Content-Type" = "application/json" } - + # Build API request payload $worspaceResourceId = "/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/microsoft.operationalinsights/workspaces/$Env:workspaceName".ToLower() $sqlExtensionId = "/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.HybridCompute/machines/$SQLvmName/extensions/WindowsAgent.SqlServer" $sqlbpaPayloadTemplate = "$Env:templateBaseUrl/artifacts/sqlbpa.payload.json" $settingsSaveTime = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() $apiPayload = (Invoke-WebRequest -Uri $sqlbpaPayloadTemplate).Content -replace '{{RESOURCEID}}', $sqlExtensionId -replace '{{LOCATION}}', $azureLocation -replace '{{WORKSPACEID}}', $worspaceResourceId -replace '{{SAVETIME}}', $settingsSaveTime - + # Call REST API to run best practices assessment $httpResp = Invoke-WebRequest -Method Patch -Uri $armRestApiEndpoint -Body $apiPayload -Headers $headers if (($httpResp.StatusCode -eq 200) -or ($httpResp.StatusCode -eq 202)){ @@ -350,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 } } @@ -481,19 +481,19 @@ Write-Host "Creating deployment logs bundle" # Changing to Jumpstart ArcBox wallpaper # Changing to Client VM wallpaper $imgPath = "$Env:ArcBoxDir\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); +$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); } } -} +} '@ # Set wallpaper image based on the ArcBox Flavor deployed diff --git a/azure_jumpstart_arcbox/artifacts/DataOpsLogonScript.ps1 b/azure_jumpstart_arcbox/artifacts/DataOpsLogonScript.ps1 index ea3126598f..a29f870851 100644 --- a/azure_jumpstart_arcbox/artifacts/DataOpsLogonScript.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DataOpsLogonScript.ps1 @@ -4,11 +4,11 @@ $Env:ArcBoxVMDir = "$Env:ArcBoxDir\Virtual Machines" $Env:ArcBoxIconDir = "C:\ArcBox\Icons" $clusters = @( - [pscustomobject]@{clusterName = $Env:capiArcDataClusterName; dataController = "$Env:capiArcDataClusterName-dc" ; customLocation = "$Env:capiArcDataClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'LicenseIncluded' ; context = 'capi' ; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-capi" } + [pscustomobject]@{clusterName = $Env:capiArcDataClusterName; dataController = "$Env:capiArcDataClusterName-dc" ; customLocation = "$Env:capiArcDataClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'LicenseIncluded' ; context = 'capi' ; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-capi"; distribution = "generic" } - [pscustomobject]@{clusterName = $Env:aksArcClusterName ; dataController = "$Env:aksArcClusterName-dc" ; customLocation = "$Env:aksArcClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'LicenseIncluded' ; context = 'aks' ; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-aks" } + [pscustomobject]@{clusterName = $Env:aksArcClusterName ; dataController = "$Env:aksArcClusterName-dc" ; customLocation = "$Env:aksArcClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'LicenseIncluded' ; context = 'aks' ; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-aks"; distribution = "aks" } - [pscustomobject]@{clusterName = $Env:aksdrArcClusterName ; dataController = "$Env:aksdrArcClusterName-dc" ; customLocation = "$Env:aksdrArcClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'DisasterRecovery' ; context = 'aks-dr'; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-aksdr" } + [pscustomobject]@{clusterName = $Env:aksdrArcClusterName ; dataController = "$Env:aksdrArcClusterName-dc" ; customLocation = "$Env:aksdrArcClusterName-cl" ; storageClassName = 'managed-premium' ; licenseType = 'DisasterRecovery' ; context = 'aks-dr'; kubeConfig = "C:\Users\$Env:adminUsername\.kube\config-aksdr"; distribution = "aks" } ) Start-Transcript -Path $Env:ArcBoxLogsDir\DataOpsLogonScript.log @@ -33,6 +33,7 @@ Connect-AzAccount -Credential $psCred -TenantId $Env:spnTenantId -ServicePrincip # Required for CLI commands Write-Header "Az CLI Login" az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId +az account set -s $Env:subscriptionId # Register Azure providers Write-Header "Registering Providers" @@ -47,6 +48,7 @@ az config set extension.use_dynamic_install=yes_without_prompt # Installing Azure CLI extensions az extension add --name connectedk8s --version 1.3.17 az extension add --name arcdata +az extension add --name customlocation az -v # Installing Azure Data Studio extensions @@ -147,24 +149,47 @@ Start-Sleep -Seconds 10 Write-Header "Onboarding clusters as an Azure Arc-enabled Kubernetes cluster" foreach ($cluster in $clusters) { if ($cluster.context -ne 'capi') { - Write-Host "Checking K8s Nodes" + Write-Host "Checking K8s Nodes for $($cluster.clusterName) cluster" kubectl get nodes --kubeconfig $cluster.kubeConfig Write-Host "`n" - az connectedk8s connect --name $cluster.clusterName ` - --resource-group $Env:resourceGroup ` - --location $Env:azureLocation ` - --correlation-id "6038cc5b-b814-4d20-bcaa-0f60392416d5" ` - --kube-config $cluster.kubeConfig + Write-Host "Connecting $($cluster.clusterName) cluster to Azure Arc" + + # Try until the provision status is successful. + Write-Host "Attempting to connect $($cluster.clusterName) cluster to Azure Arc." + try { + az connectedk8s connect --name $cluster.clusterName ` + --resource-group $Env:resourceGroup ` + --location $Env:azureLocation ` + --correlation-id "6038cc5b-b814-4d20-bcaa-0f60392416d5" ` + --kube-config $cluster.kubeConfig ` + --distribution $cluster.distribution + } + catch { + <#Do this if a terminating exception happens#> + Write-Host "Connecting $($cluster.clusterName) cluster to Azure Arc failed. Exiting deployment. Please check logs and retry again later!" + Exit + } + + # Wait for some time to make sure all the pods are deployed and provisioning is completed. + $retryCount = 0 + do { + Start-Sleep -Seconds 20 - Start-Sleep -Seconds 10 + # Check connected cluster status and make sure provisioning stutus is successful + $clusterStatus = (az connectedk8s show --name $cluster.clusterName --resource-group $Env:resourceGroup --query provisioningState -o tsv) + $retryCount += 1 + } while ($clusterStatus -ne "Succeeded" -and $retryCount -lt 3) + + if ($clusterStatus -ne "Succeeded") { + Write-Host "Connecting $($cluster.clusterName) cluster to Azure Arc failed. Exiting deployment. Please check logs and retry again later!" + Exit + } # Enabling Container Insights and Azure Policy cluster extension on Arc-enabled cluster Write-Host "`n" Write-Host "Enabling Container Insights cluster extension" az k8s-extension create --name "azuremonitor-containers" --cluster-name $cluster.clusterName --resource-group $Env:resourceGroup --cluster-type connectedClusters --extension-type Microsoft.AzureMonitor.Containers --configuration-settings logAnalyticsWorkspaceResourceID=$workspaceId Write-Host "`n" - #Write-Host "Enabling Defender for Containers on AKS clusters" - #az aks update --enable-defender --resource-group $Env:resourceGroup --name $cluster.clusterName } } @@ -184,6 +209,7 @@ foreach ($cluster in $clusters) { $context = $cluster.context Start-Transcript -Path "$Env:ArcBoxLogsDir\DataController-$context.log" + Write-Host "Creating data services extension on $($cluster.clusterName) cluster." az k8s-extension create --name arc-data-services ` --extension-type microsoft.arcdataservices ` --cluster-type connectedClusters ` @@ -192,29 +218,47 @@ foreach ($cluster in $clusters) { --auto-upgrade false ` --scope cluster ` --release-namespace arc ` - --version 1.26.0 ` + --version 1.31.0 ` --config Microsoft.CustomLocation.ServiceAccount=sa-bootstrapper Write-Host "`n" - Do { + do { Write-Host "Waiting for bootstrapper pod, hold tight..." Start-Sleep -Seconds 20 $podStatus = $(if (kubectl get pods -n arc --kubeconfig $cluster.kubeConfig | Select-String "bootstrapper" | Select-String "Running" -Quiet) { "Ready!" }Else { "Nope" }) } while ($podStatus -eq "Nope") + Write-Host "Bootstrapper pod is ready!" + do { + Write-Host "Waiting for data services extension status, hold tight..." + Start-Sleep -Seconds 20 + $provisioningState = (az k8s-extension show --name arc-data-services --cluster-type connectedClusters --cluster-name $cluster.clusterName --resource-group $Env:resourceGroup --query provisioningState -o tsv) + } while ($provisioningState -ne "Succeeded") + + Write-Host "Data services extension is ready!" + + Write-Host "Creating custom location $($cluster.clusterName) cluster." $connectedClusterId = az connectedk8s show --name $cluster.clusterName --resource-group $Env:resourceGroup --query id -o tsv + Write-Host "Kubernetes Cnnected Cluster ID: $connectedClusterId" + $extensionId = az k8s-extension show --name arc-data-services --cluster-type connectedClusters --cluster-name $cluster.clusterName --resource-group $Env:resourceGroup --query id -o tsv - Start-Sleep -Seconds 10 - az customlocation create --name $cluster.customLocation --resource-group $Env:resourceGroup --namespace arc --host-resource-id $connectedClusterId --cluster-extension-ids $extensionId --kubeconfig $cluster.kubeConfig --only-show-errors + Write-Host "Arc data services extension ID: $extensionId" - Start-Sleep -Seconds 20 + Write-Host "Custom location name: $($cluster.customLocation)" + az customlocation create --name $cluster.customLocation --resource-group $Env:resourceGroup --namespace arc --host-resource-id $connectedClusterId --cluster-extension-ids $extensionId --kubeconfig $cluster.kubeConfig + Start-Sleep -Seconds 20 + $customLocationId = $(az customlocation show --name $cluster.customLocation --resource-group $Env:resourceGroup --query id -o tsv) + Write-Host "Custom location ID: $customLocationId" + if ($null -eq $customLocationId){ + Write-Host "Failed to create custom location. Existing deployment" + Exit + } + # Deploying the Azure Arc Data Controller - $context = $cluster.context - $customLocationId = $(az customlocation show --name $cluster.customLocation --resource-group $Env:resourceGroup --query id -o tsv) $workspaceId = $(az resource show --resource-group $Env:resourceGroup --name $Env:workspaceName --resource-type "Microsoft.OperationalInsights/workspaces" --query properties.customerId -o tsv) $workspaceKey = $(az monitor log-analytics workspace get-shared-keys --resource-group $Env:resourceGroup --workspace-name $Env:workspaceName --query primarySharedKey -o tsv) Copy-Item "$Env:ArcBoxDir\dataController.parameters.json" -Destination "$Env:ArcBoxDir\dataController-$context-stage.parameters.json" diff --git a/azure_jumpstart_arcbox/artifacts/DataServicesLogonScript.ps1 b/azure_jumpstart_arcbox/artifacts/DataServicesLogonScript.ps1 index 9d3a05e5d8..ff35cd1cb8 100644 --- a/azure_jumpstart_arcbox/artifacts/DataServicesLogonScript.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DataServicesLogonScript.ps1 @@ -23,6 +23,7 @@ Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False # Required for CLI commands Write-Header "Az CLI Login" az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId +az account set -s $Env:subscriptionId # Making extension install dynamic Write-Header "Installing Azure CLI extensions" @@ -99,7 +100,7 @@ az k8s-extension create --name arc-data-services ` --resource-group $Env:resourceGroup ` --auto-upgrade false ` --scope cluster ` - --version 1.26.0 ` + --version 1.31.0 ` --release-namespace arc ` --config Microsoft.CustomLocation.ServiceAccount=sa-bootstrapper diff --git a/azure_jumpstart_arcbox/artifacts/DeployPostgreSQL.ps1 b/azure_jumpstart_arcbox/artifacts/DeployPostgreSQL.ps1 index c3a368be5a..9d4504db8c 100644 --- a/azure_jumpstart_arcbox/artifacts/DeployPostgreSQL.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DeployPostgreSQL.ps1 @@ -69,7 +69,7 @@ Start-Sleep -Seconds 60 # Downloading demo database and restoring onto Postgres Write-Host "Downloading AdventureWorks.sql template for Postgres... (1/3)" -kubectl exec $pgWorkerPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jsvhds.blob.core.windows.net/sampledb/AdventureWorks2019.sql?sp=r&st=2023-08-10T18:30:33Z&se=2033-08-11T02:30:33Z&spr=https&sv=2022-11-02&sr=b&sig=5kD1PR4gwaCWlefSknggq%2BYsx1FgBrp5Kv1pH42d1nE%3D'" 2>&1 | Out-Null +kubectl exec $pgWorkerPodName -n arc -c postgres -- /bin/bash -c "curl -o /tmp/AdventureWorks2019.sql 'https://jumpstartprodsg.blob.core.windows.net/sampledb/AdventureWorks2019.sql'" 2>&1 | Out-Null Write-Host "Creating AdventureWorks database on Postgres... (2/3)" kubectl exec $pgWorkerPodName -n arc -c postgres -- psql -U postgres -c 'CREATE DATABASE "adventureworks2019";' postgres 2>&1 | Out-Null Write-Host "Restoring AdventureWorks database on Postgres. (3/3)" diff --git a/azure_jumpstart_arcbox/artifacts/DevOpsLogonScript.ps1 b/azure_jumpstart_arcbox/artifacts/DevOpsLogonScript.ps1 index 438d131edc..8ee3e7582f 100644 --- a/azure_jumpstart_arcbox/artifacts/DevOpsLogonScript.ps1 +++ b/azure_jumpstart_arcbox/artifacts/DevOpsLogonScript.ps1 @@ -37,6 +37,7 @@ $Env:capiArcDataClusterName=$Env:capiArcDataClusterName -replace "`n","" # Required for CLI commands Write-Header "Az CLI Login" az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId +az account set -s $Env:subscriptionId # Downloading CAPI Kubernetes cluster kubeconfig file Write-Header "Downloading CAPI K8s Kubeconfig" diff --git a/azure_jumpstart_arcbox/artifacts/LogInstructions.txt b/azure_jumpstart_arcbox/artifacts/LogInstructions.txt index 7dcd4a356f..8068bc81d1 100644 --- a/azure_jumpstart_arcbox/artifacts/LogInstructions.txt +++ b/azure_jumpstart_arcbox/artifacts/LogInstructions.txt @@ -1,7 +1,6 @@ -To upload the log bundle zip file for support review, use the below command: +To upload the log bundle ZIP file for support review, use the following steps command: -azcopy copy "C:\ArcBox\Logs\.zip" "https://https://jumpstartsupport.blob.core.windows.net/logbundles?sp=cw&st=2022-02-02T22:04:39Z&se=2027-04-22T05:04:39Z&spr=https&sv=2020-08-04&sr=c&sig=2urJGqXvkXTSBpu%2FqCksANy18pY5o44tmzzKjc8eUoM%3D" --recursive - -For example: - -azcopy copy "C:\ArcBox\Logs\LogsBundle-kavpug.zip" "https://https://jumpstartsupport.blob.core.windows.net/logbundles?sp=cw&st=2022-02-02T22:04:39Z&se=2027-04-22T05:04:39Z&spr=https&sv=2020-08-04&sr=c&sig=2urJGqXvkXTSBpu%2FqCksANy18pY5o44tmzzKjc8eUoM%3D" --recursive \ No newline at end of file +1. Create a ZIP file with all the required files +1. Open an [issue](https://github.com/microsoft/azure_arc/issues/new?assignees=&labels=triage&projects=&template=bug_report.md&title=) with the appropiate title and description. +1. Attach the log bundle ZIP file +1. If extra files/logs are required, these will be asked through the GitHub issue discussion view \ No newline at end of file diff --git a/azure_jumpstart_arcbox/artifacts/installCAPI.sh b/azure_jumpstart_arcbox/artifacts/installCAPI.sh index 74ef81b512..973e122300 100644 --- a/azure_jumpstart_arcbox/artifacts/installCAPI.sh +++ b/azure_jumpstart_arcbox/artifacts/installCAPI.sh @@ -53,6 +53,7 @@ sudo -u $adminUsername az extension add --name k8s-extension echo "Log in to Azure" sudo -u $adminUsername az login --service-principal --username $SPN_CLIENT_ID --password=$SPN_CLIENT_SECRET --tenant $SPN_TENANT_ID subscriptionId=$(sudo -u $adminUsername az account show --query id --output tsv) +sudo -u $adminUsername az account set -s $subscriptionId export AZURE_RESOURCE_GROUP=$(sudo -u $adminUsername az resource list --query "[?name=='$stagingStorageAccountName']".[resourceGroup] --resource-type "Microsoft.Storage/storageAccounts" -o tsv) az -v echo "" @@ -220,7 +221,7 @@ while true; do # Iterate over each node and check its status for node in $nodes; do ready=$(kubectl get nodes $node --kubeconfig=./$CLUSTER_NAME.kubeconfig -o json | jq -r '.status.conditions[] | select(.type=="Ready") | .status') - + if [[ $ready != "True" ]]; then echo "Node $node is not ready." all_ready=false @@ -254,7 +255,7 @@ sudo service sshd restart echo "" sudo -u $adminUsername kubectl apply -f ${templateBaseUrl}artifacts/capiStorageClass.yaml -# Renaming CAPI cluster context name +# Renaming CAPI cluster context name echo "" sudo -u $adminUsername kubectl config rename-context "$CLUSTER_NAME-admin@$CLUSTER_NAME" "arcbox-capi" diff --git a/azure_jumpstart_arcbox/artifacts/installK3s.sh b/azure_jumpstart_arcbox/artifacts/installK3s.sh index 27832a807d..a8bc3b9825 100644 --- a/azure_jumpstart_arcbox/artifacts/installK3s.sh +++ b/azure_jumpstart_arcbox/artifacts/installK3s.sh @@ -78,6 +78,8 @@ sudo -u $adminUsername az extension add --name k8s-extension echo "" echo "Log in to Azure" sudo -u $adminUsername az login --service-principal --username $SPN_CLIENT_ID --password=$SPN_CLIENT_SECRET --tenant $SPN_TENANT_ID +subscriptionId=$(sudo -u $adminUsername az account show --query id --output tsv) +sudo -u $adminUsername az account set -s $subscriptionId az -v echo "" diff --git a/azure_jumpstart_arcbox/bicep/kubernetes/aks.bicep b/azure_jumpstart_arcbox/bicep/kubernetes/aks.bicep index 02edc15366..6385ad3ddb 100644 --- a/azure_jumpstart_arcbox/bicep/kubernetes/aks.bicep +++ b/azure_jumpstart_arcbox/bicep/kubernetes/aks.bicep @@ -50,7 +50,7 @@ param enableRBAC bool = true param osType string = 'Linux' @description('The version of Kubernetes') -param kubernetesVersion string = '1.26.6' +param kubernetesVersion string = '1.28.5' var serviceCidr_primary = '10.20.64.0/19' var dnsServiceIP_primary = '10.20.64.10' diff --git a/azure_jumpstart_arcbox/terraform/modules/kubernetes/aks/main.tf b/azure_jumpstart_arcbox/terraform/modules/kubernetes/aks/main.tf index cf40744e1a..67e13a7186 100644 --- a/azure_jumpstart_arcbox/terraform/modules/kubernetes/aks/main.tf +++ b/azure_jumpstart_arcbox/terraform/modules/kubernetes/aks/main.tf @@ -82,7 +82,7 @@ variable "os_type" { variable "Kubernetes_version" { type = string description = "The version of Kubernetes" - default = "1.26.6" + default = "1.28.5" } locals { diff --git a/azure_jumpstart_hcibox/artifacts/Deploy-AKS.ps1 b/azure_jumpstart_hcibox/artifacts/Deploy-AKS.ps1 deleted file mode 100644 index 24f05f1f9a..0000000000 --- a/azure_jumpstart_hcibox/artifacts/Deploy-AKS.ps1 +++ /dev/null @@ -1,104 +0,0 @@ -# Set paths -$Env:HCIBoxDir = "C:\HCIBox" -$Env:HCIBoxLogsDir = "C:\HCIBox\Logs" -$Env:HCIBoxVMDir = "C:\HCIBox\Virtual Machines" -$Env:HCIBoxKVDir = "C:\HCIBox\KeyVault" -$Env:HCIBoxGitOpsDir = "C:\HCIBox\GitOps" -$Env:HCIBoxIconDir = "C:\HCIBox\Icons" -$Env:HCIBoxVHDDir = "C:\HCIBox\VHD" -$Env:HCIBoxSDNDir = "C:\HCIBox\SDN" -$Env:HCIBoxWACDir = "C:\HCIBox\Windows Admin Center" -$Env:agentScript = "C:\HCIBox\agentScript" -$Env:ToolsDir = "C:\Tools" -$Env:tempDir = "C:\Temp" -$Env:VMPath = "C:\VMs" - -Start-Transcript -Path $Env:HCIBoxLogsDir\Deploy-AKS.log - -# Import Configuration Module and create Azure login credentials -Write-Header 'Importing config' -$ConfigurationDataFile = 'C:\HCIBox\HCIBox-Config.psd1' -$SDNConfig = Import-PowerShellDataFile -Path $ConfigurationDataFile - -# Generate credential objects -Write-Header 'Creating credentials and connecting to Azure' -$user = "jumpstart.local\administrator" -$password = ConvertTo-SecureString -String $SDNConfig.SDNAdminPassword -AsPlainText -Force -$adcred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user, $password # Domain credential - -$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 -$context = Get-AzContext # Azure credential - -Register-AzResourceProvider -ProviderNamespace Microsoft.Kubernetes -Confirm:$false -Register-AzResourceProvider -ProviderNamespace Microsoft.KubernetesConfiguration -Confirm:$false - -# Install latest versions of Nuget and PowershellGet -Write-Header "Install latest versions of Nuget and PowershellGet" -Invoke-Command -VMName $SDNConfig.HostList -Credential $adcred -ScriptBlock { - Enable-PSRemoting -Force - $ProgressPreference = "SilentlyContinue" - Install-PackageProvider -Name NuGet -Force - Set-PSRepository -Name "PSGallery" -InstallationPolicy Trusted - Install-Module -Name PowershellGet -Force - $ProgressPreference = "Continue" -} - -# Install necessary AZ modules and initialize akshci on each node -Write-Header "Install necessary AZ modules plus AksHCI module and initialize akshci on each node" - -Invoke-Command -VMName $SDNConfig.HostList -Credential $adcred -ScriptBlock { - Write-Host "Installing Required Modules" - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - $ProgressPreference = "SilentlyContinue" - Install-Module -Name AksHci -Force -AcceptLicense - Import-Module Az.Accounts -DisableNameChecking - Import-Module Az.Resources -DisableNameChecking - Import-Module AzureAD -DisableNameChecking - Import-Module AksHci -DisableNameChecking - Initialize-AksHciNode - $ProgressPreference = "Continue" -} - -# Generate unique name for workload cluster -$rand = New-Object System.Random -$prefixLen = 5 -[string]$namingPrefix = '' -for($i = 0; $i -lt $prefixLen; $i++) -{ - $namingPrefix += [char]$rand.Next(97,122) -} -$clusterName = $SDNConfig.AKSworkloadClusterName + "-" + $namingPrefix -#$azureLocation = $env:azureLocation -[System.Environment]::SetEnvironmentVariable('AKSClusterName', $clusterName,[System.EnvironmentVariableTarget]::Machine) - -# Install AksHci - only need to perform the following on one of the nodes -$rg = $env:resourceGroup -Write-Header "Prepping AKS Install" -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - $vnet = New-AksHciNetworkSetting -name $using:SDNConfig.AKSvnetname -vSwitchName $using:SDNConfig.AKSvSwitchName -k8sNodeIpPoolStart $using:SDNConfig.AKSNodeStartIP -k8sNodeIpPoolEnd $using:SDNConfig.AKSNodeEndIP -vipPoolStart $using:SDNConfig.AKSVIPStartIP -vipPoolEnd $using:SDNConfig.AKSVIPEndIP -ipAddressPrefix $using:SDNConfig.AKSIPPrefix -gateway $using:SDNConfig.AKSGWIP -dnsServers $using:SDNConfig.AKSDNSIP -vlanID $using:SDNConfig.AKSVlanID - Set-AksHciConfig -imageDir $using:SDNConfig.AKSImagedir -workingDir $using:SDNConfig.AKSWorkingdir -cloudConfigLocation $using:SDNConfig.AKSCloudConfigdir -vnet $vnet -cloudservicecidr $using:SDNConfig.AKSCloudSvcidr -controlPlaneVmSize Standard_D4s_v3 - $azurecred = Connect-AzAccount -ServicePrincipal -Subscription $using:context.Subscription.Id -Tenant $using:context.Subscription.TenantId -Credential $using:azureAppCred - Set-AksHciRegistration -subscriptionId $azurecred.Context.Subscription.Id -resourceGroupName $using:rg -Tenant $azurecred.Context.Tenant.Id -Credential $using:azureAppCred -Region "eastus" - Write-Host "Ready to Install AKS on HCI Cluster" - Install-AksHci -} - -# Create new AKS target cluster and connect it to Azure -Write-Header "Creating AKS target cluster" -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - New-AksHciCluster -name $using:clusterName -nodePoolName linuxnodepool -nodecount 2 -osType linux -nodeVmSize Standard_D8s_v3 - Enable-AksHciArcConnection -name $using:clusterName -} - -Write-Header "Checking AKS-HCI nodes and running pods" -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - Get-AksHciCredential -name $using:clusterName -Confirm:$false - kubectl get nodes - kubectl get pods -A -} - -# Set env variable deployAKSHCI to true (in case the script was run manually) -[System.Environment]::SetEnvironmentVariable('deployAKSHCI', 'true',[System.EnvironmentVariableTarget]::Machine) - -Stop-Transcript \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/Deploy-ArcResourceBridge.ps1 b/azure_jumpstart_hcibox/artifacts/Deploy-ArcResourceBridge.ps1 deleted file mode 100644 index bc89315c73..0000000000 --- a/azure_jumpstart_hcibox/artifacts/Deploy-ArcResourceBridge.ps1 +++ /dev/null @@ -1,159 +0,0 @@ -# Set paths -$Env:HCIBoxDir = "C:\HCIBox" -$Env:HCIBoxLogsDir = "C:\HCIBox\Logs" -$Env:HCIBoxVMDir = "C:\HCIBox\Virtual Machines" -$Env:HCIBoxKVDir = "C:\HCIBox\KeyVault" -$Env:HCIBoxGitOpsDir = "C:\HCIBox\GitOps" -$Env:HCIBoxIconDir = "C:\HCIBox\Icons" -$Env:HCIBoxVHDDir = "C:\HCIBox\VHD" -$Env:HCIBoxSDNDir = "C:\HCIBox\SDN" -$Env:HCIBoxWACDir = "C:\HCIBox\Windows Admin Center" -$Env:agentScript = "C:\HCIBox\agentScript" -$Env:ToolsDir = "C:\Tools" -$Env:tempDir = "C:\Temp" -$Env:VMPath = "C:\VMs" - -Start-Transcript -Path $Env:HCIBoxLogsDir\Deploy-ArcResourceBridge.log - -# Import Configuration Module -$ConfigurationDataFile = "$Env:HCIBoxDir\HCIBox-Config.psd1" -$SDNConfig = Import-PowerShellDataFile -Path $ConfigurationDataFile -$csv_path = "C:\ClusterStorage\S2D_vDISK1" - -# Set AD Domain cred -$user = "jumpstart.local\administrator" -$password = ConvertTo-SecureString -String $SDNConfig.SDNAdminPassword -AsPlainText -Force -$adcred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user, $password - -# Install AZ Resource Bridge and prerequisites -Write-Host "Now Preparing to Install Azure Arc Resource Bridge" - -if ($env:deployAKSHCI -eq $false) { - Write-Header "Install latest versions of Nuget and PowershellGet" - Invoke-Command -VMName $SDNConfig.HostList -Credential $adcred -ScriptBlock { - Enable-PSRemoting -Force - Install-PackageProvider -Name NuGet -Force - Set-PSRepository -Name "PSGallery" -InstallationPolicy Trusted - Install-Module -Name PowershellGet -Force - } -} - -Invoke-Command -VMName $SDNConfig.HostList -Credential $adcred -ScriptBlock { - Install-Module -Name Moc -Repository PSGallery -AcceptLicense -Force - Initialize-MocNode - Install-Module -Name ArcHci -Force -Confirm:$false -SkipPublisherCheck -AcceptLicense -} - -Invoke-Command -VMName $SDNConfig.HostList -Credential $adcred -ScriptBlock { - [System.Environment]::SetEnvironmentVariable('Path', [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\",[System.EnvironmentVariableTarget]::Machine) - $Env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") - az extension add --upgrade --version 0.2.31 --name arcappliance --only-show-errors - az extension add --upgrade --name connectedk8s --only-show-errors - az extension add --upgrade --name k8s-configuration --only-show-errors - az extension add --upgrade --name k8s-extension --only-show-errors - az extension add --upgrade --name customlocation --only-show-errors - az extension add --upgrade --name azurestackhci --only-show-errors -} - -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - New-Item -Path $using:csv_path -Name "ResourceBridge" -ItemType Directory -} - -$subId = $env:subscriptionId -$rg = $env:resourceGroup -$spnClientId = $env:spnClientId -$spnSecret = $env:spnClientSecret -$spnTenantId = $env:spnTenantId -$resource_name = "HCIBox-ResourceBridge" -$location = "eastus" -$custom_location_name = "hcibox-rb-cl" -$cloudServiceIP = $SDNConfig.AKSCloudSvcidr.Substring(0, $SDNConfig.AKSCloudSvcidr.IndexOf('/')) - -if ($env:deployAKSHCI -eq $false) { - Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - #$vnet = New-MocNetworkSetting -Name $using:SDNConfig.AKSvnetname -vswitchName $using:SDNConfig.AKSvSwitchName -vipPoolStart $using:SDNConfig.AKSVIPStartIP -vipPoolEnd $using:SDNConfig.AKSVIPEndIP -vlanID $using:SDNConfig.AKSVlanID - #Set-MocConfig -workingDir $using:csv_path\ResourceBridge -vnet $vnet -imageDir $using:csv_path\imageStore -skipHostLimitChecks -cloudConfigLocation $using:csv_path\cloudStore -catalog aks-hci-stable-catalogs-ext -ring stable -CloudServiceIP $using:cloudServiceIP -createAutoConfigContainers $false - Set-MocConfig -workingDir $using:csv_path\ResourceBridge -imageDir $using:csv_path\imageStore -skipHostLimitChecks -cloudConfigLocation $using:csv_path\cloudStore -catalog aks-hci-stable-catalogs-ext -ring stable -CloudServiceIP $using:cloudServiceIP -createAutoConfigContainers $false - Install-Moc - } -} - -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - New-ArcHciConfigFiles -subscriptionID $using:subId -location eastus -resourceGroup $using:rg -resourceName $using:resource_name -workDirectory $using:csv_path\ResourceBridge -vnetName $using:SDNConfig.AKSvSwitchName -vswitchName $using:SDNConfig.AKSvSwitchName -ipaddressprefix $using:SDNConfig.AKSIPPrefix -gateway $using:SDNConfig.AKSGWIP -dnsservers $using:SDNConfig.AKSDNSIP -controlPlaneIP $using:SDNConfig.rbCpip -k8snodeippoolstart $using:SDNConfig.rbIp -k8snodeippoolend $using:SDNConfig.rbIp2 -vlanID $using:SDNConfig.AKSVlanID -} - -$ErrorActionPreference = "Continue" -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - Write-Host "Deploying Arc Resource Bridge. This will take a while." - $WarningPreference = "SilentlyContinue" - [System.Environment]::SetEnvironmentVariable('Path', [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\",[System.EnvironmentVariableTarget]::Machine) - $Env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") - az login --service-principal --username $using:spnClientID --password=$using:spnSecret --tenant $using:spnTenantId - az provider register -n Microsoft.ResourceConnector --wait - az arcappliance validate hci --config-file $using:csv_path\ResourceBridge\hci-appliance.yaml --only-show-errors - az arcappliance prepare hci --config-file $using:csv_path\ResourceBridge\hci-appliance.yaml --only-show-errors - az arcappliance deploy hci --config-file $using:csv_path\ResourceBridge\hci-appliance.yaml --outfile $env:USERPROFILE\.kube\config --only-show-errors - az arcappliance create hci --config-file $using:csv_path\ResourceBridge\hci-appliance.yaml --kubeconfig $env:USERPROFILE\.kube\config --only-show-errors - - $rbReady = $false - Do { - Write-Host "Waiting on Arc Resource Bridge deployment to complete..." - Start-Sleep 60 - $readiness = az arcappliance show --resource-group $using:rg --name $using:resource_name --only-show-errors | ConvertFrom-Json - if (($readiness.provisioningState -eq "Succeeded") -and ($readiness.status -eq "Connected")) { - $rbReady = $true - } - } Until ($rbReady) - Write-Host "Arc Resource Bridge deployment complete." - - # Configuring custom location - Write-Host "Creating custom location" - $hciClusterId= (Get-AzureStackHci).AzureResourceUri - az k8s-extension create --cluster-type appliances --cluster-name $using:resource_name --resource-group $using:rg --name hci-vmoperator --extension-type Microsoft.AZStackHCI.Operator --scope cluster --release-namespace helm-operator2 --configuration-settings Microsoft.CustomLocation.ServiceAccount=hci-vmoperator --configuration-protected-settings-file $using:csv_path\ResourceBridge\hci-config.json --configuration-settings HCIClusterID=$hciClusterId --auto-upgrade true --only-show-errors - Start-Sleep -Seconds 90 - - $clReady = $false - Do { - Write-Host "Waiting for custom location to provision..." - Start-Sleep 10 - $readiness = az k8s-extension show --cluster-type appliances --cluster-name $using:resource_name --resource-group $using:rg --name hci-vmoperator --only-show-errors | ConvertFrom-Json - if ($readiness.provisioningState -eq "Succeeded") { - $clReady = $true - } - } Until ($clReady) - - az customlocation create --resource-group $using:rg --name $using:custom_location_name --cluster-extension-ids "/subscriptions/$using:subId/resourceGroups/$using:rg/providers/Microsoft.ResourceConnector/appliances/$using:resource_name/providers/Microsoft.KubernetesConfiguration/extensions/hci-vmoperator" --namespace hci-vmoperator --host-resource-id "/subscriptions/$using:subId/resourceGroups/$using:rg/providers/Microsoft.ResourceConnector/appliances/$using:resource_name" --location $using:location --only-show-errors - Write-Host "Custom location created." - $WarningPreference = "Continue" -} -$ErrorActionPreference = "Continue" - -# Copy gallery VHDs to hosts -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - New-Item -Name "VHD" -Path $using:csv_path -ItemType Directory -Force - Move-Item -Path "C:\VHD\GUI.vhdx" -Destination "$using:csv_path\VHD\GUI.vhdx" -Force - Move-Item -Path "C:\VHD\Ubuntu.vhdx" -Destination "$using:csv_path\VHD\Ubuntu.vhdx" -Force -} - -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - $vnetName="vlan200" - # New-MocGroup -name "Default_Group" -location "MocLocation" ## No longer needed with appliance version 0.2.31 - New-MocVirtualNetwork -name "$vnetName" -group "Default_Group" -tags @{'VSwitch-Name' = "sdnSwitch"} -vlanID $using:SDNConfig.AKSVlanID - [System.Environment]::SetEnvironmentVariable('Path', [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\",[System.EnvironmentVariableTarget]::Machine) - $Env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") - az azurestackhci virtualnetwork create --subscription $using:subId --resource-group $using:rg --extended-location name="/subscriptions/$using:subId/resourceGroups/$using:rg/providers/Microsoft.ExtendedLocation/customLocations/$using:custom_location_name" type="CustomLocation" --location $using:location --network-type "Transparent" --name $vnetName --vlan $using:SDNConfig.AKSVlanID --only-show-errors - - $galleryImageName = "ubuntu20" - $galleryImageSourcePath="$using:csv_path\VHD\Ubuntu.vhdx" - $osType="Linux" - az azurestackhci galleryimage create --subscription $using:subId --resource-group $using:rg --extended-location name="/subscriptions/$using:subId/resourceGroups/$using:rg/providers/Microsoft.ExtendedLocation/customLocations/$using:custom_location_name" type="CustomLocation" --location $using:location --image-path $galleryImageSourcePath --name $galleryImageName --os-type $osType --only-show-errors - - $galleryImageName = "win2k22" - $galleryImageSourcePath="$using:csv_path\VHD\GUI.vhdx" - $osType="Windows" - az azurestackhci galleryimage create --subscription $using:subId --resource-group $using:rg --extended-location name="/subscriptions/$using:subId/resourceGroups/$using:rg/providers/Microsoft.ExtendedLocation/customLocations/$using:custom_location_name" type="CustomLocation" --location $using:location --image-path $galleryImageSourcePath --name $galleryImageName --os-type $osType --only-show-errors -} - -# Set env variable deployResourceBridge to true (in case the script was run manually) -[System.Environment]::SetEnvironmentVariable('deployResourceBridge', 'true',[System.EnvironmentVariableTarget]::Machine) -Stop-Transcript diff --git a/azure_jumpstart_hcibox/artifacts/Deploy-GitOps.ps1 b/azure_jumpstart_hcibox/artifacts/Deploy-GitOps.ps1 deleted file mode 100644 index 2a445a9f81..0000000000 --- a/azure_jumpstart_hcibox/artifacts/Deploy-GitOps.ps1 +++ /dev/null @@ -1,210 +0,0 @@ -$WarningPreference = "SilentlyContinue" -$ErrorActionPreference = "Stop" -$ProgressPreference = 'SilentlyContinue' -# Set paths -$Env:HCIBoxDir = "C:\HCIBox" -$Env:HCIBoxLogsDir = "C:\HCIBox\Logs" -$Env:HCIBoxVMDir = "C:\HCIBox\Virtual Machines" -$Env:HCIBoxKVDir = "C:\HCIBox\KeyVault" -$Env:HCIBoxGitOpsDir = "C:\HCIBox\GitOps" -$Env:HCIBoxIconDir = "C:\HCIBox\Icons" -$Env:HCIBoxVHDDir = "C:\HCIBox\VHD" -$Env:HCIBoxSDNDir = "C:\HCIBox\SDN" -$Env:HCIBoxWACDir = "C:\HCIBox\Windows Admin Center" -$Env:agentScript = "C:\HCIBox\agentScript" -$Env:ToolsDir = "C:\Tools" -$Env:tempDir = "C:\Temp" -$Env:VMPath = "C:\VMs" - -$ingressNamespace = "ingress-nginx" - -$certname = "ingress-cert" -$certdns = "hcibox.devops.com" - -$appClonedRepo = "https://github.com/microsoft/azure-arc-jumpstart-apps" - -if ($host.Name -match 'ISE') {throw "Running this script in PowerShell ISE is not supported"} - -try { - Start-Transcript -Path $Env:HCIBoxLogsDir\Deploy-GitOps.log -} -catch { - Start-Transcript -Path $Env:HCIBoxLogsDir\Deploy-GitOps.log -} - -# Import Configuration Module -$ConfigurationDataFile = "$Env:HCIBoxDir\HCIBox-Config.psd1" -$SDNConfig = Import-PowerShellDataFile -Path $ConfigurationDataFile -$user = "jumpstart.local\administrator" -$password = ConvertTo-SecureString -String $SDNConfig.SDNAdminPassword -AsPlainText -Force -$adcred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user, $password - -$Env:AZURE_CONFIG_DIR = $cliDir.FullName - -# Required for CLI commands -Write-Header "Az CLI Login" -az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId -az config set extension.use_dynamic_install=yes_without_prompt - -# Required for azcopy -$azurePassword = ConvertTo-SecureString $Env:spnClientSecret -AsPlainText -Force -$psCred = New-Object System.Management.Automation.PSCredential($Env:spnClientID , $azurePassword) -Connect-AzAccount -Credential $psCred -TenantId $Env:spnTenantId -ServicePrincipal - -# Setting kubeconfig -$clusterName = az connectedk8s list --resource-group $Env:resourceGroup --query "[].{Name:name} | [? contains(Name,'hcibox')]" --output tsv -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - Get-AksHciCredential -name $using:clusterName -Confirm:$false - kubectl get nodes - foreach ($namespace in @('hello-arc')) { - kubectl create namespace $namespace - } -} - -# Create random 13 character string for Key Vault name -$strLen = 13 -$randStr = (-join ((0x30..0x39) + (0x61..0x7A) | Get-Random -Count $strLen | ForEach-Object {[char]$_})) -$keyVaultName = "HCIBox-KV-$randStr" - -[System.Environment]::SetEnvironmentVariable('keyVaultName', $keyVaultName, [System.EnvironmentVariableTarget]::Machine) - -# Create Azure Key Vault -Write-Header "Creating Azure KeyVault" -az keyvault create --name $keyVaultName --resource-group $Env:resourceGroup --location $Env:azureLocation - -# Allow SPN to import certificates into Key Vault -Write-Header "Setting KeyVault Access Policies" -az keyvault set-policy --name $keyVaultName --spn $Env:spnClientID --key-permissions --secret-permissions get --certificate-permissions get list import - -# Making extension install dynamic -az config set extension.use_dynamic_install=yes_without_prompt -Write-Host "`n" -az -v - -############################# -# - Apply GitOps Configs -############################# - -Write-Header "Applying GitOps Configs" - -# Create GitOps config for NGINX Ingress Controller -Write-Host "Creating GitOps config for NGINX Ingress Controller" -az k8s-configuration flux create ` - --cluster-name $clusterName ` - --resource-group $Env:resourceGroup ` - --name config-nginx ` - --namespace $ingressNamespace ` - --cluster-type connectedClusters ` - --scope cluster ` - --url $appClonedRepo ` - --branch main --sync-interval 3s ` - --kustomization name=nginx path=./nginx/release - -# Create GitOps config for Hello-Arc application -Write-Host "Creating GitOps config for Hello-Arc application" -az k8s-configuration flux create ` - --cluster-name $clusterName ` - --resource-group $Env:resourceGroup ` - --name config-helloarc ` - --namespace hello-arc ` - --cluster-type connectedClusters ` - --scope namespace ` - --url $appClonedRepo ` - --branch main --sync-interval 3s ` - --kustomization name=helloarc path=./hello-arc/yaml - -################################################ -# - Install Key Vault Extension / Create Ingress -################################################ - -Write-Header "Installing KeyVault Extension" - -Write-Host "Generating a TLS Certificate" -$cert = New-SelfSignedCertificate -DnsName $certdns -KeyAlgorithm RSA -KeyLength 2048 -NotAfter (Get-Date).AddYears(1) -CertStoreLocation "Cert:\CurrentUser\My" -$certPassword = ConvertTo-SecureString -String "arcbox" -Force -AsPlainText -Export-PfxCertificate -Cert "cert:\CurrentUser\My\$($cert.Thumbprint)" -FilePath "$Env:TempDir\$certname.pfx" -Password $certPassword -Copy-VMFile AzSMGMT -SourcePath "$Env:TempDir\$certname.pfx" -DestinationPath "C:\VMConfigs\$certname.pfx" -FileSource Host -$localCred = new-object -typename System.Management.Automation.PSCredential -argumentlist "Administrator", (ConvertTo-SecureString $SDNConfig.SDNAdminPassword -AsPlainText -Force) -Invoke-Command -VMName AzSMGMT -Credential $localcred -ScriptBlock { - Enable-VMIntegrationService -VMName AdminCenter -Name "Guest Service Interface" -} -Start-Sleep 20 -Invoke-Command -VMName AzSMGMT -Credential $localcred -ScriptBlock { - Copy-VMFile AdminCenter -SourcePath "C:\VMConfigs\$using:certname.pfx" -DestinationPath "C:\VHDs\$using:certname.pfx" -FileSource Host -} -Invoke-Command -ComputerName AdminCenter -Credential $adcred -ScriptBlock { - Import-PfxCertificate -FilePath "C:\VHDs\$using:certname.pfx" -CertStoreLocation Cert:\LocalMachine\Root -Password $using:certPassword -} - -Write-Host "Importing the TLS certificate to Key Vault" -az keyvault certificate import ` - --vault-name $keyVaultName ` - --password "arcbox" ` - --name $certname ` - --file "$Env:TempDir\$certname.pfx" - -Write-Host "Installing Azure Key Vault Kubernetes extension instance" -az k8s-extension create --name 'akvsecretsprovider' ` - --extension-type Microsoft.AzureKeyVaultSecretsProvider ` - --scope cluster ` - --cluster-name $clusterName ` - --resource-group $Env:resourceGroup ` - --cluster-type connectedClusters ` - --release-namespace kube-system ` - --configuration-settings 'secrets-store-csi-driver.enableSecretRotation=true' 'secrets-store-csi-driver.syncSecret.enabled=true' - -# Replace Variable values -Invoke-WebRequest ($env:templateBaseUrl + "artifacts/devops_ingress/hello-arc.yaml") -OutFile $Env:HCIBoxKVDir\hello-arc.yaml -Get-ChildItem -Path $Env:HCIBoxKVDir | - ForEach-Object { - (Get-Content -path $_.FullName -Raw) -Replace '\{JS_CERTNAME}', $certname | Set-Content -Path $_.FullName - (Get-Content -path $_.FullName -Raw) -Replace '\{JS_KEYVAULTNAME}', $keyVaultName | Set-Content -Path $_.FullName - (Get-Content -path $_.FullName -Raw) -Replace '\{JS_HOST}', $certdns | Set-Content -Path $_.FullName - (Get-Content -path $_.FullName -Raw) -Replace '\{JS_TENANTID}', $Env:spnTenantId | Set-Content -Path $_.FullName - } - -Write-Header "Creating Ingress Controller" - -# Deploy Ingress resources for Bookstore and Hello-Arc App -Copy-VMFile $SDNConfig.HostList[0] -SourcePath "$Env:HCIBoxKVDir\hello-arc.yaml" -DestinationPath "C:\VHD\hello-arc.yaml" -FileSource Host -$clientId = $env:spnClientID -$clientSecret = $env:spnClientSecret -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - foreach ($namespace in @('hello-arc')) { - # Create the Kubernetes secret with the service principal credentials - kubectl create secret generic secrets-store-creds --namespace $namespace --from-literal clientid=$using:clientId --from-literal clientsecret=$using:clientSecret - kubectl --namespace $namespace label secret secrets-store-creds secrets-store.csi.k8s.io/used=true - - # Deploy Key Vault resources and Ingress for Book Store and Hello-Arc App - kubectl --namespace $namespace apply -f "C:\VHD\hello-arc.yaml" - } -} -[string]$ip = Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - $ip = kubectl get service/ingress-nginx-controller --namespace $using:ingressNamespace --output=jsonpath='{.status.loadBalancer.ingress[0].ip}' - return $ip -} -# Insert into HOSTS file -Invoke-Command -ComputerName AdminCenter -Credential $adcred -ScriptBlock { - Add-Content -Path $Env:windir\System32\drivers\etc\hosts -Value "`n`t$using:ip`t$using:certdns" -Force -} - -Write-Header "Creating Desktop Icons" - -# # Creating CAPI Hello Arc Icon on Desktop -Invoke-WebRequest ($env:templateBaseUrl + "artifacts/icons/arc.ico") -OutFile $Env:HCIBoxIconDir\arc.ico -Copy-VMFile AzSMGMT -SourcePath "$Env:HCIBoxIconDir\arc.ico" -DestinationPath "C:\VMConfigs\arc.ico" -FileSource Host -Invoke-Command -VMName AzSMGMT -Credential $localcred -ScriptBlock { - Copy-VMFile AdminCenter -SourcePath "C:\VMConfigs\arc.ico" -DestinationPath "C:\VHDs\arc.ico" -FileSource Host -} - -Invoke-Command -ComputerName AdminCenter -Credential $adcred -ScriptBlock { - $shortcutLocation = "$Env:Public\Desktop\Hello-Arc.lnk" - $wScriptShell = New-Object -ComObject WScript.Shell - $shortcut = $wScriptShell.CreateShortcut($shortcutLocation) - $shortcut.TargetPath = "https://$using:certdns" - $shortcut.IconLocation="C:\VHDs\arc.ico, 0" - $shortcut.WindowStyle = 3 - $shortcut.Save() -} - -Stop-Transcript diff --git a/azure_jumpstart_hcibox/artifacts/Deploy-SQLMI.ps1 b/azure_jumpstart_hcibox/artifacts/Deploy-SQLMI.ps1 deleted file mode 100644 index d8ad10bb86..0000000000 --- a/azure_jumpstart_hcibox/artifacts/Deploy-SQLMI.ps1 +++ /dev/null @@ -1,610 +0,0 @@ -$WarningPreference = 'SilentlyContinue' -$ProgressPreference = 'SilentlyContinue' - -# Set paths -$Env:HCIBoxDir = "C:\HCIBox" -$Env:HCIBoxLogsDir = "C:\HCIBox\Logs" -$Env:HCIBoxVMDir = "C:\HCIBox\Virtual Machines" -$Env:HCIBoxKVDir = "C:\HCIBox\KeyVault" -$Env:HCIBoxIconDir = "C:\HCIBox\Icons" -$Env:HCIBoxVHDDir = "C:\HCIBox\VHD" -$Env:HCIBoxSDNDir = "C:\HCIBox\SDN" -$Env:HCIBoxWACDir = "C:\HCIBox\Windows Admin Center" -$Env:agentScript = "C:\HCIBox\agentScript" -$Env:ToolsDir = "C:\Tools" -$Env:tempDir = "C:\Temp" -$Env:VMPath = "C:\VMs" - -Start-Transcript -Path $Env:HCIBoxLogsDir\Deploy-SQLMI.log - -# Capture Custom Location ObjectID -do { - $customLocationObjectId = Read-Host -Prompt "Custom Location Object Id" - if ($customLocationObjectId -eq "" -or $customLocationObjectId.Length -ne 36) { write-host "Please enter a valid Custom Location Object Id to proceed!" } -} -until ($customLocationObjectId -ne "" -and $customLocationObjectId.Length -eq 36) - -# Import Configuration Module and create Azure login credentials -Write-Header 'Importing config' -$ConfigurationDataFile = 'C:\HCIBox\HCIBox-Config.psd1' -$SDNConfig = Import-PowerShellDataFile -Path $ConfigurationDataFile - -# Generate credential objects -Write-Header 'Creating credentials and connecting to Azure' -$user = "jumpstart.local\administrator" -$password = ConvertTo-SecureString -String $SDNConfig.SDNAdminPassword -AsPlainText -Force -$adcred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user, $password # Domain credential - -$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 -$context = Get-AzContext # Azure credential - -Register-AzResourceProvider -ProviderNamespace Microsoft.Kubernetes -Confirm:$false -Register-AzResourceProvider -ProviderNamespace Microsoft.KubernetesConfiguration -Confirm:$false - -# Install latest versions of Nuget and PowershellGet -Write-Header "Install latest versions of Nuget and PowershellGet" -Invoke-Command -VMName $SDNConfig.HostList -Credential $adcred -ScriptBlock { - Enable-PSRemoting -Force - $ProgressPreference = "SilentlyContinue" - Install-PackageProvider -Name NuGet -Force - Set-PSRepository -Name "PSGallery" -InstallationPolicy Trusted - Install-Module -Name PowershellGet -Force - $ProgressPreference = "Continue" -} - -# Install necessary AZ modules and initialize akshci on each node -Write-Header "Install necessary AZ modules, AZ CLI extensions, plus AksHCI module and initialize akshci on each node" - -Invoke-Command -VMName $SDNConfig.HostList -Credential $adcred -ScriptBlock { - Write-Host "Installing Required Modules" - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - $ProgressPreference = "SilentlyContinue" - Install-Module -Name AksHci -Force -AcceptLicense - Import-Module Az.Accounts -DisableNameChecking - Import-Module Az.Resources -DisableNameChecking - Import-Module AzureAD -DisableNameChecking - Import-Module AksHci -DisableNameChecking - Initialize-AksHciNode - $ProgressPreference = "Continue" -} - -# Downloading artifacts for Azure Arc Data services -Write-Header "Downloading artifacts for Azure Arc Data services" -Invoke-WebRequest ($env:templateBaseUrl + "artifacts/dataController.json") -OutFile $Env:HCIBoxDir\dataController.json -Invoke-WebRequest ($env:templateBaseUrl + "artifacts/dataController.parameters.json") -OutFile $Env:HCIBoxDir\dataController.parameters.json -Invoke-WebRequest ($env:templateBaseUrl + "artifacts/adConnector.json") -OutFile $Env:HCIBoxDir\adConnector.json -Invoke-WebRequest ($env:templateBaseUrl + "artifacts/adConnector.parameters.json") -OutFile $Env:HCIBoxDir\adConnector.parameters.json -Invoke-WebRequest ($env:templateBaseUrl + "artifacts/sqlmiAD.json") -OutFile $Env:HCIBoxDir\sqlmiAD.json -Invoke-WebRequest ($env:templateBaseUrl + "artifacts/sqlmiAD.parameters.json") -OutFile $Env:HCIBoxDir\sqlmiAD.parameters.json -Invoke-WebRequest ($env:templateBaseUrl + "artifacts/settingsTemplate.json") -OutFile $Env:HCIBoxDir\settingsTemplate.json -Invoke-WebRequest ("https://azuredatastudio-update.azurewebsites.net/latest/win32-x64-archive/stable") -OutFile $Env:HCIBoxDir\azuredatastudio.zip -Invoke-WebRequest "https://aka.ms/azdata-msi" -OutFile $Env:HCIBoxDir\AZDataCLI.msi -Invoke-WebRequest "https://github.com/ErikEJ/SqlQueryStress/releases/download/102/SqlQueryStressNet6.zip" -OutFile $Env:HCIBoxDir\SqlQueryStress.zip - -Copy-VMFile $SDNConfig.HostList[0] -SourcePath "$Env:HCIBoxDir\dataController.json" -DestinationPath "C:\VHD\dataController.json" -FileSource Host -Force -Copy-VMFile $SDNConfig.HostList[0] -SourcePath "$Env:HCIBoxDir\dataController.parameters.json" -DestinationPath "C:\VHD\dataController.parameters.json" -FileSource Host -Force -Copy-VMFile $SDNConfig.HostList[0] -SourcePath "$Env:HCIBoxDir\adConnector.json" -DestinationPath "C:\VHD\adConnector.json" -FileSource Host -Force -Copy-VMFile $SDNConfig.HostList[0] -SourcePath "$Env:HCIBoxDir\adConnector.parameters.json" -DestinationPath "C:\VHD\adConnector.parameters.json" -FileSource Host -Force -Copy-VMFile $SDNConfig.HostList[0] -SourcePath "$Env:HCIBoxDir\sqlmiAD.json" -DestinationPath "C:\VHD\sqlmiAD.json" -FileSource Host -Force -Copy-VMFile $SDNConfig.HostList[0] -SourcePath "$Env:HCIBoxDir\sqlmiAD.parameters.json" -DestinationPath "C:\VHD\sqlmiAD.parameters.json" -FileSource Host -Force -Copy-VMFile $SDNConfig.HostList[0] -SourcePath "$Env:HCIBoxDir\settingsTemplate.json" -DestinationPath "C:\VHD\settingsTemplate.json" -FileSource Host -Force -Copy-VMFile $SDNConfig.HostList[0] -SourcePath "$Env:HCIBoxDir\azuredatastudio.zip" -DestinationPath "C:\VHD\azuredatastudio.zip" -FileSource Host -Force -Copy-VMFile $SDNConfig.HostList[0] -SourcePath "$Env:HCIBoxDir\AZDataCLI.msi" -DestinationPath "C:\VHD\AZDataCLI.msi" -FileSource Host -Force -$adminCenterSession = New-PSSession -ComputerName "admincenter" -Credential $adcred -Copy-Item $Env:HCIBoxDir\azuredatastudio.zip -Destination "C:\VHDs\azuredatastudio.zip" -ToSession $adminCenterSession -Force -Copy-Item $Env:HCIBoxDir\SqlQueryStress.zip -Destination "C:\VHDs\SqlQueryStress.zip" -ToSession $adminCenterSession -Force - -# Generate unique name for workload cluster -$rand = New-Object System.Random -$prefixLen = 5 -[string]$namingPrefix = '' -for ($i = 0; $i -lt $prefixLen; $i++) { - $namingPrefix += [char]$rand.Next(97, 122) -} -# Get cluster name -$clusterName = $env:AKSClusterName -[System.Environment]::SetEnvironmentVariable('AKS-sqlmi-ClusterName', $clusterName, [System.EnvironmentVariableTarget]::Machine) - -# Initializing variables -$subId = $env:subscriptionId -$rg = $env:resourceGroup -$spnClientId = $env:spnClientId -$spnSecret = $env:spnClientSecret -$spnTenantId = $env:spnTenantId -$adminUsername = $env:adminUsername -$adminPassword = $SDNConfig.SDNAdminPassword -$workspaceName = $env:workspaceName -$dataController = "jumpstart-dc-$namingPrefix" -$sqlMI = "jumpstart-sql" -$customLocation = "jumpstart-cl-$namingPrefix" -$domainName = "jumpstart" -$defaultDomainPartition = "DC=$domainName,DC=local" - -# Deploying the Arc Data Controller -Write-Header "Deploying the Arc Data extension" -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - [System.Environment]::SetEnvironmentVariable('Path', $env:Path + ";C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2\wbin", [System.EnvironmentVariableTarget]::Machine) - $Env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") - az config set extension.use_dynamic_install=yes_without_prompt --only-show-errors - az login --service-principal --username $using:spnClientID --password=$using:spnSecret --tenant $using:spnTenantId - az extension add --name arcdata --system --only-show-errors - Get-AksHciCredential -name $using:clusterName -Confirm:$false - Write-Host "Installing the Arc Data extension" - Write-Host "`n" - az k8s-extension create --name arc-data-services ` - --extension-type microsoft.arcdataservices ` - --cluster-type connectedClusters ` - --cluster-name $using:clusterName ` - --resource-group $using:rg ` - --auto-upgrade false ` - --scope cluster ` - --version 1.26.0 ` - --release-namespace arc ` - --config Microsoft.CustomLocation.ServiceAccount=sa-bootstrapper ` - --only-show-errors - - Write-Host "`n" - - Do { - Write-Host "Waiting for bootstrapper pod, hold tight..." - Write-Host "`n" - kubectl get pods -n arc - Write-Host "`n" - Start-Sleep -Seconds 20 - $podStatus = $(if (kubectl get pods -n arc | Select-String "bootstrapper" | Select-String "Running" -Quiet) { "Ready!" }Else { "Nope" }) - } while ($podStatus -eq "Nope") - Write-Host "Bootstrapper pod is ready!" - Write-Host "`n" -} - -# Configuring Azure Arc Custom Location on the cluster -Write-Header "Deploying the Azure Arc Data Controller and Custom Location" -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - Get-AksHciCredential -name $using:clusterName -Confirm:$false - Write-Host "Creating the Azure Arc Custom Location" - Write-Host "`n" - $connectedClusterId = az connectedk8s show --name $using:clusterName --resource-group $using:rg --query id -o tsv --only-show-errors - az connectedk8s enable-features -n $using:clusterName -g $using:rg --custom-locations-oid $using:customLocationObjectId --features cluster-connect custom-locations --only-show-errors - $extensionId = az k8s-extension show --name arc-data-services --cluster-type connectedClusters --cluster-name $using:clusterName --resource-group $using:rg --query id -o tsv - Start-Sleep -Seconds 20 - az customlocation create --name $using:customLocation --resource-group $using:rg --namespace arc --host-resource-id $connectedClusterId --cluster-extension-ids $extensionId --only-show-errors --location eastus - - $customLocationId = $(az customlocation show --name $using:customLocation --resource-group $using:rg --query id -o tsv) - - $workspaceId = $(az resource show --resource-group $using:rg --name $using:workspaceName --resource-type "Microsoft.OperationalInsights/workspaces" --query properties.customerId -o tsv) - $workspaceKey = $(az monitor log-analytics workspace get-shared-keys --resource-group $using:rg --workspace-name $using:workspaceName --query primarySharedKey -o tsv) - - Write-Host "Deploying the Azure Arc Data Controller" - Write-Host "`n" - $dataControllerParams = "C:\VHD\dataController.parameters.json" - - (Get-Content -Path $dataControllerParams) -replace 'dataControllerName-stage', $using:dataController | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'resourceGroup-stage', $using:rg | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'azdataUsername-stage', $using:adminUsername | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'azdataPassword-stage', $using:adminPassword | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'customLocation-stage', $customLocationId | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'subscriptionId-stage', $using:subId | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'spnClientId-stage', $using:spnClientId | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'spnTenantId-stage', $using:spnTenantId | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'spnClientSecret-stage', $using:spnSecret | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'logAnalyticsWorkspaceId-stage', $workspaceId | Set-Content -Path $dataControllerParams - (Get-Content -Path $dataControllerParams) -replace 'logAnalyticsPrimaryKey-stage', $workspaceKey | Set-Content -Path $dataControllerParams - - az deployment group create --resource-group $using:rg --template-file "C:\VHD\dataController.json" --parameters "C:\VHD\dataController.parameters.json" - Write-Host "`n" - - Do { - Write-Host "Waiting for data controller. Hold tight, this might take a few minutes..." - Start-Sleep -Seconds 50 - Write-Host "`n" - kubectl get pods -n arc - Write-Host "`n" - Start-Sleep -Seconds 10 - $dcStatus = $(if (kubectl get datacontroller -n arc | Select-String "Ready" -Quiet) { "Ready!" }Else { "Nope" }) - } while ($dcStatus -eq "Nope") - Write-Host "Azure Arc data controller is ready!" - Write-Host "`n" -} - -# Preparing AD for SQL MI AD authenticaion -Write-Header "Deploying the Azure Arc-enabled SQL Managed Instance" -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - $ErrorActionPreference = 'SilentlyContinue' - $WarningPreference = 'SilentlyContinue' - Add-WindowsFeature -Name "RSAT-AD-PowerShell" -IncludeAllSubFeature - Add-WindowsFeature -Name "RSAT-DNS-Server" -IncludeAllSubFeature - Get-AksHciCredential -name $using:clusterName -Confirm:$false - $dcInfo = Get-ADDomainController -discover -domain $using:domainName - $sqlmiouName = "ArcSQLMI" - $sqlmiOUDN = "OU=" + $sqlmiouName + "," + $using:defaultDomainPartition - $sqlmi_port = 31433 - $adminUsername = $using:adminUsername - $adminPassword = $using:adminPassword - $dcIPv4 = ([System.Net.IPAddress]$dcInfo.IPv4Address).GetAddressBytes() - $dcIpv4Secondary = ([System.Net.IPAddress]$using:SDNConfig.dcVLAN200IP).GetAddressBytes() - $reverseLookupCidr = [System.String]::Concat($dcIPv4[0], '.', $dcIPv4[1], '.', $dcIPv4[2], '.0/24') - $reverseLookupSecondaryCidr = [System.String]::Concat($dcIpv4Secondary[0], '.', $dcIpv4Secondary[1], '.', $dcIpv4Secondary[2], '.0/24') - - Write-Host "Reverse lookup zone CIDR $reverseLookupCidr" - # Setup reverse lookup zone - # check if reverse DNS already setup - $ReverseDnsZone = Get-DnsServerZone -ComputerName $dcInfo.HostName | Where-Object { $_.IsAutoCreated -eq $false -and $_.IsReverseLookupZone -eq $true } - if ($null -eq $ReverseDnsZone) { - try { - Add-DnsServerPrimaryZone -NetworkId $reverseLookupCidr -ReplicationScope Domain -ComputerName $dcInfo.HostName - Add-DnsServerPrimaryZone -NetworkId $reverseLookupSecondaryCidr -ReplicationScope Domain -ComputerName $dcInfo.HostName - Write-Host "Successfully created reverse DNS Zone." - - $ReverseDnsZone = Get-DnsServerZone -ComputerName $dcInfo.HostName | Where-Object { $_.IsAutoCreated -eq $false -and $_.IsReverseLookupZone -eq $true } - } - catch { - # Reverse DNS already setup - Write-Host "Failed to create Reverse DNS Zone." - Exit - } - } - else { - Write-Host "Reverse DNS Zone ${ReverseDnsZone.Name} already exists for this domain controller." - } - - # Create reverse DNS for domain controller - if ($null -ne $ReverseDnsZone) { - try { - Add-DNSServerResourceRecordPTR -ZoneName $ReverseDnsZone[0].ZoneName -Name $dcIPv4[3] -PTRDomainName $dcInfo.HostName -ComputerName $dcInfo.HostName - Add-DNSServerResourceRecordPTR -ZoneName $ReverseDnsZone[1].ZoneName -Name $dcIpv4Secondary[3] -PTRDomainName $dcInfo.HostName -ComputerName $dcInfo.HostName - Write-Host "Created PTR record for domain controller." - } - catch { - Write-Host "Failed to create domain controller PTR record or PTR record already exists." - } - } - else { - Write-Host "Failed to create reverse DNS lookup zone or zone does not exist." - Exit - } - - # Create ArcSQLMI OU - Write-Host "Creating the SQL MI OU in Active directory" - Write-Host "`n" - try { - $ou = Get-ADOrganizationalUnit -Identity $sqlmiOUDN - if ($null -ne $ou -and $ou.Name.Length -gt 0) { - Write-Host "Organization Unit $sqlmiouName already exist. Skipping this step." - } - else { - Write-Host "Organization Unit $sqlmiouName does not exist. Creating new OU." - New-ADOrganizationalUnit -Name $sqlmiouName -Path $using:defaultDomainPartition -ProtectedFromAccidentalDeletion $False - } - } - catch { - Write-Host "Organization Unit $sqlmiOu does not exist. Creating new OU." - New-ADOrganizationalUnit -Name $sqlmiouName -Path $using:defaultDomainPartition -ProtectedFromAccidentalDeletion $False - } - - # Creating endpoints file - Write-Host "Creating endpoints file" - Write-Host "`n" - $filename = "SQLMIEndpoints.txt" - $file = New-Item -Path "C:\VHD" -Name $filename -ItemType "file" - $Endpoints = $file.FullName - - $sqlMIName = $using:sqlMI - $sqlmi_fqdn_name = $sqlMIName + "." + $dcInfo.domain - $sqlmi_secondary_fqdn_name = $sqlMIName + "-secondary." + $dcInfo.domain - - # Create dedicated service account for AD connector - Write-Host "Creating dedicated service account for AD connector" - Write-Host "`n" - $arcsaname = "sa-$sqlMIName" - $arcsapass = "ArcDSA#Pwd123$" - $arcsasecpass = $arcsapass | ConvertTo-SecureString -AsPlainText -Force - $sqlmisaupn = $arcsaname + "@" + $dcInfo.domain - - $samaccountname = $arcsaname - $domain_netbios_name = $dcInfo.domain.split('.')[0].ToUpper(); - $domain_name = $dcInfo.domain.ToUpper() - - try { - New-ADUser -Name $arcsaname ` - -UserPrincipalName $sqlmisaupn ` - -Path $sqlmiOUDN ` - -AccountPassword $arcsasecpass ` - -Enabled $true ` - -ChangePasswordAtLogon $false ` - -PasswordNeverExpires $true - } - catch { - # User already exists - Write-Host "User $arcsaname already existings in the directory." - } - - Start-Sleep -Seconds 10 - # Geneate key tab - Write-Host "Gerating key tab for primary and secondary SQL MI instance" - Write-Host "`n" - try { - setspn -A MSSQLSvc/${sqlmi_fqdn_name} ${domain_netbios_name}\${samaccountname} - setspn -A MSSQLSvc/${sqlmi_fqdn_name}:${sqlmi_port} ${domain_netbios_name}\${samaccountname} - - # Secondary instance spn - setspn -A MSSQLSvc/${sqlmi_secondary_fqdn_name} ${domain_netbios_name}\${samaccountname} - setspn -A MSSQLSvc/${sqlmi_secondary_fqdn_name}:${sqlmi_port} ${domain_netbios_name}\${samaccountname} - - $keytab_file = "C:\VHD\$sqlMIName.keytab" - ktpass /princ MSSQLSvc/${sqlmi_fqdn_name}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /out $keytab_file -setpass -setupn /pass $arcsapass - ktpass /princ MSSQLSvc/${sqlmi_fqdn_name}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - ktpass /princ MSSQLSvc/${sqlmi_fqdn_name}:${sqlmi_port}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - ktpass /princ MSSQLSvc/${sqlmi_fqdn_name}:${sqlmi_port}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - - # Generate Keytab for secondary - ktpass /princ MSSQLSvc/${sqlmi_secondary_fqdn_name}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - ktpass /princ MSSQLSvc/${sqlmi_secondary_fqdn_name}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - ktpass /princ MSSQLSvc/${sqlmi_secondary_fqdn_name}:${sqlmi_port}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - ktpass /princ MSSQLSvc/${sqlmi_secondary_fqdn_name}:${sqlmi_port}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - - ktpass /princ ${samaccountname}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto aes256-sha1 /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - ktpass /princ ${samaccountname}@${domain_name} /ptype KRB5_NT_PRINCIPAL /crypto rc4-hmac-nt /mapuser ${domain_netbios_name}\${samaccountname} /in $keytab_file /out $keytab_file -setpass -setupn /pass $arcsapass - # Convert key tab file into base64 data - $keytabrawdata = Get-Content $keytab_file -Encoding byte - $b64keytabtext = [System.Convert]::ToBase64String($keytabrawdata) - # Grant permission to DSA account on SQLMI OU - } - catch { - - } - - Start-Sleep -Seconds 10 - - Write-Host "Deploying Azure Arc AD connecter" - Write-Host "`n" - $adConnectorParams = "C:\VHD\adConnector.parameters.json" - $adConnectorName = $using:dataController + "/adarc" - $serviceAccountProvisioning = "manual" - (Get-Content -Path $adConnectorParams) -replace 'connectorName-stage', $adConnectorName | Set-Content -Path $adConnectorParams - (Get-Content -Path $adConnectorParams) -replace 'domainController-stage', $dcInfo.HostName | Set-Content -Path $adConnectorParams - (Get-Content -Path $adConnectorParams) -replace 'netbiosDomainName-stage', $domain_netbios_name | Set-Content -Path $adConnectorParams - (Get-Content -Path $adConnectorParams) -replace 'realm-stage', $dcInfo.domain.ToUpper() | Set-Content -Path $adConnectorParams - (Get-Content -Path $adConnectorParams) -replace 'serviceAccountProvisioning-stage', $serviceAccountProvisioning | Set-Content -Path $adConnectorParams - (Get-Content -Path $adConnectorParams) -replace 'domainName-stage', $dcInfo.domain.Tolower() | Set-Content -Path $adConnectorParams - - az deployment group create --resource-group $using:rg --name "AD-Connector" --template-file "C:\VHD\adConnector.json" --parameters "C:\VHD\adConnector.parameters.json" - Write-Host "`n" - Do { - Write-Host "Waiting for AD connector deployment. Hold tight, this might take a few minutes...(30s sleeping loop)" - Write-Host "`n" - Start-Sleep -Seconds 15 - kubectl get pods -n arc - Start-Sleep -Seconds 5 - Write-Host "`n" - $adcStatus = $(if (kubectl get adc adarc -n arc | Select-String "Ready" -Quiet) { "Ready!" }Else { "Nope" }) - } while ($adcStatus -eq "Nope") - - Write-Host "`n" - Write-Host "Azure Arc AD connector ready!" - Write-Host "`n" - - # Deploying the Azure Arc-enabled SQL Managed Instance - Write-Host "Deploying the Azure Arc-enabled SQL Managed Instance" - Write-Host "`n" - - $dataControllerId = $(az resource show --resource-group $using:rg --name $using:dataController --resource-type "Microsoft.AzureArcData/dataControllers" --query id -o tsv) - $customLocationId = $(az customlocation show --name $using:customLocation --resource-group $using:rg --query id -o tsv) - - ################################################ - # Localize ARM template - ################################################ - $ServiceType = "LoadBalancer" - $readableSecondaries = $ServiceType - - # Resource Requests - $vCoresRequest = "2" - $memoryRequest = "4Gi" - $vCoresLimit = "4" - $memoryLimit = "8Gi" - - # Storage - $StorageClassName = "default" - $dataStorageSize = "5Gi" - $logsStorageSize = "5Gi" - $dataLogsStorageSize = "5Gi" - - # High Availability - $replicas = 2 # Deploy SQL MI "Business Critical" tier - ####################################################### - - $SQLParams = "C:\VHD\sqlmiAD.parameters.json" - -(Get-Content -Path $SQLParams) -replace 'resourceGroup-stage', $using:rg | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'dataControllerId-stage', $dataControllerId | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'customLocation-stage', $customLocationId | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'subscriptionId-stage', $using:subId | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'azdataUsername-stage', $using:adminUsername | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'azdataPassword-stage', $using:adminPassword | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'serviceType-stage', $ServiceType | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'readableSecondaries-stage', $readableSecondaries | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'vCoresRequest-stage', $vCoresRequest | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'memoryRequest-stage', $memoryRequest | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'vCoresLimit-stage', $vCoresLimit | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'memoryLimit-stage', $memoryLimit | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'dataStorageClassName-stage', $StorageClassName | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'logsStorageClassName-stage', $StorageClassName | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'dataLogStorageClassName-stage', $StorageClassName | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'dataSize-stage', $dataStorageSize | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'logsSize-stage', $logsStorageSize | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'dataLogSize-stage', $dataLogsStorageSize | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'replicasStage' , $replicas | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'sqlInstanceName-stage' , $using:sqlMI | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'keyTab-stage' , $b64keytabtext | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'adAccountName-stage' , $arcsaname | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'adConnectorName-stage' , "adarc" | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'dnsName-stage' , $sqlmi_fqdn_name | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'dnsNameSecondary-stage' , $sqlmi_secondary_fqdn_name | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'port-stage' , $sqlmi_port | Set-Content -Path $SQLParams -(Get-Content -Path $SQLParams) -replace 'licenseType-stage' , "LicenseIncluded" | Set-Content -Path $SQLParams - - az deployment group create --resource-group $using:rg --name $using:sqlMI --template-file "C:\VHD\sqlmiAD.json" --parameters "C:\VHD\sqlmiAD.parameters.json" - Write-Host "`n" - - Do { - Write-Host "Waiting for SQL Managed Instance. Hold tight, this might take a few minutes...(45s sleeping loop)" - Write-Host "`n" - Start-Sleep -Seconds 50 - kubectl get pods -n arc - Write-Host "`n" - Start-Sleep -Seconds 10 - $dcStatus = $(if (kubectl get sqlmanagedinstances -n arc | Select-String "Ready" -Quiet) { "Ready!" }Else { "Nope" }) - } while ($dcStatus -eq "Nope") - Write-Host "Azure Arc SQL Managed Instance is ready!" - Write-Host "`n" - - # Create windows account in SQLMI to support AD authentication and grant sysadmin role - $podname = "${sqlMIName}-0" - kubectl exec $podname -c arc-sqlmi -n arc -- /opt/mssql-tools/bin/sqlcmd -S localhost -U $adminUsername -P "$adminPassword" -Q "CREATE LOGIN [${domain_netbios_name}\$adminUsername] FROM WINDOWS" - Write-Host "Created Windows user account ${domain_netbios_name}\$adminUsername in SQLMI instance." - - kubectl exec $podname -c arc-sqlmi -n arc -- /opt/mssql-tools/bin/sqlcmd -S localhost -U $adminUsername -P "$adminPassword" -Q "EXEC master..sp_addsrvrolemember @loginame = N'${domain_netbios_name}\$adminUsername', @rolename = N'sysadmin'" - Write-Host "Granted sysadmin role to user account ${domain_netbios_name}\$adminUsername in SQLMI instance." - - # Downloading demo database and restoring onto SQL MI - Write-Host "`n" - Write-Host "Downloading AdventureWorks database for MS SQL... (1/2)" - kubectl exec $podname -n arc -c arc-sqlmi -- wget https://github.com/Microsoft/sql-server-samples/releases/download/adventureworks/AdventureWorks2019.bak -O /var/opt/mssql/data/AdventureWorks2019.bak 2>&1 | Out-Null - Write-Host "Restoring AdventureWorks database for MS SQL. (2/2)" - kubectl exec $podname -n arc -c arc-sqlmi -- /opt/mssql-tools/bin/sqlcmd -S localhost -U $adminUsername -P "$adminPassword" -Q "RESTORE DATABASE AdventureWorks2019 FROM DISK = N'/var/opt/mssql/data/AdventureWorks2019.bak' WITH MOVE 'AdventureWorks2019' TO '/var/opt/mssql/data/AdventureWorks2019.mdf', MOVE 'AdventureWorks2019_Log' TO '/var/opt/mssql/data/AdventureWorks2019_Log.ldf'" 2>&1 $null - Write-Host "Restoring AdventureWorks database completed." -} - -# Install Azure Data Studio -Invoke-Command -ComputerName admincenter -Credential $adcred -ScriptBlock { - $adminUsername = $using:adminUsername - Write-Host "Installing Azure Data Studio" - Expand-Archive "C:\VHDs\azuredatastudio.zip" -DestinationPath 'C:\Program Files\Azure Data Studio' - Start-Process msiexec.exe -Wait -ArgumentList "/I C:\VHD\AZDataCLI.msi /quiet" - Write-Host "Installing Azure Data Studio extensions" - $Env:argument1 = "--install-extension" - $Env:argument2 = "microsoft.azcli" - $Env:argument3 = "Microsoft.arc" - - & "C:\Program Files\Azure Data Studio\bin\azuredatastudio.cmd" $Env:argument1 $Env:argument2 - & "C:\Program Files\Azure Data Studio\bin\azuredatastudio.cmd" $Env:argument1 $Env:argument3 - - # Create Azure Data Studio desktop shortcut - Write-Host "Creating Azure Data Studio Desktop Shortcut" - $TargetFile = "C:\Program Files\Azure Data Studio\azuredatastudio.exe" - $ShortcutFile = "C:\Users\$adminUsername\Desktop\Azure Data Studio.lnk" - $WScriptShell = New-Object -ComObject WScript.Shell - $Shortcut = $WScriptShell.CreateShortcut($ShortcutFile) - $Shortcut.TargetPath = $TargetFile - $Shortcut.Save() - - -} - -Write-Header "Configure ADS" -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - $WarningPreference = 'SilentlyContinue' - $ErrorActionPreference = 'silentlycontinue' - $adminUsername = $using:adminUsername - $adminCenterSession = New-PSSession -ComputerName "admincenter" -Credential $using:adcred - Write-Host "Generating endpoints file" - Write-host "`n" - - # Retrieving SQL MI connection endpoint - $sqlMIName = $using:sqlMI - $dcInfo = Get-ADDomainController -discover -domain $using:domainName - Get-AksHciCredential -name $using:clusterName -Confirm:$false - $sqlmiEndPoint = kubectl get SqlManagedInstance $sqlMIName -n arc -o=jsonpath='{.status.endpoints.primary}' - $sqlmiSecondaryEndPoint = kubectl get SqlManagedInstance $sqlMIName -n arc -o=jsonpath='{.status.endpoints.secondary}' - Write-host "`n" - - # Get public ip of the SQLMI endpoint - $sqlmiIpaddress = kubectl get svc -n arc "$sqlMIName-external-svc" -o jsonpath='{.status.loadBalancer.ingress[0].ip}' - Add-DnsServerResourceRecord -ComputerName $dcInfo.HostName -ZoneName $dcInfo.Domain -A -Name $sqlMIName -AllowUpdateAny -IPv4Address $sqlmiIpaddress -TimeToLive 01:00:00 -AgeRecord - - # Get public ip of the secondary SQLMI endpoint - $sqlmiSecondaryIpaddress = kubectl get svc -n arc "$sqlMIName-secondary-external-svc" -o jsonpath='{.status.loadBalancer.ingress[0].ip}' - Add-DnsServerResourceRecord -ComputerName $dcInfo.HostName -ZoneName $dcInfo.Domain -A -Name "$sqlMIName-secondary" -AllowUpdateAny -IPv4Address $sqlmiSecondaryIpaddress -TimeToLive 01:00:00 -AgeRecord - - # Write endpoint information in the file - - Start-Sleep -Seconds 5 - $filename = "SQLMIEndpoints.txt" - $Endpoints = "c:\vhd\$filename.txt" - - Add-Content $Endpoints "======================================================================" - Add-Content $Endpoints "" - Add-Content $Endpoints "$sqlMIName external endpoint DNS name for AD Authentication:" - $sqlmiEndPoint | Add-Content $Endpoints - - Add-Content $Endpoints "" - Add-Content $Endpoints "$sqlMIName secondary external endpoint DNS name for AD Authentication:" - $sqlmiSecondaryEndPoint | Add-Content $Endpoints - - Add-Content $Endpoints "" - Add-Content $Endpoints "SQL Managed Instance SQL login username:" - $using:adminUsername | Add-Content $Endpoints - - Add-Content $Endpoints "" - Add-Content $Endpoints "SQL Managed Instance SQL login password:" - $using:adminPassword | Add-Content $Endpoints - Add-Content $Endpoints "" - - Add-Content $Endpoints "======================================================================" - Add-Content $Endpoints "" - - Copy-Item "c:\VHD\$filename.txt" -Destination "C:\users\$adminUsername\desktop\SQLMI endpoints.txt" -ToSession $adminCenterSession -Force - - write-host "Configuring ADS" - - $settingsTemplate = "c:\VHD\settingsTemplate.json" - $ADSConnections = @" -{ - "options": { - "connectionName": "SQLMI", - "server": "$sqlmiEndPoint", - "database": "", - "authenticationType": "Integrated", - "applicationName": "azdata", - "groupId": "C777F06B-202E-4480-B475-FA416154D458", - "databaseDisplayName": "" - }, - "groupId": "C777F06B-202E-4480-B475-FA416154D458", - "providerName": "MSSQL", - "savePassword": true, - "id": "ac333479-a04b-436b-88ab-3b314a201295" -} -"@ - - $settingsTemplateJson = Get-Content $settingsTemplate | ConvertFrom-Json - $settingsTemplateJson.'datasource.connections'[0] = ConvertFrom-Json -InputObject $ADSConnections - ConvertTo-Json -InputObject $settingsTemplateJson -Depth 3 | Set-Content -Path $settingsTemplate - - New-Item -Path "\\admincenter\c$\users\$adminUsername\AppData\Roaming\azuredatastudio\" -Name "User" -ItemType "directory" -Force - Copy-Item -Path $settingsTemplate -Destination "\\admincenter\c$\users\$adminUsername\AppData\Roaming\azuredatastudio\User\settings.json" - -} - -Write-Header "Configure SQL Query Stress" -# Unzip SqlQueryStress -Invoke-Command -ComputerName admincenter -Credential $adcred -ScriptBlock { - Expand-Archive -Path C:\VHDs\SqlQueryStress.zip -DestinationPath C:\VHDs\SqlQueryStress - - # Create SQLQueryStress desktop shortcut - Write-Host "Installing dotnet sdk" - choco install dotnet-sdk -y -r --no-progress - Write-Host "`n" - Write-Host "Creating SQLQueryStress Desktop shortcut" - Write-Host "`n" - $TargetFile = "C:\VHDs\SqlQueryStress\SqlQueryStress.exe" - $ShortcutFile = "C:\Users\$using:adminUsername\Desktop\SqlQueryStress.lnk" - $WScriptShell = New-Object -ComObject WScript.Shell - $Shortcut = $WScriptShell.CreateShortcut($ShortcutFile) - $Shortcut.TargetPath = $TargetFile - $Shortcut.Save() -} - -# Set env variable deployAKSHCI to true (in case the script was run manually) -[System.Environment]::SetEnvironmentVariable('deploySQLMI', 'true', [System.EnvironmentVariableTarget]::Machine) - -Stop-Transcript \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/GetServiceAccountBearerToken.ps1 b/azure_jumpstart_hcibox/artifacts/GetServiceAccountBearerToken.ps1 deleted file mode 100644 index 370ad334f7..0000000000 --- a/azure_jumpstart_hcibox/artifacts/GetServiceAccountBearerToken.ps1 +++ /dev/null @@ -1,38 +0,0 @@ -# Set paths -$Env:HCIBoxDir = "C:\HCIBox" -$Env:HCIBoxLogsDir = "C:\HCIBox\Logs" -$Env:HCIBoxVMDir = "C:\HCIBox\Virtual Machines" -$Env:HCIBoxKVDir = "C:\HCIBox\KeyVault" -$Env:HCIBoxGitOpsDir = "C:\HCIBox\GitOps" -$Env:HCIBoxIconDir = "C:\HCIBox\Icons" -$Env:HCIBoxVHDDir = "C:\HCIBox\VHD" -$Env:HCIBoxSDNDir = "C:\HCIBox\SDN" -$Env:HCIBoxWACDir = "C:\HCIBox\Windows Admin Center" -$Env:agentScript = "C:\HCIBox\agentScript" -$Env:ToolsDir = "C:\Tools" -$Env:tempDir = "C:\Temp" -$Env:VMPath = "C:\VMs" - -# Import Configuration Module and create Azure login credentials -Write-Header 'Importing config' -$ConfigurationDataFile = 'C:\HCIBox\HCIBox-Config.psd1' -$SDNConfig = Import-PowerShellDataFile -Path $ConfigurationDataFile - -# Generate credential objects -$user = "jumpstart.local\administrator" -$password = ConvertTo-SecureString -String $SDNConfig.SDNAdminPassword -AsPlainText -Force -$adcred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user, $password # Domain credential -$clusterName = $env:AKSClusterName -Copy-VMFile -Name $SDNConfig.HostList[0] -SourcePath $env:HCIBoxDir\jumpstart-user-secret.yaml -DestinationPath C:\AksHci\jumpstart-user-secret.yaml -FileSource Host -Force -$TOKEN = Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - Get-AksHciCredential -name $using:clusterName -Confirm:$false - kubectl create serviceaccount jumpstart-user - kubectl create clusterrolebinding jumpstart-user-binding --clusterrole cluster-admin --serviceaccount default:jumpstart-user - kubectl apply -f C:\AksHci\jumpstart-user-secret.yaml - $TOKEN = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String((kubectl get secret jumpstart-user-secret -o jsonpath='{$.data.token}')))) - return $TOKEN -} - -Write-Output "The service account bearer token below can be used to view Kubernetes resources inside the Azure portal. Copy the code starting after the dashed line (do not include the dashed line)." -Write-Output "----------------------------------" -Write-Output $TOKEN \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/HCIBoxLogonScript.ps1 b/azure_jumpstart_hcibox/artifacts/HCIBoxLogonScript.ps1 deleted file mode 100644 index ec15338553..0000000000 --- a/azure_jumpstart_hcibox/artifacts/HCIBoxLogonScript.ps1 +++ /dev/null @@ -1,120 +0,0 @@ -# Set paths -$Env:HCIBoxDir = "C:\HCIBox" -$Env:HCIBoxLogsDir = "C:\HCIBox\Logs" -$Env:HCIBoxVMDir = "C:\HCIBox\Virtual Machines" -$Env:HCIBoxKVDir = "C:\HCIBox\KeyVault" -$Env:HCIBoxGitOpsDir = "C:\HCIBox\GitOps" -$Env:HCIBoxIconDir = "C:\HCIBox\Icons" -$Env:HCIBoxVHDDir = "C:\HCIBox\VHD" -$Env:HCIBoxSDNDir = "C:\HCIBox\SDN" -$Env:HCIBoxWACDir = "C:\HCIBox\Windows Admin Center" -$Env:agentScript = "C:\HCIBox\agentScript" -$Env:ToolsDir = "C:\Tools" -$Env:tempDir = "C:\Temp" -$Env:VMPath = "C:\VMs" - -Start-Transcript -Path $Env:HCIBoxLogsDir\HCIBoxLogonScript.log - -$cliDir = New-Item -Path "$Env:ArcBoxDir\.cli\" -Name ".servers" -ItemType Directory - -if(-not $($cliDir.Parent.Attributes.HasFlag([System.IO.FileAttributes]::Hidden))) { - $folder = Get-Item $cliDir.Parent.FullName -ErrorAction SilentlyContinue - $folder.Attributes += [System.IO.FileAttributes]::Hidden -} - -$Env:AZURE_CONFIG_DIR = $cliDir.FullName - -# Configure storage pools and data disks -Write-Header "Configuring storage" -New-StoragePool -FriendlyName AsHciPool -StorageSubSystemFriendlyName '*storage*' -PhysicalDisks (Get-PhysicalDisk -CanPool $true) -$disks = Get-StoragePool -FriendlyName AsHciPool -IsPrimordial $False | Get-PhysicalDisk -$diskNum = $disks.Count -New-VirtualDisk -StoragePoolFriendlyName AsHciPool -FriendlyName AsHciDisk -ResiliencySettingName Simple -NumberOfColumns $diskNum -UseMaximumSize -$vDisk = Get-VirtualDisk -FriendlyName AsHciDisk -if ($vDisk | Get-Disk | Where-Object PartitionStyle -eq 'raw') { - $vDisk | Get-Disk | Initialize-Disk -Passthru | New-Partition -DriveLetter V -UseMaximumSize | Format-Volume -NewFileSystemLabel AsHciData -AllocationUnitSize 64KB -FileSystem NTFS -} -elseif ($vDisk | Get-Disk | Where-Object PartitionStyle -eq 'GPT') { - $vDisk | Get-Disk | New-Partition -DriveLetter V -UseMaximumSize | Format-Volume -NewFileSystemLabel AsHciData -AllocationUnitSize 64KB -FileSystem NTFS -} -New-Item -Path "V:\" -Name "VMs" -ItemType "directory" - -# Required for CLI commands -Write-Header "Az CLI Login" -az login --service-principal --username $Env:spnClientID --password=$Env:spnClientSecret --tenant $Env:spnTenantId - -# Register Azure providers -Write-Header "Registering Providers" -az provider register --namespace Microsoft.HybridCompute --wait -az provider register --namespace Microsoft.GuestConfiguration --wait -az provider register --namespace Microsoft.Kubernetes --wait -az provider register --namespace Microsoft.KubernetesConfiguration --wait -az provider register --namespace Microsoft.ExtendedLocation --wait -az provider register --namespace Microsoft.AzureArcData --wait -az provider register --namespace Microsoft.OperationsManagement --wait -az provider register --namespace Microsoft.AzureStackHCI --wait -az provider register --namespace Microsoft.ResourceConnector --wait - -Stop-Transcript - -# Build HCI cluster -Write-Header "Deploying HCI cluster" -& "$Env:HCIBoxDir\New-HCIBoxCluster.ps1" - -# Register HCI cluster -if ($env:registerCluster -eq $true) { - Write-Header "Registering HCI cluster" - & "$Env:HCIBoxDir\Register-AzSHCI.ps1" -} - -# deploy AKS -if (($env:registerCluster -eq $true) -and ($env:deployAKSHCI -eq $true)) { - Write-Header "Deploying AKS" - & "$Env:HCIBoxDir\Deploy-AKS.ps1" -} - -# Deploy Arc Resource Bridge -if (($env:registerCluster -eq $true) -and ($env:deployResourceBridge -eq $true)) { - Write-Header "Deploying Arc Resource Bridge" - & "$Env:HCIBoxDir\Deploy-ArcResourceBridge.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); - } - } - } -'@ - -Write-Header "Changing Wallpaper" -$imgPath="$Env:HCIBoxDir\wallpaper.png" -Add-Type $code -[Win32.Wallpaper]::SetWallpaper($imgPath) - -# Removing the LogonScript Scheduled Task so it won't run on next reboot -Write-Header "Removing Logon Task" -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 { - $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 - Write-Host "`n" - Write-Host "Creating deployment logs bundle" - 7z a $Env:HCIBoxLogsDir\LogsBundle-"$RandomString".zip $Env:HCIBoxLogsDir\*.log -}' - -Stop-Transcript \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/LogInstructions.txt b/azure_jumpstart_hcibox/artifacts/LogInstructions.txt index a22d848dc7..8068bc81d1 100644 --- a/azure_jumpstart_hcibox/artifacts/LogInstructions.txt +++ b/azure_jumpstart_hcibox/artifacts/LogInstructions.txt @@ -1,7 +1,6 @@ -To upload the log bundle zip file for support review, use the below command: +To upload the log bundle ZIP file for support review, use the following steps command: -azcopy copy "C:\HCIBox\Logs\.zip" "https://https://jumpstartsupport.blob.core.windows.net/logbundles?sp=cw&st=2022-02-02T22:04:39Z&se=2027-04-22T05:04:39Z&spr=https&sv=2020-08-04&sr=c&sig=2urJGqXvkXTSBpu%2FqCksANy18pY5o44tmzzKjc8eUoM%3D" --recursive - -For example: - -azcopy copy "C:\HCIBox\Logs\LogsBundle-kavpug.zip" "https://https://jumpstartsupport.blob.core.windows.net/logbundles?sp=cw&st=2022-02-02T22:04:39Z&se=2027-04-22T05:04:39Z&spr=https&sv=2020-08-04&sr=c&sig=2urJGqXvkXTSBpu%2FqCksANy18pY5o44tmzzKjc8eUoM%3D" --recursive \ No newline at end of file +1. Create a ZIP file with all the required files +1. Open an [issue](https://github.com/microsoft/azure_arc/issues/new?assignees=&labels=triage&projects=&template=bug_report.md&title=) with the appropiate title and description. +1. Attach the log bundle ZIP file +1. If extra files/logs are required, these will be asked through the GitHub issue discussion view \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/New-HCIBoxCluster.ps1 b/azure_jumpstart_hcibox/artifacts/New-HCIBoxCluster.ps1 deleted file mode 100644 index 3dde72c2eb..0000000000 --- a/azure_jumpstart_hcibox/artifacts/New-HCIBoxCluster.ps1 +++ /dev/null @@ -1,3118 +0,0 @@ -[CmdletBinding(DefaultParameterSetName = "NoParameters")] - -param( - [Parameter(Mandatory = $true, ParameterSetName = "ConfigurationFile")] - [String] $ConfigurationDataFile = 'C:\HCIBox\HCIBox-Config.psd1', - [Parameter(Mandatory = $false, ParameterSetName = "Delete")] - [Bool] $Delete = $false -) - -# Set paths -$Env:HCIBoxDir = "C:\HCIBox" -$Env:HCIBoxLogsDir = "C:\HCIBox\Logs" -$Env:HCIBoxVMDir = "C:\HCIBox\Virtual Machines" -$Env:HCIBoxKVDir = "C:\HCIBox\KeyVault" -$Env:HCIBoxGitOpsDir = "C:\HCIBox\GitOps" -$Env:HCIBoxIconDir = "C:\HCIBox\Icons" -$Env:HCIBoxVHDDir = "C:\HCIBox\VHD" -$Env:HCIBoxSDNDir = "C:\HCIBox\SDN" -$Env:HCIBoxWACDir = "C:\HCIBox\Windows Admin Center" -$Env:agentScript = "C:\HCIBox\agentScript" -$Env:ToolsDir = "C:\Tools" -$Env:tempDir = "C:\Temp" -$Env:VMPath = "C:\VMs" - -#region functions - -function BITSRequest { - Param( - [Parameter(Mandatory=$True)] - [hashtable]$Params - ) - $url = $Params['Uri'] - $filename = $Params['Filename'] - $download = Start-BitsTransfer -Source $url -Destination $filename -Asynchronous - $ProgressPreference = "Continue" - while ($download.JobState -ne "Transferred") { - if ($download.JobState -eq "TransientError"){ - 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; - } - Complete-BitsTransfer $download.JobId - Write-Progress -Activity "Downloading File $filename..." -Status "Ready" -Completed - $ProgressPreference = "SilentlyContinue" -} - -function Set-LocalHyperVSettings { - Param ( - - [string]$HostVMPath - ) - - Write-Verbose "Configuring Hyper-V Settings on localhost" - $params = @{ - VirtualHardDiskPath = $HostVMPath - VirtualMachinePath = $HostVMPath - EnableEnhancedSessionMode = $true - } - Set-VMhost @params -} - -function New-InternalSwitch { - Param ( - $pswitchname, - $SDNConfig - ) - - $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 = $SDNConfig.PhysicalHostInternalIP - $Prefix = ($SDNConfig.AzSMGMTIP.Split("/"))[1] - $Gateway = $SDNConfig.SDNLABRoute - $DNS = $SDNConfig.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-Verbose "Internal Switch $pswitchname already exists. Not creating a new internal switch." - } -} - -function Test-VHDPath { - Param ( - $guiVHDXPath, - $azSHCIVHDXPath - ) - - $Result = Get-ChildItem -Path $guiVHDXPath -ErrorAction Ignore - if (!$result) { Write-Host "Path $guiVHDXPath was not found!" -ForegroundColor Red ; break } - $Result = Get-ChildItem -Path $azSHCIVHDXPath -ErrorAction Ignore - if (!$result) { Write-Host "Path $azSHCIVHDXPath was not found!" -ForegroundColor Red ; break } -} - -function Select-SingleHost { - Param ( - $AzSHOSTs - ) - - $results = @() - foreach ($AzSHOST in $AzSHOSTs) { - $results = $results + [pscustomobject]@{AzSHOST = $AzSHOST; VMHost = $env:COMPUTERNAME } - } - - return $results -} - -function Copy-VHDXtoHost { - Param ( - $guiVHDXPath, - $HostVMPath, - $azSHCIVHDXPath - ) - - Write-Verbose "Copying $guiVHDXPath to $HostVMPath\GUI.VHDX" - Copy-Item -Path $guiVHDXPath -Destination "$HostVMPath\GUI.VHDX" -Force | Out-Null - Write-Verbose "Copying $azSHCIVHDXPath to $HostVMPath\AzSHCI.VHDX" - Copy-Item -Path $azSHCIVHDXPath -Destination "$HostVMPath\AzSHCI.VHDX" -Force | Out-Null -} - -function Get-GuiVHDXPath { - Param ( - $guiVHDXPath, - $HostVMPath - ) - $ParentVHDXPath = $HostVMPath + 'GUI.vhdx' - return $ParentVHDXPath -} - -function Get-AzSHCIVHDXPath { - Param ( - $azSHCIVHDXPath, - $HostVMPath - ) - $ParentVHDXPath = $HostVMPath + 'AzSHCI.vhdx' - return $ParentVHDXPath -} - -function Get-ConsoleVHDXPath { - Param ( - $ConsoleVHDXPath, - $HostVMPath - ) - $ParentVHDXPath = $HostVMPath + 'Console.vhdx' - return $ParentVHDXPath -} - -function New-NestedVM { - Param ( - $AzSHOST, - $VMHost, - $HostVMPath, - $VMSwitch, - $SDNConfig - ) - - $parentpath = "$HostVMPath\GUI.vhdx" - $coreparentpath = "$HostVMPath\AzSHCI.vhdx" - $vmMac = Invoke-Command -ComputerName $VMHost -ScriptBlock { - - $VerbosePreference = "SilentlyContinue" - Import-Module Hyper-V - $VerbosePreference = "Continue" - - $AzSHOST = $using:AzSHOST - $VMHost = $using:VMHost - $HostVMPath = $using:HostVMPath - $VMSwitch = $using:VMSwitch - $parentpath = $using:parentpath - $coreparentpath = $using:coreparentpath - $SDNConfig = $using:SDNConfig - $S2DDiskSize = $SDNConfig.S2D_Disk_Size - $NestedVMMemoryinGB = $SDNConfig.NestedVMMemoryinGB - $AzSMGMTMemoryinGB = $SDNConfig.AzSMGMTMemoryinGB - - # Create Differencing Disk. Note: AzSMGMT is GUI - if ($AzSHOST -eq "AzSMGMT") { - $VHDX1 = New-VHD -ParentPath $parentpath -Path "$HostVMPath\$AzSHOST.vhdx" -Differencing - $VHDX2 = New-VHD -Path "$HostVMPath\$AzSHOST-Data.vhdx" -SizeBytes 268435456000 -Dynamic - $NestedVMMemoryinGB = $AzSMGMTMemoryinGB - } - else { - $VHDX1 = New-VHD -ParentPath $coreparentpath -Path "$HostVMPath\$AzSHOST.vhdx" -Differencing - $VHDX2 = New-VHD -Path "$HostVMPath\$AzSHOST-Data.vhdx" -SizeBytes 268435456000 -Dynamic - - # Create S2D Storage - New-VHD -Path "$HostVMPath\$AzSHOST-S2D_Disk1.vhdx" -SizeBytes $S2DDiskSize -Dynamic | Out-Null - New-VHD -Path "$HostVMPath\$AzSHOST-S2D_Disk2.vhdx" -SizeBytes $S2DDiskSize -Dynamic | Out-Null - New-VHD -Path "$HostVMPath\$AzSHOST-S2D_Disk3.vhdx" -SizeBytes $S2DDiskSize -Dynamic | Out-Null - New-VHD -Path "$HostVMPath\$AzSHOST-S2D_Disk4.vhdx" -SizeBytes $S2DDiskSize -Dynamic | Out-Null - New-VHD -Path "$HostVMPath\$AzSHOST-S2D_Disk5.vhdx" -SizeBytes $S2DDiskSize -Dynamic | Out-Null - New-VHD -Path "$HostVMPath\$AzSHOST-S2D_Disk6.vhdx" -SizeBytes $S2DDiskSize -Dynamic | Out-Null - } - - # Create Nested VM - $params = @{ - Name = $AzSHOST - MemoryStartupBytes = $NestedVMMemoryinGB - VHDPath = $VHDX1.Path - SwitchName = $VMSwitch - Generation = 2 - } - New-VM @params | Out-Null - Add-VMHardDiskDrive -VMName $AzSHOST -Path $VHDX2.Path - - if ($AzSHOST -ne "AzSMGMT") { - Add-VMHardDiskDrive -Path "$HostVMPath\$AzSHOST-S2D_Disk1.vhdx" -VMName $AzSHOST | Out-Null - Add-VMHardDiskDrive -Path "$HostVMPath\$AzSHOST-S2D_Disk2.vhdx" -VMName $AzSHOST | Out-Null - Add-VMHardDiskDrive -Path "$HostVMPath\$AzSHOST-S2D_Disk3.vhdx" -VMName $AzSHOST | Out-Null - Add-VMHardDiskDrive -Path "$HostVMPath\$AzSHOST-S2D_Disk4.vhdx" -VMName $AzSHOST | Out-Null - Add-VMHardDiskDrive -Path "$HostVMPath\$AzSHOST-S2D_Disk5.vhdx" -VMName $AzSHOST | Out-Null - Add-VMHardDiskDrive -Path "$HostVMPath\$AzSHOST-S2D_Disk6.vhdx" -VMName $AzSHOST | Out-Null - } - - Set-VM -Name $AzSHOST -ProcessorCount 20 -AutomaticStartAction Start - Get-VMNetworkAdapter -VMName $AzSHOST | Rename-VMNetworkAdapter -NewName "SDN" - Get-VMNetworkAdapter -VMName $AzSHOST | Set-VMNetworkAdapter -DeviceNaming On -StaticMacAddress ("{0:D12}" -f ( Get-Random -Minimum 0 -Maximum 99999 )) - Add-VMNetworkAdapter -VMName $AzSHOST -Name SDN2 -DeviceNaming On -SwitchName $VMSwitch - $vmMac = ((Get-VMNetworkAdapter -Name SDN -VMName $AzSHOST).MacAddress) -replace '..(?!$)', '$&-' - Write-Verbose "Virtual Machine FABRIC NIC MAC is = $vmMac" - - if ($AzSHOST -ne "AzSMGMT") { - Add-VMNetworkAdapter -VMName $AzSHOST -SwitchName $VMSwitch -DeviceNaming On -Name StorageA - Add-VMNetworkAdapter -VMName $AzSHOST -SwitchName $VMSwitch -DeviceNaming On -Name StorageB - } - - Get-VM $AzSHOST | Set-VMProcessor -ExposeVirtualizationExtensions $true - Get-VM $AzSHOST | Set-VMMemory -DynamicMemoryEnabled $false - Get-VM $AzSHOST | Get-VMNetworkAdapter | Set-VMNetworkAdapter -MacAddressSpoofing On - - Set-VMNetworkAdapterVlan -VMName $AzSHOST -VMNetworkAdapterName SDN -Trunk -NativeVlanId 0 -AllowedVlanIdList 1-200 - Set-VMNetworkAdapterVlan -VMName $AzSHOST -VMNetworkAdapterName SDN2 -Trunk -NativeVlanId 0 -AllowedVlanIdList 1-200 - - if ($AzSHOST -ne "AzSMGMT") { - Set-VMNetworkAdapterVlan -VMName $AzSHOST -VMNetworkAdapterName StorageA -Access -VlanId $SDNConfig.StorageAVLAN - Set-VMNetworkAdapterVlan -VMName $AzSHOST -VMNetworkAdapterName StorageB -Access -VlanId $SDNConfig.StorageBVLAN - } - - Enable-VMIntegrationService -VMName $AzSHOST -Name "Guest Service Interface" - return $vmMac - } - - return $vmMac -} - -function Add-Files { - Param( - $VMPlacement, - $HostVMPath, - $SDNConfig, - $guiVHDXPath, - $azSHCIVHDXPath, - $vmMacs - ) - - foreach ($AzSHOST in $VMPlacement) { - # Get Drive Paths - $HypervHost = $AzSHOST.VMHost - $DriveLetter = $HostVMPath.Split(':') - $path = (("\\$HypervHost\") + ($DriveLetter[0] + "$") + ($DriveLetter[1]) + "\" + $AzSHOST.AzSHOST + ".vhdx") - - # Install Hyper-V Offline - Write-Verbose "Performing offline installation of Hyper-V to path $path" - $VerbosePreference = "SilentlyContinue" - Install-WindowsFeature -Vhd $path -Name Hyper-V, RSAT-Hyper-V-Tools, Hyper-V-Powershell -Confirm:$false | Out-Null - $VerbosePreference = "Continue" - Start-Sleep -Seconds 20 - - # Mount VHDX - bunch of kludgey logic in here to deal with different partition layouts on the GUI and HCI VHD images - Write-Verbose "Mounting VHDX file at $path" - [string]$MountedDrive = "" - if ($AzSHOST.AzSHOST -eq "AzSMGMT") { - $partition = Mount-VHD -Path $path -Passthru | Get-Disk | Get-Partition -PartitionNumber 3 - if (!$partition.DriveLetter) { - $MountedDrive = "X" - $partition | Set-Partition -NewDriveLetter $MountedDrive - } - else { - $MountedDrive = $partition.DriveLetter - } - } else { - $partition = Mount-VHD -Path $path -Passthru | Get-Disk | Get-Partition -PartitionNumber 3 - if (!$partition.DriveLetter) { - $MountedDrive = "Y" - $partition | Set-Partition -NewDriveLetter $MountedDrive - } - else { - $MountedDrive = $partition.DriveLetter - } - } - - # Get Assigned MAC Address so we know what NIC to assign a static IP to - $vmMac = ($vmMacs | Where-Object { $_.Hostname -eq $AzSHost.AzSHOST }).vmMac - - # Inject Answer File - Write-Verbose "Injecting answer file to $path" - - $AzSHOSTComputerName = $AzSHOST.AzSHOST - $AzSHOSTIP = $SDNConfig.($AzSHOSTComputerName + "IP") - $SDNAdminPassword = $SDNConfig.SDNAdminPassword - $SDNDomainFQDN = $SDNConfig.SDNDomainFQDN - $SDNLABDNS = $SDNConfig.SDNLABDNS - $SDNLabRoute = $SDNConfig.SDNLABRoute - $ProductKey = $SDNConfig.GUIProductKey - - # Only inject product key if host is AzSMGMT - $azsmgmtProdKey = $null - if ($AzSHOST.AzSHOST -eq "AzSMGMT") { $azsmgmtProdKey = "$ProductKey"} - - $UnattendXML = @" - - - - -false -false -false - - -$AzSHOSTComputerName -$azsmgmtProdKey - - -false - - -en-us -en-us -en-us -en-us - - -false -false - - - - -$vmMac - -false - - -$AzSHOSTIP - - - -1 -$SDNLabRoute -0.0.0.0/0 -100 - - - - - - - -$SDNDomainFQDN - - - - -$SDNLABDNS - -$vmMac -false -$SDNDomainFQDN -true - - - - - - - -true -true -true -true - - - -$SDNAdminPassword -true</PlainText> -</AdministratorPassword> -</UserAccounts> -</component> -</settings> -<cpi:offlineImage cpi:source="" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> -</unattend> -"@ - - Write-Verbose "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 - - if ($AzSHOST.AzSHOST -eq "AzSMGMT") { - # Creating folder structure on AzSMGMT - Write-Verbose "Creating VMs\Base folder structure on AzSMGMT" - New-Item -Path ($MountedDrive + ":\VMs\Base") -ItemType Directory -Force | Out-Null - - # Injecting configs into VMs - Write-Verbose "Injecting VMConfigs to $path" - Copy-Item -Path "$Env:HCIBoxDir\HCIBox-Config.psd1" -Destination ($MountedDrive + ":\") -Recurse -Force - New-Item -Path ($MountedDrive + ":\") -Name VMConfigs -ItemType Directory -Force | Out-Null - Copy-Item -Path $guiVHDXPath -Destination ($MountedDrive + ":\VMs\Base\GUI.vhdx") -Force - Copy-Item -Path $azSHCIVHDXPath -Destination ($MountedDrive + ":\VMs\Base\AzSHCI.vhdx") -Force - Copy-Item -Path $Env:HCIBoxSDNDir -Destination ($MountedDrive + ":\VmConfigs") -Recurse -Force - Copy-Item -Path $Env:HCIBoxSDNDir -Destination ($MountedDrive + ":\VmConfigs") -Recurse -Force - Copy-Item -Path $Env:HCIBoxWACDir -Destination ($MountedDrive + ":\VmConfigs") -Recurse -Force - } - - if ($AzSHOST.AzSHOST -eq "AzSHOST1") { - New-Item -Path ($MountedDrive + ":\VHD") -ItemType Directory -Force | Out-Null - Copy-Item -Path "$Env:HCIBoxVHDDir\GUI.vhdx" -Destination ($MountedDrive + ":\VHD") -Recurse -Force - Copy-Item -Path "$Env:HCIBoxVHDDir\Ubuntu.vhdx" -Destination ($MountedDrive + ":\VHD") -Recurse -Force - } - - # Dismount VHDX - Write-Verbose "Dismounting VHDX File at path $path" - Dismount-VHD $path - } -} - -function Start-AzSHOSTS { - Param( - $VMPlacement - ) - - foreach ($VMHost in $VMPlacement) { - Write-Verbose "Starting VM: $VMHost" - Start-VM -ComputerName $VMHost.VMhost -Name $VMHost.AzSHOST - } -} - -function New-DataDrive { - param ( - $VMPlacement, - $SDNConfig - ) - - foreach ($SDNVM in $VMPlacement) { - - Invoke-Command -ComputerName $SDNVM.VMHost -ScriptBlock { - $VerbosePreference = "Continue" - Write-Verbose "Onlining, partitioning, and formatting Data Drive on $($Using:SDNVM.AzSHOST)" - - $localCred = new-object -typename System.Management.Automation.PSCredential -argumentlist "Administrator" ` - , (ConvertTo-SecureString $using:SDNConfig.SDNAdminPassword -AsPlainText -Force) - - Invoke-Command -VMName $using:SDNVM.AzSHOST -Credential $localCred -ScriptBlock { - - 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 - - } - } - } -} - -function Test-AzSHOSTVMConnection { - - param ( - - $VMPlacement, - $localCred - - ) - - foreach ($SDNVM in $VMPlacement) { - - Invoke-Command -ComputerName $SDNVM.VMHost -ScriptBlock { - $VerbosePreference = "Continue" - $localCred = $using:localCred - $testconnection = $null - While (!$testconnection) { - $testconnection = Invoke-Command -VMName $using:SDNVM.AzSHOST -ScriptBlock { - $ErrorOccurred = $false - do { - try { - $ErrorActionPreference = 'Stop' - Get-VMHost - } - catch { - $ErrorOccurred = $true - } - } while ($ErrorOccurred -eq $true) - } -Credential $localCred -ErrorAction Ignore - } - - Write-Verbose "Successfully contacted $($using:SDNVM.AzSHOST)" - } - } -} - -function Start-PowerShellScriptsOnHosts { - - Param ( - - $VMPlacement, - $ScriptPath, - $localCred - - ) - - foreach ($SDNVM in $VMPlacement) { - - Invoke-Command -ComputerName $SDNVM.VMHost -ScriptBlock { - - $VerbosePreference = "Continue" - Write-Verbose "Executing Script: $($using:ScriptPath) on host $($using:SDNVM.AzSHOST)" - Invoke-Command -VMName $using:SDNVM.AzSHOST -ArgumentList $using:Scriptpath -ScriptBlock { Invoke-Expression -Command $args[0] } -Credential $using:localCred - - } - } -} - -function New-NATSwitch { - - Param ( - - $VMPlacement, - $SwitchName, - $SDNConfig - - ) - - $natSwitchTarget = $VMPlacement | Where-Object { $_.AzSHOST -eq "AzSMGMT" } - - Add-VMNetworkAdapter -VMName $natSwitchTarget.AzSHOST -ComputerName $natSwitchTarget.VMHost -DeviceNaming On - - $params = @{ - - VMName = $natSwitchTarget.AzSHOST - ComputerName = $natSwitchTarget.VMHost - } - - Get-VMNetworkAdapter @params | Where-Object { $_.Name -match "Network" } | Connect-VMNetworkAdapter -SwitchName $SDNConfig.natHostVMSwitchName - Get-VMNetworkAdapter @params | Where-Object { $_.Name -match "Network" } | Rename-VMNetworkAdapter -NewName "NAT" - - Get-VM @params | Get-VMNetworkAdapter -Name NAT | Set-VMNetworkAdapter -MacAddressSpoofing On - - <# Should not need this anymore - - if ($SDNConfig.natVLANID) { - - Get-VM @params | Get-VMNetworkAdapter -Name NAT | Set-VMNetworkAdapterVlan -Access -VlanId $natVLANID | Out-Null - - } - - #> - - #Create PROVIDER NIC in order for NAT to work from SLB/MUX and RAS Gateways - - Add-VMNetworkAdapter @params -Name PROVIDER -DeviceNaming On -SwitchName $SwitchName - Get-VM @params | Get-VMNetworkAdapter -Name PROVIDER | Set-VMNetworkAdapter -MacAddressSpoofing On - Get-VM @params | Get-VMNetworkAdapter -Name PROVIDER | Set-VMNetworkAdapterVlan -Access -VlanId $SDNConfig.providerVLAN | Out-Null - - #Create VLAN 200 NIC in order for NAT to work from L3 Connections - - Add-VMNetworkAdapter @params -Name VLAN200 -DeviceNaming On -SwitchName $SwitchName - Get-VM @params | Get-VMNetworkAdapter -Name VLAN200 | Set-VMNetworkAdapter -MacAddressSpoofing On - Get-VM @params | Get-VMNetworkAdapter -Name VLAN200 | Set-VMNetworkAdapterVlan -Access -VlanId $SDNConfig.vlan200VLAN | Out-Null - - - #Create Simulated Internet NIC in order for NAT to work from L3 Connections - - Add-VMNetworkAdapter @params -Name simInternet -DeviceNaming On -SwitchName $SwitchName - Get-VM @params | Get-VMNetworkAdapter -Name simInternet | Set-VMNetworkAdapter -MacAddressSpoofing On - Get-VM @params | Get-VMNetworkAdapter -Name simInternet | Set-VMNetworkAdapterVlan -Access -VlanId $SDNConfig.simInternetVLAN | Out-Null - - -} - -function Resolve-Applications { - - Param ( - - $SDNConfig - ) - - # Verify Product Keys - - Write-Verbose "Performing simple validation of Product Keys" - $guiResult = $SDNConfig.GUIProductKey -match '^([A-Z0-9]{5}-){4}[A-Z0-9]{5}$' - - if (!$guiResult) { Write-Error "Cannot validate or find the product key for the Windows Server Datacenter Desktop Experience." } - - - # Verify Windows Admin Center - $isWAC = Get-ChildItem -Path $Env:HCIBoxWACDir -Filter *.MSI - if (!$isWAC) { Write-Error "Please check and ensure that you have correctly copied the Admin Center install file to C:\HCIBox\Windows Admin Center." } - - # Are we on Server Core? - $regKey = "hklm:/software/microsoft/windows nt/currentversion" - $Core = (Get-ItemProperty $regKey).InstallationType -eq "Server Core" - If ($Core) { - - Write-Warning "You might not want to run the Azure Stack HCI OS Sandbox on Server Core, getting remote access to the AdminCenter VM may require extra configuration." - Start-Sleep -Seconds 5 - - } - - -} - -function Get-PhysicalNICMTU { - - Param ( - - $SDNConfig - - ) - - foreach ($VMHost in $SDNConfig.MultipleHyperVHostNames) { - - Invoke-Command -ComputerName $VMHost -ScriptBlock { - - $SDNConfig = $using:SDNConfig - - $VswitchNICs = (Get-VMSwitch -Name ($SDNConfig.MultipleHyperVHostExternalSwitchName)).NetAdapterInterfaceDescription - - if ($VswitchNICs) { - foreach ($VswitchNIC in $VswitchNICs) { - - $MTUSetting = (Get-NetAdapterAdvancedProperty -InterfaceDescription $VswitchNIC -RegistryKeyword '*JumboPacket').RegistryValue - - if ($MTUSetting -ne $SDNConfig.SDNLABMTU) { - - Write-Error "There is a mismatch in the MTU value for the external switch and the value in the HCIBox-Config.psd1 data file." - - } - - } - - } - - else { - - Write-Error "The external switch was not found on $Env:COMPUTERNAME" - - } - - } - - } - -} - -function Set-SDNserver { - - Param ( - - $VMPlacement, - $SDNConfig, - $localCred - - ) - - - # Set base number for Storage IPs - $int = 9 - - - foreach ($SDNVM in $VMPlacement) { - - - # Increment Storage IPs - - $int++ - - - Invoke-Command -ComputerName $SDNVM.VMHost -ScriptBlock { - - Invoke-Command -VMName $using:SDNVM.AzSHOST -ArgumentList $using:SDNConfig, $using:localCred, $using:int -ScriptBlock { - - $SDNConfig = $args[0] - $localCred = $args[1] - $int = $args[2] - $VerbosePreference = "Continue" - - - # Create IP Address of Storage Adapters - - $storageAIP = $sdnconfig.storageAsubnet.Replace("0/24", $int) - $storageBIP = $sdnconfig.storageBsubnet.Replace("0/24", $int) - - - # 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 - - } - - $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 } - - } - - - - - # Enable WinRM - - Write-Verbose "Enabling Windows Remoting in $env:COMPUTERNAME" - $VerbosePreference = "SilentlyContinue" - Set-Item WSMan:\localhost\Client\TrustedHosts * -Confirm:$false -Force - Enable-PSRemoting | Out-Null - $VerbosePreference = "Continue" - - Start-Sleep -Seconds 60 - - if ($env:COMPUTERNAME -ne "AzSMGMT") { - - Write-Verbose "Installing and Configuring Failover Clustering on $env:COMPUTERNAME" - $VerbosePreference = "SilentlyContinue" - Install-WindowsFeature -Name Failover-Clustering -IncludeAllSubFeature -IncludeManagementTools -ComputerName $env:COMPUTERNAME -Credential $localCred | Out-Null - $VerbosePreference = "Continue" - } - - # Enable CredSSP and MTU Settings - - Invoke-Command -ComputerName localhost -Credential $localCred -ScriptBlock { - - $fqdn = $Using:SDNConfig.SDNDomainFQDN - - Write-Verbose "Enabling CredSSP on $env:COMPUTERNAME" - Enable-WSManCredSSP -Role Server -Force - Enable-WSManCredSSP -Role Client -DelegateComputer localhost -Force - Enable-WSManCredSSP -Role Client -DelegateComputer $env:COMPUTERNAME -Force - Enable-WSManCredSSP -Role Client -DelegateComputer $fqdn -Force - Enable-WSManCredSSP -Role Client -DelegateComputer "*.$fqdn" -Force - 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 - } -InDisconnectedSession | Out-Null - - } -Credential $using:localCred - - } - - } - -} - -function Set-AzSMGMT { - - param ( - - $SDNConfig, - $localCred, - $domainCred - - ) - - # Sleep to get around race condition on fast systems - Start-Sleep -Seconds 10 - $VerbosePreference = "Continue" - - Invoke-Command -ComputerName azsmgmt -Credential $localCred -ScriptBlock { - - # Creds - - $localCred = $using:localCred - $domainCred = $using:domainCred - $SDNConfig = $using:SDNConfig - - $ErrorActionPreference = "Stop" - $VerbosePreference = "Continue" - $WarningPreference = "SilentlyContinue" - - # Disable Fabric2 Network Adapter - $fabTwo = $null - while ($fabTwo -ne 'Disabled') { - Write-Verbose "Disabling Fabric2 Adapter" - Get-Netadapter FABRIC2 | Disable-NetAdapter -Confirm:$false | Out-Null - $fabTwo = (Get-Netadapter -Name FABRIC2).Status - - } - # Enable WinRM on AzSMGMT - $VerbosePreference = "Continue" - Write-Verbose "Enabling PSRemoting on $env:COMPUTERNAME" - $VerbosePreference = "SilentlyContinue" - Set-Item WSMan:\localhost\Client\TrustedHosts * -Confirm:$false -Force - Enable-PSRemoting | Out-Null - - - #Disable ServerManager Auto-Start - Get-ScheduledTask -TaskName ServerManager | Disable-ScheduledTask | Out-Null - - # Create Hyper-V Networking for AzSMGMT - Import-Module Hyper-V - - Try { - - $VerbosePreference = "Continue" - Write-Verbose "Creating VM Switch on $env:COMPUTERNAME" - - New-VMSwitch -AllowManagementOS $true -Name "vSwitch-Fabric" -NetAdapterName FABRIC -MinimumBandwidthMode None | Out-Null - - # Configure NAT on AzSMGMT - - if ($SDNConfig.natConfigure) { - - Write-Verbose "Configuring NAT on $env:COMPUTERNAME" - - $VerbosePreference = "SilentlyContinue" - - $natSubnet = $SDNConfig.natSubnet - $Prefix = ($natSubnet.Split("/"))[1] - $natIP = ($natSubnet.TrimEnd("0./$Prefix")) + (".1") - $provIP = $SDNConfig.BGPRouterIP_ProviderNetwork.TrimEnd("1/24") + "254" - $vlan200IP = $SDNConfig.BGPRouterIP_VLAN200.TrimEnd("1/24") + "250" - $provGW = $SDNConfig.BGPRouterIP_ProviderNetwork.TrimEnd("/24") - $provpfx = $SDNConfig.BGPRouterIP_ProviderNetwork.Split("/")[1] - $vlanpfx = $SDNConfig.BGPRouterIP_VLAN200.Split("/")[1] - $simInternetIP = $SDNConfig.BGPRouterIP_SimulatedInternet.TrimEnd("1/24") + "254" - $simInternetPFX = $SDNConfig.BGPRouterIP_SimulatedInternet.Split("/")[1] - - New-VMSwitch -SwitchName NAT -SwitchType Internal -MinimumBandwidthMode None | Out-Null - New-NetIPAddress -IPAddress $natIP -PrefixLength $Prefix -InterfaceAlias "vEthernet (NAT)" | Out-Null - New-NetNat -Name NATNet -InternalIPInterfaceAddressPrefix $natSubnet | Out-Null - - $VerbosePreference = "Continue" - Write-Verbose "Configuring Provider NIC on $env:COMPUTERNAME" - $VerbosePreference = "SilentlyContinue" - - $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq "PROVIDER" } - Rename-NetAdapter -name $NIC.name -newname "PROVIDER" | Out-Null - New-NetIPAddress -InterfaceAlias "PROVIDER" -IPAddress $provIP -PrefixLength $provpfx | Out-Null - - $VerbosePreference = "Continue" - Write-Verbose "Configuring VLAN200 NIC on $env:COMPUTERNAME" - $VerbosePreference = "SilentlyContinue" - - $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq "VLAN200" } - Rename-NetAdapter -name $NIC.name -newname "VLAN200" | Out-Null - New-NetIPAddress -InterfaceAlias "VLAN200" -IPAddress $vlan200IP -PrefixLength $vlanpfx | Out-Null - - $VerbosePreference = "Continue" - Write-Verbose "Configuring simulatedInternet NIC on $env:COMPUTERNAME" - $VerbosePreference = "SilentlyContinue" - - - $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 - - Write-Verbose "Making NAT Work" - - $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" ` - | Where-Object { $_.RegistryValue -eq "Network Adapter" -or $_.RegistryValue -eq "NAT" } - - Rename-NetAdapter -name $NIC.name -newname "Internet" | Out-Null - - $internetIP = $SDNConfig.natHostSubnet.Replace("0/24", "5") - $internetGW = $SDNConfig.natHostSubnet.Replace("0/24", "1") - - Start-Sleep -Seconds 30 - - $internetIndex = (Get-NetAdapter | Where-Object { $_.Name -eq "Internet" }).ifIndex - - Start-Sleep -Seconds 30 - - New-NetIPAddress -IPAddress $internetIP -PrefixLength 24 -InterfaceIndex $internetIndex -DefaultGateway $internetGW -AddressFamily IPv4 | Out-Null - Set-DnsClientServerAddress -InterfaceIndex $internetIndex -ServerAddresses ($SDNConfig.natDNS) | Out-Null - - #Enable Large MTU - - $VerbosePreference = "Continue" - Write-Verbose "Configuring MTU on all Adapters" - $VerbosePreference = "SilentlyContinue" - Get-NetAdapter | Where-Object { $_.Status -eq "Up" -and $_.Name -ne "Ethernet" } | Set-NetAdapterAdvancedProperty ` - -RegistryValue $SDNConfig.SDNLABMTU -RegistryKeyword "*JumboPacket" - $VerbosePreference = "Continue" - - Start-Sleep -Seconds 30 - - #Provision Public and Private VIP Route - - New-NetRoute -DestinationPrefix $SDNConfig.PublicVIPSubnet -NextHop $provGW -InterfaceAlias PROVIDER | Out-Null - - # Remove Gateway from Fabric NIC - Write-Verbose "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 - - } - - } - - Catch { - - throw $_ - - } - - } - - # Provision BGP TOR Router - New-RouterVM -SDNConfig $SDNConfig -localCred $localCred -domainCred $domainCred | Out-Null - - # Provision Domain Controller - Write-Verbose "Provisioning Domain Controller VM" - New-DCVM -SDNConfig $SDNConfig -localCred $localCred -domainCred $domainCred | Out-Null - - # Join AzSHOSTs to Domain - Invoke-Command -VMName AzSMGMT -Credential $localCred -ScriptBlock { - - $SDNConfig = $using:SDNConfig - $VerbosePreference = "Continue" - - function AddAzSHOSTToDomain { - - Param ( - - $IP, - $localCred, - $domainCred, - $AzSHOSTName, - $SDNConfig - - ) - - Write-Verbose "Joining host $AzSHOSTName ($ip) to domain" - - Try { - - $AzSHOSTTest = Test-Connection $IP -Quiet - - While (!$AzSHOSTTest) { - Write-Host "Unable to contact computer $AzSHOSTname at $IP. Please make sure the system is contactable before continuing and the Press Enter to continue." ` - -ForegroundColor Red - pause - $AzSHOSTTest = Test-Connection $AzSHOSTName -Quiet -Count 1 - } - - While ($DomainJoined -ne $SDNConfig.SDNDomainFQDN) { - - $params = @{ - - ComputerName = $IP - Credential = $localCred - ArgumentList = ($domainCred, $SDNConfig.SDNDomainFQDN) - } - - - $job = Invoke-Command @params -ScriptBlock { add-computer -DomainName $args[1] -Credential $args[0] } -AsJob - - While ($Job.JobStateInfo.State -ne "Completed") { Start-Sleep -Seconds 10 } - $DomainJoined = (Get-WmiObject -ComputerName $ip -Class win32_computersystem).domain - } - - Restart-Computer -ComputerName $IP -Credential $localCred -Force - - } - - Catch { - - throw $_ - - } - - } - - # Set VM Path for Physical Hosts - try { - - $AzSHOST1 = $SDNConfig.AzSHOST1IP.Split("/")[0] - $AzSHOST2 = $SDNConfig.AzSHOST2IP.Split("/")[0] - - Write-Verbose "Setting VMStorage Path for all Hosts" - - Invoke-Command -ComputerName $AzSHOST1 -ArgumentList $VMStoragePathforOtherHosts ` - -ScriptBlock { Set-VMHost -VirtualHardDiskPath $args[0] -VirtualMachinePath $args[0] } ` - -Credential $using:localCred -AsJob | Out-Null - Invoke-Command -ComputerName $AzSHOST2 -ArgumentList $VMStoragePathforOtherHosts ` - -ScriptBlock { Set-VMHost -VirtualHardDiskPath $args[0] -VirtualMachinePath $args[0] } ` - -Credential $using:localCred -AsJob | Out-Null - - # 2nd pass - Invoke-Command -ComputerName $AzSHOST1 -ArgumentList $VMStoragePathforOtherHosts ` - -ScriptBlock { Set-VMHost -VirtualHardDiskPath $args[0] -VirtualMachinePath $args[0] } ` - -Credential $using:localCred -AsJob | Out-Null - Invoke-Command -ComputerName $AzSHOST2 -ArgumentList $VMStoragePathforOtherHosts ` - -ScriptBlock { Set-VMHost -VirtualHardDiskPath $args[0] -VirtualMachinePath $args[0] } ` - -Credential $using:localCred -AsJob | Out-Null - - } - - catch { - - throw $_ - - } - - # Add AzSHOSTS to domain - try { - - Write-Verbose "Adding HCIBox Hosts to the Domain" - AddAzSHOSTToDomain -IP $AzSHOST1 -localCred $using:localCred -domainCred $using:domainCred -AzSHOSTName AzSHOST1 -SDNConfig $SDNConfig - AddAzSHOSTToDomain -IP $AzSHOST2 -localCred $using:localCred -domainCred $using:domainCred -AzSHOSTName AzSHOST2 -SDNConfig $SDNConfig - } - catch { - throw $_ - } - } | Out-Null - - # Provision Admincenter - Write-Verbose "Provisioning admincenter VM" - $domainCred = new-object -typename System.Management.Automation.PSCredential -argumentlist (($SDNConfig.SDNDomainFQDN.Split(".")[0]) + "\$env:adminUsername"), (ConvertTo-SecureString $SDNConfig.SDNAdminPassword -AsPlainText -Force) - New-AdminCenterVM -SDNConfig $SDNConfig -localCred $localCred -domainCred $domainCred | Out-Null - -} - -function New-DCVM { - Param ( - $SDNConfig, - $localCred, - $domainCred - ) - - $ErrorActionPreference = "Continue" - $adminUser = $env:adminUsername - Invoke-Command -VMName AzSMGMT -Credential $localCred -ScriptBlock { - $adminUser = $using:adminUser - $SDNConfig = $using:SDNConfig - $localcred = $using:localcred - $domainCred = $using:domainCred - $ParentDiskPath = "C:\VMs\Base\" - $vmpath = "D:\VMs\" - $OSVHDX = "GUI.vhdx" - $VMName = $SDNConfig.DCName - - $ProgressPreference = "SilentlyContinue" - $ErrorActionPreference = "Stop" - $VerbosePreference = "Continue" - $WarningPreference = "SilentlyContinue" - - # Create Virtual Machine - Write-Verbose "Creating $VMName differencing disks" - $params = @{ - ParentPath = ($ParentDiskPath + $OSVHDX) - Path = ($vmpath + $VMName + '\' + $VMName + '.vhdx') - } - New-VHD @params -Differencing | Out-Null - - Write-Verbose "Creating $VMName virtual machine" - $params = @{ - Name = $VMName - VHDPath = ($vmpath + $VMName + '\' + $VMName + '.vhdx') - Path = ($vmpath + $VMName) - Generation = 2 - } - New-VM @params | Out-Null - - Write-Verbose "Setting $VMName Memory" - $params = @{ - VMName = $VMName - DynamicMemoryEnabled = $true - StartupBytes = $SDNConfig.MEM_DC - MaximumBytes = $SDNConfig.MEM_DC - MinimumBytes = 500MB - } - Set-VMMemory @params | Out-Null - - Write-Verbose "Configuring $VMName's networking" - Remove-VMNetworkAdapter -VMName $VMName -Name "Network Adapter" | Out-Null - $params = @{ - VMName = $VMName - Name = $SDNConfig.DCName - SwitchName = 'vSwitch-Fabric' - DeviceNaming = 'On' - } - Add-VMNetworkAdapter @params | Out-Null - Write-Verbose "Configuring $VMName's settings" - Set-VMProcessor -VMName $VMName -Count 2 | Out-Null - Set-VM -Name $VMName -AutomaticStartAction Start -AutomaticStopAction ShutDown | Out-Null - - # Add NIC for VLAN200 for DHCP server - Add-VMNetworkAdapter -VMName $VMName -Name "VLAN200" -SwitchName "vSwitch-Fabric" -DeviceNaming "On" - Get-VMNetworkAdapter -VMName $VMName -Name "VLAN200" | Set-VMNetworkAdapterVLAN -Access -VlanId $SDNConfig.AKSVlanID - - # Inject Answer File - Write-Verbose "Mounting and injecting answer file into the $VMName VM." - $VerbosePreference = "SilentlyContinue" - - New-Item -Path "C:\TempMount" -ItemType Directory | Out-Null - Mount-WindowsImage -Path "C:\TempMount" -Index 1 -ImagePath ($vmpath + $VMName + '\' + $VMName + '.vhdx') | Out-Null - - $VerbosePreference = "Continue" - Write-Verbose "Applying Unattend file to Disk Image..." - - $password = $SDNConfig.SDNAdminPassword - $Unattend = @" -<?xml version="1.0" encoding="utf-8"?> -<unattend xmlns="urn:schemas-microsoft-com:unattend"> - <servicing> - <package action="configure"> - <assemblyIdentity name="Microsoft-Windows-Foundation-Package" version="10.0.14393.0" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="" /> - <selection name="ADCertificateServicesRole" state="true" /> - <selection name="CertificateServices" state="true" /> - </package> - </servicing> - <settings pass="specialize"> - <component name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <DomainProfile_EnableFirewall>false</DomainProfile_EnableFirewall> - <PrivateProfile_EnableFirewall>false</PrivateProfile_EnableFirewall> - <PublicProfile_EnableFirewall>false</PublicProfile_EnableFirewall> - </component> - <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <ComputerName>$VMName</ComputerName> - </component> - <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <fDenyTSConnections>false</fDenyTSConnections> - </component> - <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <UserLocale>en-us</UserLocale> - <UILanguage>en-us</UILanguage> - <SystemLocale>en-us</SystemLocale> - <InputLocale>en-us</InputLocale> - </component> - </settings> - <settings pass="oobeSystem"> - <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <OOBE> - <HideEULAPage>true</HideEULAPage> - <SkipMachineOOBE>true</SkipMachineOOBE> - <SkipUserOOBE>true</SkipUserOOBE> - <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> - </OOBE> - <UserAccounts> - <AdministratorPassword> - <Value>$password</Value> - <PlainText>true</PlainText> - </AdministratorPassword> - </UserAccounts> - </component> - </settings> - <cpi:offlineImage cpi:source="" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> -</unattend> -"@ - - New-Item -Path C:\TempMount\windows -ItemType Directory -Name Panther -Force | Out-Null - Set-Content -Value $Unattend -Path "C:\TempMount\Windows\Panther\Unattend.xml" -Force - - Write-Verbose "Dismounting Windows Image" - Dismount-WindowsImage -Path "C:\TempMount" -Save | Out-Null - Remove-Item "C:\TempMount" | Out-Null - - # Start Virtual Machine - - Write-Verbose "Starting Virtual Machine" - Start-VM -Name $VMName | Out-Null - - # Wait until the VM is restarted - while ((Invoke-Command -VMName $VMName -Credential $using:domainCred { "Test" } ` - -ea SilentlyContinue) -ne "Test") { Start-Sleep -Seconds 1 } - - Write-Verbose "Configuring Domain Controller VM and Installing Active Directory." - - $ErrorActionPreference = "SilentlyContinue" - - Invoke-Command -VMName $VMName -Credential $localCred -ArgumentList $SDNConfig -ScriptBlock { - - $SDNConfig = $args[0] - - $VerbosePreference = "Continue" - $WarningPreference = "SilentlyContinue" - $ErrorActionPreference = "Stop" - $DCName = $SDNConfig.DCName - $IP = $SDNConfig.SDNLABDNS - $PrefixLength = ($SDNConfig.AzSMGMTIP.split("/"))[1] - $SDNLabRoute = $SDNConfig.SDNLABRoute - $DomainFQDN = $SDNConfig.SDNDomainFQDN - $DomainNetBiosName = $DomainFQDN.Split(".")[0] - - Write-Verbose "Configuring NIC Settings for Domain Controller" - $VerbosePreference = "SilentlyContinue" - $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq $DCName } - 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 - $VerbosePreference = "Continue" - - Write-Verbose "Configuring NIC settings for DC VLAN200" - $VerbosePreference = "SilentlyContinue" - $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq "VLAN200" } - Rename-NetAdapter -name $NIC.name -newname VLAN200 | Out-Null - New-NetIPAddress -InterfaceAlias VLAN200 -IPAddress $SDNConfig.dcVLAN200IP -PrefixLength ($SDNConfig.AKSIPPrefix.split("/"))[1] -DefaultGateway $SDNConfig.AKSGWIP | Out-Null - $VerbosePreference = "Continue" - - Write-Verbose "Configuring Trusted Hosts" - Set-Item WSMan:\localhost\Client\TrustedHosts * -Confirm:$false -Force - - Write-Verbose "Installing Active Directory Forest. This will take some time..." - - $SecureString = ConvertTo-SecureString $SDNConfig.SDNAdminPassword -AsPlainText -Force - Write-Verbose "Installing Active Directory..." - $params = @{ - DomainName = $DomainFQDN - DomainMode = 'WinThreshold' - DatabasePath = "C:\Domain" - DomainNetBiosName = $DomainNetBiosName - SafeModeAdministratorPassword = $SecureString - } - Write-Output $params - - $VerbosePreference = "SilentlyContinue" - Install-ADDSForest @params -InstallDns -Confirm -Force -NoRebootOnCompletion # | Out-Null - } - $ErrorActionPreference = "Stop" - - Write-Verbose "Stopping $VMName" - Get-VM $VMName | Stop-VM - Write-Verbose "Starting $VMName" - Get-VM $VMName | Start-VM - - # Wait until DC is created and rebooted - - while ((Invoke-Command -VMName $VMName -Credential $using:domainCred ` - -ArgumentList $SDNConfig.DCName { (Get-ADDomainController $args[0]).enabled } -ea SilentlyContinue) -ne $true) { Start-Sleep -Seconds 1 } - - $VerbosePreference = "Continue" - Write-Verbose "Configuring User Accounts and Groups in Active Directory" - - - $ErrorActionPreference = "Continue" - $SDNConfig.adminUser = $using:adminUser - Invoke-Command -VMName $VMName -Credential $using:domainCred -ArgumentList $SDNConfig -ScriptBlock { - - $SDNConfig = $args[0] - $SDNDomainFQDN = $SDNConfig.SDNDomainFQDN - - $VerbosePreference = "Continue" - $ErrorActionPreference = "Stop" - - $SecureString = ConvertTo-SecureString $SDNConfig.SDNAdminPassword -AsPlainText -Force - - $params = @{ - ComplexityEnabled = $false - Identity = $SDNConfig.SDNDomainFQDN - MinPasswordLength = 0 - } - - Set-ADDefaultDomainPasswordPolicy @params - - $params = @{ - Name = 'NC Admin' - GivenName = 'NC' - Surname = 'Admin' - SamAccountName = 'NCAdmin' - UserPrincipalName = "NCAdmin@$SDNDomainFQDN" - AccountPassword = $SecureString - Enabled = $true - ChangePasswordAtLogon = $false - CannotChangePassword = $true - PasswordNeverExpires = $true - } - - New-ADUser @params - $params = @{ - Name = $SDNConfig.adminUser - GivenName = 'Jumpstart' - Surname = 'Jumpstart' - SamAccountName = $SDNConfig.adminUser - UserPrincipalName = "$SDNConfig.adminUser@$SDNDomainFQDN" - AccountPassword = $SecureString - Enabled = $true - ChangePasswordAtLogon = $false - CannotChangePassword = $true - PasswordNeverExpires = $true - } - - New-ADUser @params - - $params.Name = 'NC Client' - $params.Surname = 'Client' - $params.SamAccountName = 'NCClient' - $params.UserPrincipalName = "NCClient@$SDNDomainFQDN" - - New-ADUser @params - - NEW-ADGroup -name “NCAdmins” -groupscope Global - NEW-ADGroup -name “NCClients” -groupscope Global - - add-ADGroupMember "Domain Admins" "NCAdmin" - add-ADGroupMember "NCAdmins" "NCAdmin" - add-ADGroupMember "NCClients" "NCClient" - add-ADGroupMember "NCClients" "Administrator" - add-ADGroupMember "NCAdmins" "Administrator" - add-ADGroupMember "Domain Admins" $SDNConfig.adminUser - add-ADGroupMember "NCAdmins" $SDNConfig.adminUser - add-ADGroupMember "NCClients" $SDNConfig.adminUser - - # Set Administrator Account Not to Expire - - Get-ADUser Administrator | Set-ADUser -PasswordNeverExpires $true -CannotChangePassword $true - - # Set DNS Forwarder - - Write-Verbose "Adding DNS Forwarders" - $VerbosePreference = "SilentlyContinue" - - if ($SDNConfig.natDNS) { Add-DnsServerForwarder $SDNConfig.natDNS } - else { Add-DnsServerForwarder 8.8.8.8 } - - # Create Enterprise CA - - $VerbosePreference = "Continue" - Write-Verbose "Installing and Configuring Active Directory Certificate Services and Certificate Templates" - $VerbosePreference = "SilentlyContinue" - - - - Install-WindowsFeature -Name AD-Certificate -IncludeAllSubFeature -IncludeManagementTools | Out-Null - - $params = @{ - - CAtype = 'EnterpriseRootCa' - CryptoProviderName = 'ECDSA_P256#Microsoft Software Key Storage Provider' - KeyLength = 256 - HashAlgorithmName = 'SHA256' - ValidityPeriod = 'Years' - ValidityPeriodUnits = 10 - } - - Install-AdcsCertificationAuthority @params -Confirm:$false | Out-Null - - # Give WebServer Template Enroll rights for Domain Computers - - $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() - - if ($Template -ne $null) { - $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" - - } - - # Set up DHCP scope for Arc resource bridge - Invoke-Command -VMName $SDNConfig.DCName -Credential $using:domainCred -ArgumentList $SDNConfig -ScriptBlock { - $SDNConfig = $args[0] - - # Install DHCP feature - Install-WindowsFeature DHCP -IncludeManagementTools - CMD.exe /c "netsh dhcp add securitygroups" - Restart-Service dhcpserver - - # Allow DHCP in domain - $dnsName = $SDNConfig.DCName - $fqdnsName = $SDNConfig.DCName + "." + $SDNConfig.SDNDomainFQDN - Add-DhcpServerInDC -DnsName $fqdnsName -IPAddress $SDNConfig.dcVLAN200IP - Get-DHCPServerInDC - - # Configure dynamic DNS updates for DHCP records - #Set-DhcpServerv4DnsSetting -ComputerName "jumpstartdc.jumpstart.local" -DynamicUpdates "Always" -DeleteDnsRRonLeaseExpiry $True - #$Credential = Get-Credential - #Set-DhcpServerDnsCredential -Credential $Credential -ComputerName "jumpstartdc.jumpstart.local" - - # Bind DHCP only to VLAN200 NIC - Set-DhcpServerv4Binding -ComputerName $dnsName -InterfaceAlias $dnsName -BindingState $false - Set-DhcpServerv4Binding -ComputerName $dnsName -InterfaceAlias VLAN200 -BindingState $true - - # Add DHCP scope for Resource bridge VMs - Add-DhcpServerv4Scope -name "ResourceBridge" -StartRange $SDNConfig.rbVipStart -EndRange $SDNConfig.rbVipEnd -SubnetMask 255.255.255.0 -State Active - $scope = Get-DhcpServerv4Scope - Add-DhcpServerv4ExclusionRange -ScopeID $scope.ScopeID.IPAddressToString -StartRange $SDNConfig.rbDHCPExclusionStart -EndRange $SDNConfig.rbDHCPExclusionEnd - #Set-DhcpServerv4OptionValue -OptionID 3 -Value $SDNConfig.BGPRouterIP_VLAN200.Trim("/24") -ScopeID $scope.ScopeID.IPAddressToString -ComputerName $dnsName - Set-DhcpServerv4OptionValue -ComputerName $dnsName -ScopeId $scope.ScopeID.IPAddressToString -DnsServer $SDNConfig.SDNLABDNS -Router $SDNConfig.BGPRouterIP_VLAN200.Trim("/24") - } - } - $ErrorActionPreference = "Stop" -} - -function New-RouterVM { - - Param ( - - $SDNConfig, - $localCred, - $domainCred - - ) - - Invoke-Command -VMName AzSMGMT -Credential $localCred -ScriptBlock { - - $SDNConfig = $using:SDNConfig - $localcred = $using:localcred - $domainCred = $using:domainCred - $ParentDiskPath = "C:\VMs\Base\" - $vmpath = "D:\VMs\" - $OSVHDX = "AzSHCI.vhdx" - - $ProgressPreference = "SilentlyContinue" - $ErrorActionPreference = "Stop" - $VerbosePreference = "Continue" - $WarningPreference = "SilentlyContinue" - - $VMName = "bgp-tor-router" - - # Create Host OS Disk - - Write-Verbose "Creating $VMName differencing disks" - - $params = @{ - - ParentPath = ($ParentDiskPath + $OSVHDX) - Path = ($vmpath + $VMName + '\' + $VMName + '.vhdx') - - } - - New-VHD @params -Differencing | Out-Null - - # Create VM - - $params = @{ - - Name = $VMName - VHDPath = ($vmpath + $VMName + '\' + $VMName + '.vhdx') - Path = ($vmpath + $VMName) - Generation = 2 - - } - - Write-Verbose "Creating the $VMName VM." - New-VM @params | Out-Null - - # Set VM Configuration - - Write-Verbose "Setting $VMName's VM Configuration" - - $params = @{ - - VMName = $VMName - DynamicMemoryEnabled = $true - StartupBytes = $SDNConfig.MEM_BGP - MaximumBytes = $SDNConfig.MEM_BGP - MinimumBytes = 500MB - } - - Set-VMMemory @params | Out-Null - Remove-VMNetworkAdapter -VMName $VMName -Name "Network Adapter" | Out-Null - Set-VMProcessor -VMName $VMName -Count 2 | Out-Null - set-vm -Name $VMName -AutomaticStopAction TurnOff | Out-Null - - # Configure VM Networking - - Write-Verbose "Configuring $VMName's Networking" - Add-VMNetworkAdapter -VMName $VMName -Name Mgmt -SwitchName vSwitch-Fabric -DeviceNaming On - Add-VMNetworkAdapter -VMName $VMName -Name Provider -SwitchName vSwitch-Fabric -DeviceNaming On - Add-VMNetworkAdapter -VMName $VMName -Name VLAN200 -SwitchName vSwitch-Fabric -DeviceNaming On - Add-VMNetworkAdapter -VMName $VMName -Name SIMInternet -SwitchName vSwitch-Fabric -DeviceNaming On - Set-VMNetworkAdapterVlan -VMName $VMName -VMNetworkAdapterName Provider -Access -VlanId $SDNConfig.providerVLAN - Set-VMNetworkAdapterVlan -VMName $VMName -VMNetworkAdapterName VLAN200 -Access -VlanId $SDNConfig.vlan200VLAN - Set-VMNetworkAdapterVlan -VMName $VMName -VMNetworkAdapterName SIMInternet -Access -VlanId $SDNConfig.simInternetVLAN - - - # Add NAT Adapter - - if ($SDNConfig.natConfigure) { - - Add-VMNetworkAdapter -VMName $VMName -Name NAT -SwitchName NAT -DeviceNaming On - } - - # Configure VM - Set-VMProcessor -VMName $VMName -Count 2 - Set-VM -Name $VMName -AutomaticStartAction Start -AutomaticStopAction ShutDown | Out-Null - - # Inject Answer File - - Write-Verbose "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 - - $Password = $SDNConfig.SDNAdminPassword - - $Unattend = @" -<?xml version="1.0" encoding="utf-8"?> -<unattend xmlns="urn:schemas-microsoft-com:unattend"> - <servicing> - <package action="configure"> - <assemblyIdentity name="Microsoft-Windows-Foundation-Package" version="10.0.14393.0" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="" /> - <selection name="RemoteAccessServer" state="true" /> - <selection name="RasRoutingProtocols" state="true" /> - </package> - </servicing> - <settings pass="specialize"> - <component name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <DomainProfile_EnableFirewall>false</DomainProfile_EnableFirewall> - <PrivateProfile_EnableFirewall>false</PrivateProfile_EnableFirewall> - <PublicProfile_EnableFirewall>false</PublicProfile_EnableFirewall> - </component> - <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <ComputerName>$VMName</ComputerName> - </component> - <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <fDenyTSConnections>false</fDenyTSConnections> - </component> - <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <UserLocale>en-us</UserLocale> - <UILanguage>en-us</UILanguage> - <SystemLocale>en-us</SystemLocale> - <InputLocale>en-us</InputLocale> - </component> - </settings> - <settings pass="oobeSystem"> - <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <OOBE> - <HideEULAPage>true</HideEULAPage> - <SkipMachineOOBE>true</SkipMachineOOBE> - <SkipUserOOBE>true</SkipUserOOBE> - <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> - </OOBE> - <UserAccounts> - <AdministratorPassword> - <Value>$Password</Value> - <PlainText>true</PlainText> - </AdministratorPassword> - </UserAccounts> - </component> - </settings> - <cpi:offlineImage cpi:source="" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> - </unattend> -"@ - Set-Content -Value $Unattend -Path "C:\TempBGPMount\Windows\Panther\Unattend.xml" -Force - - Write-Verbose "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-Verbose "Dismounting Disk Image for $VMName VM." - Dismount-WindowsImage -Path "C:\TempBGPMount" -Save | Out-Null - Remove-Item "C:\TempBGPMount" - - # Start the VM - - Write-Verbose "Starting $VMName VM." - 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-Verbose "Configuring $VMName" - - Invoke-Command -VMName $VMName -Credential $localCred -ArgumentList $SDNConfig -ScriptBlock { - - $ErrorActionPreference = "Stop" - $VerbosePreference = "Continue" - $WarningPreference = "SilentlyContinue" - - $SDNConfig = $args[0] - $DNS = $SDNConfig.SDNLABDNS - $natSubnet = $SDNConfig.natSubnet - $natDNS = $SDNConfig.natSubnet - $MGMTIP = $SDNConfig.BGPRouterIP_MGMT.Split("/")[0] - $MGMTPFX = $SDNConfig.BGPRouterIP_MGMT.Split("/")[1] - $PNVIP = $SDNConfig.BGPRouterIP_ProviderNetwork.Split("/")[0] - $PNVPFX = $SDNConfig.BGPRouterIP_ProviderNetwork.Split("/")[1] - $VLANIP = $SDNConfig.BGPRouterIP_VLAN200.Split("/")[0] - $VLANPFX = $SDNConfig.BGPRouterIP_VLAN200.Split("/")[1] - $simInternetIP = $SDNConfig.BGPRouterIP_SimulatedInternet.Split("/")[0] - $simInternetPFX = $SDNConfig.BGPRouterIP_SimulatedInternet.Split("/")[1] - - # Renaming NetAdapters and setting up the IPs inside the VM using CDN parameters - - Write-Verbose "Configuring $env:COMPUTERNAME's Networking" - $VerbosePreference = "SilentlyContinue" - $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 "SIMInternet" } - Rename-NetAdapter -name $NIC.name -newname "SIMInternet" | Out-Null - New-NetIPAddress -InterfaceAlias "SIMInternet" -IPAddress $simInternetIP -PrefixLength $simInternetPFX | Out-Null - - # if NAT is selected, configure the adapter - - if ($SDNConfig.natConfigure) { - - $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" ` - | Where-Object { $_.RegistryValue -eq "NAT" } - Rename-NetAdapter -name $NIC.name -newname "NAT" | Out-Null - $Prefix = ($natSubnet.Split("/"))[1] - $natIP = ($natSubnet.TrimEnd("0./$Prefix")) + (".10") - $natGW = ($natSubnet.TrimEnd("0./$Prefix")) + (".1") - New-NetIPAddress -InterfaceAlias "NAT" -IPAddress $natIP -PrefixLength $Prefix -DefaultGateway $natGW | Out-Null - if ($natDNS) { - Set-DnsClientServerAddress -InterfaceAlias "NAT" -ServerAddresses $natDNS | Out-Null - } - } - - # Configure Trusted Hosts - - Write-Verbose "Configuring Trusted Hosts" - Set-Item WSMan:\localhost\Client\TrustedHosts * -Confirm:$false -Force - - - # Installing Remote Access - - Write-Verbose "Installing Remote Access on $env:COMPUTERNAME" - $VerbosePreference = "SilentlyContinue" - Install-RemoteAccess -VPNType RoutingOnly | Out-Null - - # Adding a BGP Router to the VM - - $VerbosePreference = "Continue" - Write-Verbose "Installing BGP Router on $env:COMPUTERNAME" - $VerbosePreference = "SilentlyContinue" - - $params = @{ - - BGPIdentifier = $PNVIP - LocalASN = $SDNConfig.BGPRouterASN - TransitRouting = 'Enabled' - ClusterId = 1 - RouteReflector = 'Enabled' - - } - - Add-BgpRouter @params - - #Add-BgpRouter -BGPIdentifier $PNVIP -LocalASN $SDNConfig.BGPRouterASN ` - # -TransitRouting Enabled -ClusterId 1 -RouteReflector Enabled - - # Configure BGP Peers - - if ($SDNConfig.ConfigureBGPpeering -and $SDNConfig.ProvisionNC) { - - Write-Verbose "Peering future MUX/GWs" - - $Mux01IP = ($SDNConfig.BGPRouterIP_ProviderNetwork.TrimEnd("1/24")) + "4" - $GW01IP = ($SDNConfig.BGPRouterIP_ProviderNetwork.TrimEnd("1/24")) + "5" - $GW02IP = ($SDNConfig.BGPRouterIP_ProviderNetwork.TrimEnd("1/24")) + "6" - - $params = @{ - - Name = 'MUX01' - LocalIPAddress = $PNVIP - PeerIPAddress = $Mux01IP - PeerASN = $SDNConfig.SDNASN - OperationMode = 'Mixed' - PeeringMode = 'Automatic' - } - - Add-BgpPeer @params -PassThru - - $params.Name = 'GW01' - $params.PeerIPAddress = $GW01IP - - Add-BgpPeer @params -PassThru - - $params.Name = 'GW02' - $params.PeerIPAddress = $GW02IP - - Add-BgpPeer @params -PassThru - - } - - # Enable Large MTU - - Write-Verbose "Configuring MTU on all Adapters" - Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Set-NetAdapterAdvancedProperty -RegistryValue $SDNConfig.SDNLABMTU -RegistryKeyword "*JumboPacket" - -# # Enable DHCP Relay -# $routerNetAdapterName = "VLAN200" -# $netshDhcpRelay=@" -# pushd routing ip relay -# install -# set global loglevel=ERROR -# add dhcpserver $($SDNConfig.DCIP) -# add interface name="$routerNetAdapterName" -# set interface name="$routerNetAdapterName" relaymode=enable maxhop=6 minsecs=6 -# popd -# "@ - -# $netshDhcpRelayPath="$ENV:TEMP\netshDhcpRelay" - - # # Create netsh script file - # New-Item -Path $netshDhcpRelayPath -Type File -ErrorAction SilentlyContinue | Out-Null - - # # Populate contents of the script - # Set-Content -Path $netshDhcpRelayPath -Value $netshDhcpRelay.Split("`r`n") -Encoding ASCII - - # # run it - # CMD.exe /c "netsh -f $netshDhcpRelayPath" - } - - $ErrorActionPreference = "Continue" - $VerbosePreference = "SilentlyContinue" - $WarningPreference = "Continue" - - } -AsJob - -} - -function New-AdminCenterVM { - Param ( - - $SDNConfig, - $localCred, - $domainCred - - ) - - $domainAdminUsername = $env:adminUsername - - Invoke-Command -VMName AzSMGMT -Credential $localCred -ScriptBlock { - - $VMName = "admincenter" - $ParentDiskPath = "C:\VMs\Base\" - $VHDPath = "D:\VMs\" - $OSVHDX = "GUI.vhdx" - $BaseVHDPath = $ParentDiskPath + $OSVHDX - $SDNConfig = $using:SDNConfig - - $ProgressPreference = "SilentlyContinue" - $ErrorActionPreference = "Stop" - $VerbosePreference = "Continue" - $WarningPreference = "SilentlyContinue" - - # Set Credentials - $localCred = $using:localCred - $domainCred = $using:domainCred - - # Create Host OS Disk - Write-Verbose "Creating $VMName differencing disks" - - $params = @{ - - ParentPath = $BaseVHDPath - Path = (($VHDPath) + ($VMName) + (".vhdx")) - } - - New-VHD -Differencing @params | out-null - - # MountVHDXFile - $VerbosePreference = "SilentlyContinue" - Import-Module DISM - $VerbosePreference = "Continue" - - Write-Verbose "Mounting $VMName VHD." - New-Item -Path "C:\TempWACMount" -ItemType Directory | Out-Null - Mount-WindowsImage -Path "C:\TempWACMount" -Index 1 -ImagePath (($VHDPath) + ($VMName) + (".vhdx")) | Out-Null - - # Copy Source Files - Write-Verbose "Copying Application and Script Source Files to $VMName" - Copy-Item 'C:\VMConfigs\Windows Admin Center' -Destination C:\TempWACMount\ -Recurse -Force - Copy-Item C:\VMConfigs\SDN -Destination C:\TempWACMount -Recurse -Force - New-Item -Path C:\TempWACMount\VHDs -ItemType Directory -Force | Out-Null - Copy-Item C:\VMs\Base\AzSHCI.vhdx -Destination C:\TempWACMount\VHDs -Force - Copy-Item C:\VMs\Base\GUI.vhdx -Destination C:\TempWACMount\VHDs -Force - - # Create VM - Write-Verbose "Creating the $VMName VM." - - $params = @{ - - Name = $VMName - VHDPath = (($VHDPath) + ($VMName) + (".vhdx")) - Path = $VHDPath - Generation = 2 - } - - New-VM @params | Out-Null - - $params = @{ - - VMName = $VMName - DynamicMemoryEnabled = $true - StartupBytes = $SDNConfig.MEM_WAC - MaximumBytes = $SDNConfig.MEM_WAC - MinimumBytes = 500mb - } - - Set-VMMemory @params | Out-Null - Set-VM -Name $VMName -AutomaticStartAction Start -AutomaticStopAction ShutDown | Out-Null - - Write-Verbose "Configuring $VMName's Networking" - Remove-VMNetworkAdapter -VMName $VMName -Name "Network Adapter" - Add-VMNetworkAdapter -VMName $VMName -Name "Fabric" -SwitchName "vSwitch-Fabric" -DeviceNaming On - Set-VMNetworkAdapter -VMName $VMName -StaticMacAddress "10155D010B00" - - # Apply Custom Unattend.xml file - New-Item -Path C:\TempWACMount\windows -ItemType Directory -Name Panther -Force | Out-Null - $Password = $SDNConfig.SDNAdminPassword - $ProductKey = $SDNConfig.GUIProductKey - $Gateway = $SDNConfig.SDNLABRoute - $DNS = $SDNConfig.SDNLABDNS - $IPAddress = $SDNConfig.WACIP - $Domain = $SDNConfig.SDNDomainFQDN - - $Unattend = @" -<?xml version="1.0" encoding="utf-8"?> -<unattend xmlns="urn:schemas-microsoft-com:unattend"> - <settings pass="specialize"> - <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <ProductKey>$ProductKey</ProductKey> - <ComputerName>$VMName</ComputerName> - <RegisteredOwner>$ENV:USERNAME</RegisteredOwner> - </component> - <component name="Microsoft-Windows-TCPIP" processorArchitecture="wow64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <Interfaces> - <Interface wcm:action="add"> - <Ipv4Settings> - <DhcpEnabled>false</DhcpEnabled> - <Metric>20</Metric> - <RouterDiscoveryEnabled>true</RouterDiscoveryEnabled> - </Ipv4Settings> - <UnicastIpAddresses> - <IpAddress wcm:action="add" wcm:keyValue="1">$IPAddress</IpAddress> - </UnicastIpAddresses> - <Identifier>10-15-5D-01-0B-00</Identifier> - <Routes> - <Route wcm:action="add"> - <Identifier>1</Identifier> - <NextHopAddress>$Gateway</NextHopAddress> - </Route> - </Routes> - </Interface> - </Interfaces> - </component> - <component name="Microsoft-Windows-DNS-Client" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <Interfaces> - <Interface wcm:action="add"> - <DNSServerSearchOrder> - <IpAddress wcm:action="add" wcm:keyValue="1">$DNS</IpAddress> - </DNSServerSearchOrder> - <Identifier>10-15-5D-01-0B-00</Identifier> - <DNSDomain>$Domain</DNSDomain> - <EnableAdapterDomainNameRegistration>true</EnableAdapterDomainNameRegistration> - </Interface> - </Interfaces> - </component> - <component name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <DomainProfile_EnableFirewall>false</DomainProfile_EnableFirewall> - <PrivateProfile_EnableFirewall>false</PrivateProfile_EnableFirewall> - <PublicProfile_EnableFirewall>false</PublicProfile_EnableFirewall> - </component> - <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <fDenyTSConnections>false</fDenyTSConnections> - </component> - <component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <Identification> - <Credentials> - <Domain>$Domain</Domain> - <Password>$Password</Password> - <Username>Administrator</Username> - </Credentials> - <JoinDomain>$Domain</JoinDomain> - </Identification> - </component> - <component name="Microsoft-Windows-IE-ESC" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <IEHardenAdmin>false</IEHardenAdmin> - <IEHardenUser>false</IEHardenUser> - </component> - </settings> - <settings pass="oobeSystem"> - <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <UserAccounts> - <AdministratorPassword> - <Value>$Password</Value> - <PlainText>true</PlainText> - </AdministratorPassword> - </UserAccounts> - <TimeZone>Pacific Standard Time</TimeZone> - <OOBE> - <HideEULAPage>true</HideEULAPage> - <SkipUserOOBE>true</SkipUserOOBE> - <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> - <HideOnlineAccountScreens>true</HideOnlineAccountScreens> - <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> - <NetworkLocation>Work</NetworkLocation> - <ProtectYourPC>1</ProtectYourPC> - <HideLocalAccountScreen>true</HideLocalAccountScreen> - </OOBE> - </component> - <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <UserLocale>en-US</UserLocale> - <SystemLocale>en-US</SystemLocale> - <InputLocale>0409:00000409</InputLocale> - <UILanguage>en-US</UILanguage> - </component> - </settings> - <cpi:offlineImage cpi:source="" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> -</unattend> -"@ - - Write-Verbose "Mounting and Injecting Answer File into the $VMName VM." - Set-Content -Value $Unattend -Path "C:\TempWACMount\Windows\Panther\Unattend.xml" -Force - - # Save Customizations and then dismount. - Write-Verbose "Dismounting Disk" - Dismount-WindowsImage -Path "C:\TempWACMount" -Save | Out-Null - Remove-Item "C:\TempWACMount" - - Write-Verbose "Setting $VMName's VM Configuration" - Set-VMProcessor -VMName $VMname -Count 4 - set-vm -Name $VMName -AutomaticStopAction TurnOff - - Write-Verbose "Starting $VMName VM." - Start-VM -Name $VMName - - # Refresh Domain Cred - $domainCred = new-object -typename System.Management.Automation.PSCredential ` - -argumentlist (($SDNConfig.SDNDomainFQDN.Split(".")[0]) + "\$using:domainAdminUsername"), ` - (ConvertTo-SecureString $SDNConfig.SDNAdminPassword -AsPlainText -Force) - - # Wait until the VM is restarted - while ((Invoke-Command -VMName $VMName -Credential $domainCred { "Test" } ` - -ea SilentlyContinue) -ne "Test") { Start-Sleep -Seconds 1 } - - # Finish Configuration - Invoke-Command -VMName $VMName -Credential $domainCred -ArgumentList $SDNConfig, $VMName -ScriptBlock { - - $SDNConfig = $args[0] - $VMName = $args[1] - $Gateway = $SDNConfig.SDNLABRoute - $VerbosePreference = "Continue" - $ErrorActionPreference = "Stop" - - $VerbosePreference = "SilentlyContinue" - Import-Module NetAdapter - $VerbosePreference = "Continue" - - # Enabling Remote Access on Admincenter VM - Write-Verbose "Enabling Remote Access" - Enable-WindowsOptionalFeature -FeatureName RasRoutingProtocols -All -LimitAccess -Online | Out-Null - Enable-WindowsOptionalFeature -FeatureName RemoteAccessPowerShell -All -LimitAccess -Online | Out-Null - - Write-Verbose "Configuring WSMAN Trusted Hosts" - Set-Item WSMan:\localhost\Client\TrustedHosts * -Confirm:$false -Force - Enable-WSManCredSSP -Role Client -DelegateComputer * -Force - - Write-Verbose "Rename Network Adapter in $VMName VM" - Get-NetAdapter | Rename-NetAdapter -NewName Fabric - - # Set Gateway - $index = (Get-WmiObject Win32_NetworkAdapter | Where-Object { $_.netconnectionid -eq "Fabric" }).InterfaceIndex - $NetInterface = Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object { $_.InterfaceIndex -eq $index } - $NetInterface.SetGateways($Gateway) | Out-Null - - $fqdn = $SDNConfig.SDNDomainFQDN - - # Enable CredSSP - $VerbosePreference = "SilentlyContinue" - Enable-PSRemoting -force - Enable-WSManCredSSP -Role Server -Force - Enable-WSManCredSSP -Role Client -DelegateComputer localhost -Force - Enable-WSManCredSSP -Role Client -DelegateComputer $env:COMPUTERNAME -Force - Enable-WSManCredSSP -Role Client -DelegateComputer $fqdn -Force - Enable-WSManCredSSP -Role Client -DelegateComputer "*.$fqdn" -Force - 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 - - $VerbosePreference = "Continue" - - # Enable Large MTU - Write-Verbose "Configuring MTU on all Adapters" - Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Set-NetAdapterAdvancedProperty -RegistryValue $SDNConfig.SDNLABMTU -RegistryKeyword "*JumboPacket" - - $WACIP = $SDNConfig.WACIP.Split("/")[0] - - # Install RSAT-NetworkController - $isAvailable = Get-WindowsFeature | Where-Object { $_.Name -eq 'RSAT-NetworkController' } - - if ($isAvailable) { - Write-Verbose "Installing RSAT-NetworkController" - - $VerbosePreference = "SilentlyContinue" - Import-Module ServerManager - Install-WindowsFeature -Name RSAT-NetworkController -IncludeAllSubFeature -IncludeManagementTools | Out-Null - $VerbosePreference = "Continue" - } - - # Install Hyper-V RSAT - Write-Verbose "Installing Hyper-V RSAT Tools" - $VerbosePreference = "SilentlyContinue" - Install-WindowsFeature -Name RSAT-Hyper-V-Tools -IncludeAllSubFeature -IncludeManagementTools | Out-Null - $VerbosePreference = "Continue" - - # Install RSAT AD Tools - Write-Verbose "Installing Active Directory RSAT Tools" - $VerbosePreference = "SilentlyContinue" - Install-WindowsFeature -Name RSAT-ADDS -IncludeAllSubFeature -IncludeManagementTools | Out-Null - $VerbosePreference = "Continue" - - # Install Failover Cluster RSAT Tools - Write-Verbose "Installing Failover Clustering RSAT Tools" - $VerbosePreference = "SilentlyContinue" - Install-WindowsFeature -Name RSAT-Clustering-Mgmt, RSAT-Clustering-PowerShell -IncludeAllSubFeature -IncludeManagementTools | Out-Null - $VerbosePreference = "Continue" - - # Install DNS RSAT Tool - Write-Verbose "Installing DNS Server RSAT Tools" - $VerbosePreference = "SilentlyContinue" - Install-WindowsFeature -Name RSAT-DNS-Server -IncludeAllSubFeature -IncludeManagementTools | Out-Null - $VerbosePreference = "Continue" - - # Install VPN Routing - $VerbosePreference = "SilentlyContinue" - Install-RemoteAccess -VPNType RoutingOnly | Out-Null - $VerbosePreference = "Continue" - - # Install Nuget - $VerbosePreference = "SilentlyContinue" - Install-PackageProvider -Name Nuget -MinimumVersion 2.8.5.201 -Force - $VerbosePreference = "Continue" - - # Stop Server Manager from starting on boot - Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\ServerManager" -Name "DoNotOpenServerManagerAtLogon" -Value 1 - - # Request SSL Certificate for Windows Admin Center - Write-Verbose "Generating SSL Certificate Request" - - # Create BGP Router - $params = @{ - BGPIdentifier = $WACIP - LocalASN = $SDNConfig.WACASN - TransitRouting = 'Enabled' - ClusterId = 1 - RouteReflector = 'Enabled' - } - - Add-BgpRouter @params - - $RequestInf = @" -[Version] -Signature="`$Windows NT$" - -[NewRequest] -Subject = "CN=AdminCenter.$fqdn" -Exportable = True -KeyLength = 2048 -KeySpec = 1 -KeyUsage = 0xA0 -MachineKeySet = True -ProviderName = "Microsoft RSA SChannel Cryptographic Provider" -ProviderType = 12 -SMIME = FALSE -RequestType = CMC -FriendlyName = "Nested SDN 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" -szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2" -[Extensions] -%szOID_SUBJECT_ALT_NAME2% = "{text}dns=admincenter.$fqdn" -%szOID_ENHANCED_KEY_USAGE% = "{text}%szOID_PKIX_KP_SERVER_AUTH%,%szOID_PKIX_KP_CLIENT_AUTH%" -[RequestAttributes] -CertificateTemplate= WebServer -"@ - - New-Item C:\WACCert -ItemType Directory -Force | Out-Null - Set-Content -Value $RequestInf -Path C:\WACCert\WACCert.inf -Force | Out-Null - - $WACdomainCred = new-object -typename System.Management.Automation.PSCredential ` - -argumentlist (($SDNConfig.SDNDomainFQDN.Split(".")[0]) + "\administrator"), (ConvertTo-SecureString $SDNConfig.SDNAdminPassword -AsPlainText -Force) - $WACVMName = "admincenter" - $DCFQDN = $SDNConfig.DCName + '.' + $SDNConfig.SDNDomainFQDN - $WACport = $SDNConfig.WACport - $SDNConfig = $Using:SDNConfig - $fqdn = $SDNConfig.SDNDomainFQDN - - $params = @{ - Name = 'microsoft.SDNNested' - RunAsCredential = $Using:domainCred - MaximumReceivedDataSizePerCommandMB = 1000 - MaximumReceivedObjectSizeMB = 1000 - } - - $VerbosePreference = "SilentlyContinue" - Register-PSSessionConfiguration @params - $VerbosePreference = "Continue" - - Write-Verbose "Requesting and installing SSL Certificate" - Invoke-Command -ComputerName $WACVMName -ConfigurationName microsoft.SDNNested -ArgumentList $WACVMName, $SDNConfig, $DCFQDN -Credential $WACdomainCred -ScriptBlock { - - $DCFQDN = $args[2] - $VerbosePreference = "Continue" - $ErrorActionPreference = "Stop" - - # Get the CA Name - $CertDump = certutil -dump - $ca = ((((($CertDump.Replace('`', "")).Replace("'", "")).Replace(":", "=")).Replace('\', "")).Replace('"', "") ` - | ConvertFrom-StringData).Name - $CertAuth = $DCFQDN + '\' + $ca - - Write-Verbose "CA is: $ca" - Write-Verbose "Certificate Authority is: $CertAuth" - Write-Verbose "Certdump is $CertDump" - - # 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 -accept WACCert.cer - certutil -q -store my - - Set-Location 'C:\' - Remove-Item C:\WACCert -Recurse -Force - - } -Authentication Credssp - - $SDNConfig = Import-PowerShellDataFile -Path C:\SDN\HCIBox-Config.psd1 - - # Install Windows Admin Center - $pfxThumbPrint = (Get-ChildItem -Path Cert:\LocalMachine\my | Where-Object { $_.FriendlyName -match "Nested SDN Windows Admin Cert" }).Thumbprint - Write-Verbose "Thumbprint: $pfxThumbPrint" - Write-Verbose "WACPort: $WACPort" - $WindowsAdminCenterGateway = "https://admincenter." + $fqdn - Write-Verbose $WindowsAdminCenterGateway - Write-Verbose "Installing and Configuring Windows Admin Center" - $PathResolve = Resolve-Path -Path 'C:\Windows Admin Center\*.msi' - $arguments = "/qn /L*v C:\log.txt SME_PORT=$WACport SME_THUMBPRINT=$pfxThumbPrint SSL_CERTIFICATE_OPTION=installed SME_URL=$WindowsAdminCenterGateway" - Start-Process -FilePath $PathResolve -ArgumentList $arguments -PassThru | Wait-Process - - # Create a shortcut for Windows PowerShell ISE - Write-Verbose "Creating Shortcut for PowerShell ISE" - $TargetFile = "c:\windows\system32\WindowsPowerShell\v1.0\powershell_ise.exe" - $ShortcutFile = "C:\Users\Public\Desktop\PowerShell ISE.lnk" - $WScriptShell = New-Object -ComObject WScript.Shell - $Shortcut = $WScriptShell.CreateShortcut($ShortcutFile) - $Shortcut.TargetPath = $TargetFile - $Shortcut.Save() - - # Create a shortcut for Windows PowerShell Console - Write-Verbose "Creating Shortcut for PowerShell Console" - $TargetFile = "c:\windows\system32\WindowsPowerShell\v1.0\powershell.exe" - $ShortcutFile = "C:\Users\Public\Desktop\PowerShell.lnk" - $WScriptShell = New-Object -ComObject WScript.Shell - $Shortcut = $WScriptShell.CreateShortcut($ShortcutFile) - $Shortcut.TargetPath = $TargetFile - $Shortcut.Save() - - # Install Chocolatey - $ErrorActionPreference = "Continue" - Write-Verbose "Installing Chocolatey" - Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) - Start-Sleep -Seconds 10 - - # Install Azure PowerShell - Write-Verbose 'Installing Az PowerShell' - $expression = "choco install az.powershell -y" - Invoke-Expression $expression - $ErrorActionPreference = "Stop" - - # Create Shortcut for Hyper-V Manager - Write-Verbose "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" - - # Create Shortcut for Failover-Cluster Manager - Write-Verbose "Creating Shortcut for Failover-Cluster Manager" - Copy-Item -Path "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools\Failover Cluster Manager.lnk" ` - -Destination "C:\Users\Public\Desktop" - - # Create Shortcut for DNS - Write-Verbose "Creating Shortcut for DNS Manager" - Copy-Item -Path "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools\DNS.lnk" ` - -Destination "C:\Users\Public\Desktop" - - # Create Shortcut for Active Directory Users and Computers - Write-Verbose "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 the SDNExplorer Script and place on desktop - Write-Verbose "Configuring SDNExplorer" - $SENCIP = "nc01." + $SDNConfig.SDNDomainFQDN - $SDNEXPLORER = "Set-Location 'C:\VMConfigs\SDN';.\SDNExplorer.ps1 -NCIP $SENCIP" - Set-Content -Value $SDNEXPLORER -Path 'C:\users\Public\Desktop\SDN Explorer.ps1' -Force - - # Set Network Profiles - 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 - - # Install Kubectl - Write-Verbose 'Installing kubectl' - $expression = "choco install kubernetes-cli -y" - Invoke-Expression $expression - $ErrorActionPreference = "Stop" - - # Create a shortcut for Windows Admin Center - Write-Verbose "Creating Shortcut for Windows Admin Center" - if ($SDNConfig.WACport -ne "443") { $TargetPath = "https://admincenter." + $SDNConfig.SDNDomainFQDN + ":" + $SDNConfig.WACport } - else { $TargetPath = "https://admincenter." + $SDNConfig.SDNDomainFQDN } - $ShortcutFile = "C:\Users\Public\Desktop\Windows Admin Center.url" - $WScriptShell = New-Object -ComObject WScript.Shell - $Shortcut = $WScriptShell.CreateShortcut($ShortcutFile) - $Shortcut.TargetPath = $TargetPath - $Shortcut.Save() - - # Disable Edge 'First Run' Setup - $edgePolicyRegistryPath = 'HKLM:SOFTWARE\Policies\Microsoft\Edge' - $desktopSettingsRegistryPath = 'HKCU:SOFTWARE\Microsoft\Windows\Shell\Bags\1\Desktop' - $firstRunRegistryName = 'HideFirstRunExperience' - $firstRunRegistryValue = '0x00000001' - $savePasswordRegistryName = 'PasswordManagerEnabled' - $savePasswordRegistryValue = '0x00000000' - $autoArrangeRegistryName = 'FFlags' - $autoArrangeRegistryValue = '1075839525' - - if (-NOT (Test-Path -Path $edgePolicyRegistryPath)) { - New-Item -Path $edgePolicyRegistryPath -Force | Out-Null - } - if (-NOT (Test-Path -Path $desktopSettingsRegistryPath)) { - New-Item -Path $desktopSettingsRegistryPath -Force | Out-Null - } - - 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 - } - } -} - -function New-HyperConvergedEnvironment { - Param ( - $localCred, - $domainCred - ) - - Invoke-Command -ComputerName Admincenter -Credential $domainCred -ScriptBlock { - $SDNConfig = $Using:SDNConfig - - $ErrorActionPreference = "Stop" - $VerbosePreference = "Continue" - - $domainCred = new-object -typename System.Management.Automation.PSCredential ` - -argumentlist (($SDNConfig.SDNDomainFQDN.Split(".")[0]) + "\administrator"), ` - (ConvertTo-SecureString $SDNConfig.SDNAdminPassword -AsPlainText -Force) - - foreach ($AzSHOST in $SDNconfig.HostList) { - Write-Verbose "Invoking Command on $AzSHOST" - Invoke-Command -ComputerName $AzSHOST -ArgumentList $SDNConfig -Credential $using:domainCred -ScriptBlock { - function New-sdnSETSwitch { - param ( - $sdnswitchName, - $sdnswitchIP, - $sdnswitchIPpfx, - $sdnswitchVLAN, - $sdnswitchGW, - $sdnswitchDNS, - $sdnswitchteammembers - ) - - $VerbosePreference = "Continue" - Write-Verbose "Creating SET Hyper-V External Switch $sdnswitchName on host $env:COMPUTERNAME" - $params = @{ - - Name = $sdnswitchName - AllowManagementOS = $true - NetAdapterName = $sdnswitchteammembers - EnableEmbeddedTeaming = $true - MinimumBandwidthMode = "Weight" - } - New-VMSwitch @params | Out-Null - - # Set IP Config - Write-Verbose "Setting IP Configuration on $sdnswitchName" - $sdnswitchNIC = Get-Netadapter | Where-Object { $_.Name -match $sdnswitchName } - - $params = @{ - InterfaceIndex = $sdnswitchNIC.InterfaceIndex - IpAddress = $sdnswitchIP - PrefixLength = $sdnswitchIPpfx - AddressFamily = 'IPv4' - DefaultGateway = $sdnswitchGW - ErrorAction = 'SilentlyContinue' - } - - New-NetIPAddress @params | Out-Null - - # Set DNS - Set-DnsClientServerAddress -InterfaceIndex $sdnswitchNIC.InterfaceIndex -ServerAddresses ($sdnswitchDNS) - - # Set VLAN - Write-Verbose "Setting VLAN ($sdnswitchVLAN) on host vNIC" - $params = @{ - IsolationMode = 'Vlan' - DefaultIsolationID = $sdnswitchVLAN - AllowUntaggedTraffic = $true - VMNetworkAdapterName = $sdnswitchName - } - Set-VMNetworkAdapterIsolation -ManagementOS @params - - # Disable Switch Extensions - Get-VMSwitchExtension -VMSwitchName $sdnswitchName | Disable-VMSwitchExtension | Out-Null - - # Enable Large MTU - Write-Verbose "Configuring MTU on all Adapters" - Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Set-NetAdapterAdvancedProperty -RegistryValue $SDNConfig.SDNLABMTU -RegistryKeyword "*JumboPacket" - - } - - $ErrorActionPreference = "Stop" - - $SDNConfig = $args[0] - $sdnswitchteammembers = @("FABRIC", "FABRIC2") - $sdnswitchIP = $SDNConfig.($env:COMPUTERNAME + "IP").Split("/")[0] - $sdnswitchIPpfx = $SDNConfig.($env:COMPUTERNAME + "IP").Split("/")[1] - $sdnswitchGW = $SDNConfig.BGPRouterIP_MGMT.Split("/")[0] - - $sdnswitchCheck = Get-VMSwitch | Where-Object { $_.Name -eq "sdnSwitch" } - - if ($sdnswitchCheck) { Write-Warning "Switch already exists on $env:COMPUTERNAME. Skipping this host." } - else { - $params = @{ - sdnswitchName = 'sdnSwitch' - sdnswitchIP = $sdnswitchIP - sdnswitchIPpfx = $sdnswitchIPpfx - sdnswitchVLAN = $SDNConfig.mgmtVLAN - sdnswitchGW = $sdnswitchGW - sdnswitchDNS = $SDNConfig.SDNLABDNS - sdnswitchteammembers = $sdnswitchteammembers - } - New-sdnSETSwitch @params | out-null - } - } - - Start-Sleep -Seconds 60 - - } - } - # Wait until all the AzSHOSTs have been restarted - foreach ($AzSHOST in $SDNconfig.HostList) { - Write-Verbose "Rebooting HCIBox Host $AzSHOST" - Restart-Computer $AzSHOST -Force -Confirm:$false -Credential $localCred -Protocol WSMan - Write-Verbose "Checking to see if $AzSHOST is up and online" - while ((Invoke-Command -ComputerName $AzSHOST -Credential $localCred { "Test" } -ea SilentlyContinue) -ne "Test") { Start-Sleep -Seconds 60 } - } -} - -function New-SDNEnvironment { - Param ( - $domainCred, - $SDNConfig - ) - - Invoke-Command -ComputerName admincenter -Credential $domainCred -ScriptBlock { - - Register-PSSessionConfiguration -Name microsoft.SDNNestedSetup -RunAsCredential $domainCred -MaximumReceivedDataSizePerCommandMB 1000 -MaximumReceivedObjectSizeMB 1000 | Out-Null - - Invoke-Command -ComputerName localhost -Credential $Using:domainCred -ArgumentList $Using:domainCred, $Using:SDNConfig -ConfigurationName microsoft.SDNNestedSetup -ScriptBlock { - $NCConfig = @{ } - - $ErrorActionPreference = "Stop" - $VerbosePreference = "Continue" - - # Set Credential Object - $domainCred = $args[0] - $SDNConfig = $args[1] - - # Set fqdn - $fqdn = $SDNConfig.SDNDomainFQDN - - if ($SDNConfig.ProvisionNC) { - # Set NC Configuration Data - $NCConfig.RestName = ("NC01.") + $SDNConfig.SDNDomainFQDN - $NCConfig.PASubnet = $SDNConfig.ProviderSubnet - $NCConfig.JoinDomain = $SDNConfig.SDNDomainFQDN - $NCConfig.ManagementGateway = ($SDNConfig.BGPRouterIP_MGMT).Split("/")[0] - $NCConfig.PublicVIPSubnet = $SDNConfig.PublicVIPSubnet - $NCConfig.PrivateVIPSubnet = $SDNConfig.PrivateVIPSubnet - $NCConfig.GRESubnet = $SDNConfig.GRESubnet - $NCConfig.LocalAdminDomainUser = ($SDNConfig.SDNDomainFQDN.Split(".")[0]) + "\administrator" - $NCConfig.DomainJoinUsername = ($SDNConfig.SDNDomainFQDN.Split(".")[0]) + "\administrator" - $NCConfig.NCUsername = ($SDNConfig.SDNDomainFQDN.Split(".")[0]) + "\administrator" - $NCConfig.SDNMacPoolStart = "00-1D-D8-B7-1C-09" - $NCConfig.SDNMacPoolEnd = "00:1D:D8:B7:1F:FF" - $NCConfig.PAGateway = ($SDNConfig.BGPRouterIP_ProviderNetwork.TrimEnd("1/24")) + "1" - $NCConfig.PAPoolStart = ($SDNConfig.BGPRouterIP_ProviderNetwork.TrimEnd("1/24")) + "5" - $NCConfig.PAPoolEnd = ($SDNConfig.BGPRouterIP_ProviderNetwork.TrimEnd("1/24")) + "254" - $NCConfig.Capacity = "10000" - $NCConfig.ScriptVersion = "2.0" - $NCConfig.SDNASN = $SDNConfig.SDNASN - $NCConfig.ManagementVLANID = $SDNConfig.mgmtVLAN - $NCConfig.PAVLANID = $SDNConfig.providerVLAN - $NCConfig.PoolName = "DefaultAll" - $NCConfig.VMLocation = "D:\SDNVMS" - $NCConfig.VHDFile = "AzSHCI.vhdx" - $NCConfig.VHDPath = "C:\VHDS" - $NCConfig.ManagementSubnet = $SDNConfig.MGMTSubnet - $NCConfig.ProductKey = $SDNConfig.COREProductKey - $NCConfig.HyperVHosts = @("AzSHOST1.$fqdn", "AzSHOST2.$fqdn") - $NCConfig.ManagementDNS = @( - ($SDNConfig.BGPRouterIP_MGMT.Split("/")[0].TrimEnd("1")) + "254" - ) - $NCConfig.Muxes = @( - @{ - ComputerName = 'Mux01' - HostName = "AzSHOST2.$($SDNConfig.SDNDomainFQDN)" - ManagementIP = ($SDNConfig.BGPRouterIP_MGMT.TrimEnd("1/24")) + "61" - MACAddress = '00-1D-D8-B7-1C-01' - PAIPAddress = ($SDNConfig.BGPRouterIP_ProviderNetwork.TrimEnd("1/24")) + "4" - PAMACAddress = '00-1D-D8-B7-1C-02' - } - ) - - $NCConfig.Gateways = @( - @{ - ComputerName = "GW01" - ManagementIP = ($SDNConfig.BGPRouterIP_MGMT.TrimEnd("1/24")) + "62" - HostName = "AzSHOST2.$($SDNConfig.SDNDomainFQDN)" - FrontEndIP = ($SDNConfig.BGPRouterIP_ProviderNetwork.TrimEnd("1/24")) + "5" - MACAddress = "00-1D-D8-B7-1C-03" - FrontEndMac = "00-1D-D8-B7-1C-04" - BackEndMac = "00-1D-D8-B7-1C-05" - }, - @{ - ComputerName = "GW02" - ManagementIP = ($SDNConfig.BGPRouterIP_MGMT.TrimEnd("1/24")) + "63" - HostName = "AzSHOST1.$($SDNConfig.SDNDomainFQDN)" - FrontEndIP = ($SDNConfig.BGPRouterIP_ProviderNetwork.TrimEnd("1/24")) + "6" - MACAddress = "00-1D-D8-B7-1C-06" - FrontEndMac = "00-1D-D8-B7-1C-07" - BackEndMac = "00-1D-D8-B7-1C-08" - } - ) - - $NCConfig.NCs = @{ - MACAddress = "00:1D:D8:B7:1C:00" - ComputerName = "NC01" - HostName = "AzSHOST2.$($SDNConfig.SDNDomainFQDN)" - ManagementIP = ($SDNConfig.BGPRouterIP_MGMT.TrimEnd("1/24")) + "60" - } - - $NCConfig.Routers = @( - @{ - RouterASN = $SDNConfig.BGPRouterASN - RouterIPAddress = ($SDNConfig.BGPRouterIP_ProviderNetwork).Split("/")[0] - } - ) - - # Start SDNExpress (Nested Version) Install - Set-Location -Path 'C:\SDN' - $params = @{ - ConfigurationData = $NCConfig - DomainJoinCredential = $domainCred - LocalAdminCredential = $domainCred - NCCredential = $domainCred - } - - .\SDNExpress.ps1 @params - } - - } -Authentication Credssp - } -} - -function Remove-AzSHCISandbox { - param ( - $VMPlacement, - $SDNConfig, - $SingleHostDelete - ) - - $VerbosePreference = "Continue" - - Write-Verbose "Deleting Azure Stack HCI Sandbox" - foreach ($vm in $VMPlacement) { - $AzSHOSTName = $vm.vmHost - $VMName = $vm.AzSHOST - - Invoke-Command -ComputerName $AzSHOSTName -ArgumentList $VMName -ScriptBlock { - - $VerbosePreference = "SilentlyContinue" - Import-Module Hyper-V - - $VerbosePreference = "Continue" - $vmname = $args[0] - # Delete SBXAccess vNIC (if present) - $vNIC = Get-VMNetworkAdapter -ManagementOS | Where-Object { $_.Name -match "SBXAccess" } - if ($vNIC) { $vNIC | Remove-VMNetworkAdapter -Confirm:$false } - - $sdnvm = Get-VM | Where-Object { $_.Name -eq $vmname } - - If (!$sdnvm) { Write-Verbose "Could not find $vmname to delete" } - - if ($sdnvm) { - - Write-Verbose "Shutting down VM: $sdnvm)" - - Stop-VM -VM $sdnvm -TurnOff -Force -Confirm:$false - $VHDs = $sdnvm | Select-Object VMId | Get-VHD - Remove-VM -VM $sdnvm -Force -Confirm:$false - - foreach ($VHD in $VHDs) { - Write-Verbose "Removing $($VHD.Path)" - Remove-Item -Path $VHD.Path -Force -Confirm:$false - } - } - } - } - - If ($SingleHostDelete -eq $true) { - $RemoveSwitch = Get-VMSwitch | Where-Object { $_.Name -match $SDNConfig.InternalSwitch } - If ($RemoveSwitch) { - Write-Verbose "Removing Internal Switch: $($SDNConfig.InternalSwitch)" - $RemoveSwitch | Remove-VMSwitch -Force -Confirm:$false - } - } - - Write-Verbose "Deleting RDP links" - Remove-Item C:\Users\Public\Desktop\AdminCenter.lnk -Force -ErrorAction SilentlyContinue - - Write-Verbose "Deleting NetNAT" - Get-NetNAT | Remove-NetNat -Confirm:$false - - Write-Verbose "Deleting Internal Switches" - Get-VMSwitch | Where-Object { $_.SwitchType -eq "Internal" } | Remove-VMSwitch -Force -Confirm:$false -} - -function Add-WACtenants { - param ( - $SDNLabSystems, - $SDNConfig, - $domainCred - ) - - $VerbosePreference = "Continue" - Write-Verbose "Invoking Command to add Windows Admin Center Tenants" - - Invoke-Command -ComputerName Admincenter -Credential $domainCred -ScriptBlock { - $domainCred = $using:domainCred - $SDNLabSystems = $using:SDNLabSystems - $SDNConfig = $using:SDNConfig - $VerbosePreference = "Continue" - - Invoke-Command -ComputerName admincenter -Credential $domainCred -ScriptBlock { - # Set Variables - $SDNConfig = Import-PowerShellDataFile -Path C:\SDN\HCIBox-Config.psd1 - $fqdn = $SDNConfig.SDNDomainFQDN - $SDNLabSystems = @("bgp-tor-router", "$($SDNConfig.DCName).$fqdn", "NC01.$fqdn", "MUX01.$fqdn", "GW01.$fqdn", "GW02.$fqdn") - $VerbosePreference = "Continue" - $domainCred = new-object -typename System.Management.Automation.PSCredential ` - -argumentlist (($SDNConfig.SDNDomainFQDN.Split(".")[0]) + "\administrator"), ` - (ConvertTo-SecureString $SDNConfig.SDNAdminPassword -AsPlainText -Force) - - # Set Constrained Delegation for NC/MUX/GW Virtual Machines for Windows Admin Center - $SDNvms = ("NC01", "MUX01", "GW01", "GW02") - - $VerbosePreference = "Continue" - foreach ($SDNvm in $SDNvms) { - Write-Verbose "Setting Delegation for $SDNvm" - $gateway = "AdminCenter" - Write-Verbose "gateway = $gateway" - $node = $SDNvm - Write-Verbose "node = $node" - $gatewayObject = Get-ADComputer -Identity $gateway -Credential $domainCred - Write-Verbose "GatewayObject = $gatewayObject" - $nodeObject = Get-ADComputer -Identity $node -Credential $domainCred - Write-Verbose "nodeObject = $nodeObject" - Set-ADComputer -Identity $nodeObject -PrincipalsAllowedToDelegateToAccount $gatewayObject -Credential $domainCred - } - - foreach ($SDNLabSystem in $SDNLabSystems) { - $json = [pscustomobject]@{ - id = "msft.sme.connection-type.server!$SDNLabSystem" - name = $SDNLabSystem - type = "msft.sme.connection-type.server" - } | ConvertTo-Json - - $payload = @" -[ -$json -] -"@ - - if ($SDNConfig.WACport -eq "443" -or !$SDNConfig.WACport) { - $uri = "https://admincenter.$($SDNConfig.SDNDomainFQDN)/api/connections" - } - else { - $uri = "https://admincenter.$($SDNConfig.SDNDomainFQDN):$($SDNConfig.WACport)/api/connections" - } - - Write-Verbose "Adding Host: $SDNLabSystem" - $param = @{ - Uri = $uri - Method = 'Put' - Body = $payload - ContentType = $content - Credential = $domainCred - } - - Invoke-RestMethod @param -UseBasicParsing -DisableKeepAlive | Out-Null - } - } - } -} - -function New-SDNS2DCluster { - param ( - $SDNConfig, - $domainCred, - $AzStackClusterNode - ) - - $VerbosePreference = "Continue" - Invoke-Command -ComputerName $AzStackClusterNode -ArgumentList $SDNConfig, $domainCred -Credential $domainCred -ScriptBlock { - - $SDNConfig = $args[0] - $domainCred = $args[1] - $VerbosePreference = "SilentlyContinue" - $ErrorActionPreference = "Stop" - - Register-PSSessionConfiguration -Name microsoft.SDNNestedS2D -RunAsCredential $domainCred -MaximumReceivedDataSizePerCommandMB 1000 -MaximumReceivedObjectSizeMB 1000 | Out-Null - - Invoke-Command -ComputerName $Using:AzStackClusterNode -ArgumentList $SDNConfig, $domainCred -Credential $domainCred -ConfigurationName microsoft.SDNNestedS2D -ScriptBlock { - - $SDNConfig = $args[0] - - # Create S2D Cluster - $SDNConfig = $args[0] - $AzSHOSTs = @("AzSHOST1", "AzSHOST2") - - Write-Verbose "Creating Cluster: hciboxcluster" - $VerbosePreference = "SilentlyContinue" - Import-Module FailoverClusters - Import-Module Storage - $VerbosePreference = "Continue" - - - # Create Cluster - $ClusterIP = ($SDNConfig.MGMTSubnet.TrimEnd("0/24")) + "252" - $ClusterName = "hciboxcluster" - - $VerbosePreference = "SilentlyContinue" - New-Cluster -Name $ClusterName -Node $AzSHOSTs -StaticAddress $ClusterIP -NoStorage -WarningAction SilentlyContinue | Out-Null - $VerbosePreference = "Continue" - - # Invoke Command to enable S2D on hciboxcluster - Enable-ClusterS2D -Confirm:$false -Verbose - - # Wait for Cluster Performance History Volume to be Created - while (!$PerfHistory) { - - Write-Verbose "Waiting for Cluster Performance History volume to come online." - Start-Sleep -Seconds 10 - $PerfHistory = Get-ClusterResource | Where-Object {$_.Name -match 'ClusterPerformanceHistory'} - if ($PerfHistory) {Write-Verbose "Cluster Perfomance History volume online." } - - } - - Write-Verbose "Setting Physical Disk Media Type" - - Get-PhysicalDisk | Where-Object { $_.Size -lt 127GB } | Set-PhysicalDisk -MediaType HDD | Out-Null - - $params = @{ - - FriendlyName = "S2D_vDISK1" - FileSystem = 'CSVFS_ReFS' - StoragePoolFriendlyName = 'S2D on hciboxcluster' - ResiliencySettingName = 'Mirror' - PhysicalDiskRedundancy = 1 - AllocationUnitSize = 64KB - - } - - Write-Verbose "Creating Physical Disk" - Start-Sleep -Seconds 60 - New-Volume @params -UseMaximumSize | Out-Null - - # Set Virtual Environment Optimizations - Write-Verbose "Setting Virtual Environment Optimizations" - - $VerbosePreference = "SilentlyContinue" - Get-storagesubsystem clus* | set-storagehealthsetting -name “System.Storage.PhysicalDisk.AutoReplace.Enabled” -value “False” - Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\spaceport\Parameters -Name HwTimeout -Value 0x00007530 - $VerbosePreference = "Continue" - - # Rename Storage Network Adapters - - Write-Verbose "Renaming Storage Network Adapters" - - (Get-Cluster -Name hciboxcluster | Get-ClusterNetwork | Where-Object { $_.Address -eq ($sdnconfig.storageAsubnet.Replace('/24', '')) }).Name = 'StorageA' - (Get-Cluster -Name hciboxcluster | Get-ClusterNetwork | Where-Object { $_.Address -eq ($sdnconfig.storageBsubnet.Replace('/24', '')) }).Name = 'StorageB' - (Get-Cluster -Name hciboxcluster | Get-ClusterNetwork | Where-Object { $_.Address -eq ($sdnconfig.MGMTSubnet.Replace('/24', '')) }).Name = 'Public' - - - # Set Allowed Networks for Live Migration - - Write-Verbose "Setting allowed networks for Live Migration" - - Get-ClusterResourceType -Name "Virtual Machine" -Cluster hciboxcluster | Set-ClusterParameter -Cluster hciboxcluster -Name MigrationExcludeNetworks ` - -Value ([String]::Join(";", (Get-ClusterNetwork -Cluster hciboxcluster | Where-Object { $_.Name -notmatch "Storage" }).ID)) - - } | Out-Null - - } -} - -function Test-InternetConnect { - $testIP = $SDNConfig.natDNS - $ErrorActionPreference = "Stop" - $intConnect = Test-NetConnection -ComputerName $testip -Port 53 - - if (!$intConnect.TcpTestSucceeded) { - throw "Unable to connect to DNS by pinging $SDNConfig.natDNS - Network access to this IP is required." - } -} - -function Set-HostNAT { - param ( - $SDNConfig - ) - - $VerbosePreference = "Continue" - $switchExist = Get-NetAdapter | Where-Object { $_.Name -match $SDNConfig.natHostVMSwitchName } - if (!$switchExist) { - Write-Verbose "Creating Internal NAT Switch: $($SDNConfig.natHostVMSwitchName)" - # Create Internal VM Switch for NAT - New-VMSwitch -Name $SDNConfig.natHostVMSwitchName -SwitchType Internal | Out-Null - - Write-Verbose "Applying IP Address to NAT Switch: $($SDNConfig.natHostVMSwitchName)" - # Apply IP Address to new Internal VM Switch - $intIdx = (Get-NetAdapter | Where-Object { $_.Name -match $SDNConfig.natHostVMSwitchName }).ifIndex - $natIP = $SDNConfig.natHostSubnet.Replace("0/24", "1") - New-NetIPAddress -IPAddress $natIP -PrefixLength 24 -InterfaceIndex $intIdx | Out-Null - - # Create NetNAT - Write-Verbose "Creating new NETNAT" - New-NetNat -Name $SDNConfig.natHostVMSwitchName -InternalIPInterfaceAddressPrefix $SDNConfig.natHostSubnet | Out-Null - } -} - -function enable-singleSignOn { - param ( - $SDNConfig - ) - - $domainCred = new-object -typename System.Management.Automation.PSCredential -argumentlist (($SDNConfig.SDNDomainFQDN.Split(".")[0]) + "\administrator"), (ConvertTo-SecureString $SDNConfig.SDNAdminPassword -AsPlainText -Force) - - Invoke-Command -ComputerName ("$($SDNConfig.DCName).$($SDNConfig.SDNDomainFQDN)") -ScriptBlock { - Get-ADComputer -Filter * | Set-ADComputer -PrincipalsAllowedToDelegateToAccount (Get-ADComputer AdminCenter) - } -Credential $domainCred -} - -#endregion - -#region Main - -Start-Transcript -Path $Env:HCIBoxLogsDir\New-HCIBoxCluster.log - -$starttime = Get-Date - -# Import Configuration Module -$SDNConfig = Import-PowerShellDataFile -Path $ConfigurationDataFile -Copy-Item $ConfigurationDataFile -Destination $Env:HCIBoxSDNDir -Force -$NestedVMMemoryinGB = $SDNConfig.NestedVMMemoryinGB -$guiVHDXPath = $SDNConfig.guiVHDXPath -$azSHCIVHDXPath = $SDNConfig.azSHCIVHDXPath -$HostVMPath = $SDNConfig.HostVMPath -$InternalSwitch = $SDNConfig.InternalSwitch -$natDNS = $SDNConfig.natDNS -$natSubnet = $SDNConfig.natSubnet -$natConfigure = $SDNConfig.natConfigure - -Import-Module Hyper-V - -$VerbosePreference = "Continue" -$ErrorActionPreference = "Stop" -$WarningPreference = "SilentlyContinue" -$ProgressPreference = 'SilentlyContinue' - -# Download HCIBox VHDs -Write-Verbose "Downloading HCIBox VHDs. This will take a while..." -BITSRequest -Params @{'Uri'='https://aka.ms/AAijhe3'; 'Filename'="$env:HCIBoxVHDDir\AZSHCI.vhdx" } -BITSRequest -Params @{'Uri'='https://aka.ms/AAij9n9'; 'Filename'="$env:HCIBoxVHDDir\GUI.vhdx"} -BITSRequest -Params @{'Uri'='https://partner-images.canonical.com/hyper-v/desktop/focal/current/ubuntu-focal-hyperv-amd64-ubuntu-desktop-hyperv.vhdx.zip'; 'Filename'="$env:HCIBoxVHDDir\Ubuntu.vhdx.zip"} -Expand-Archive -Path $env:HCIBoxVHDDir\Ubuntu.vhdx.zip -DestinationPath $env:HCIBoxVHDDir -Move-Item -Path $env:HCIBoxVHDDir\livecd.ubuntu-desktop-hyperv.vhdx -Destination $env:HCIBoxVHDDir\Ubuntu.vhdx - -# Set VM Host Memory -# $availablePhysicalMemory = (([math]::Round(((((Get-Counter -Counter '\Hyper-V Dynamic Memory Balancer(System Balancer)\Available Memory For Balancing' -ComputerName $env:COMPUTERNAME).CounterSamples.CookedValue) / 1024) - 18) / 2))) * 1073741824 -# $SDNConfig.NestedVMMemoryinGB = $availablePhysicalMemory - -# Set-Credentials -$localCred = new-object -typename System.Management.Automation.PSCredential -argumentlist "Administrator", (ConvertTo-SecureString $SDNConfig.SDNAdminPassword -AsPlainText -Force) - -$domainCred = new-object -typename System.Management.Automation.PSCredential ` - -argumentlist (($SDNConfig.SDNDomainFQDN.Split(".")[0]) + "\Administrator"), ` - (ConvertTo-SecureString $SDNConfig.SDNAdminPassword -AsPlainText -Force) - -$NCAdminCred = new-object -typename System.Management.Automation.PSCredential ` - -argumentlist (($SDNConfig.SDNDomainFQDN.Split(".")[0]) + "\NCAdmin"), ` - (ConvertTo-SecureString $SDNConfig.SDNAdminPassword -AsPlainText -Force) - -# Define HCIBox host Names. Please do not change names as these names are hardcoded in the setup. -$AzSHOSTs = @("AzSMGMT", "AzSHOST1", "AzSHOST2") - -# Delete configuration if specified -if ($Delete) { - Write-Verbose "Deleting cluster..." - $VMPlacement = Select-SingleHost -AzSHOSTs $AzSHOSTs - $SingleHostDelete = $true - Remove-AzSHCISandbox -SDNConfig $SDNConfig -VMPlacement $VMPlacement -SingleHostDelete $SingleHostDelete - - Write-Verbose "Successfully Removed the Azure Stack HCI Sandbox" - exit -} - -# Enable PSRemoting -Write-Verbose "Enabling PS Remoting on client..." -$VerbosePreference = "SilentlyContinue" -Enable-PSRemoting -Set-Item WSMan:\localhost\Client\TrustedHosts * -Confirm:$false -Force -$VerbosePreference = "Continue" - -# Verify Applications -Resolve-Applications -SDNConfig $SDNConfig - -# Verify Internet Connectivity -Test-InternetConnect - -# Set up installation parameters -Write-Verbose "Testing VHDX Path" -$params = @{ - guiVHDXPath = $guiVHDXPath - azSHCIVHDXPath = $azSHCIVHDXPath -} -Test-VHDPath @params - -Write-Verbose "Generating Single Host Placement" -$VMPlacement = Select-SingleHost -AzSHOSTs $AzSHOSTs - -Write-Verbose "Creating Internal Switch" -$params = @{ - pswitchname = $InternalSwitch - SDNConfig = $SDNConfig -} -New-InternalSwitch @params - -Write-Verbose "Creating NAT Switch" -Set-HostNAT -SDNConfig $SDNConfig - -$VMSwitch = $InternalSwitch - -Write-Verbose "Setting local Parent VHDX Path" -$params = @{ - guiVHDXPath = $guiVHDXPath - HostVMPath = $HostVMPath -} -$ParentVHDXPath = Get-GuiVHDXPath @params -Set-LocalHyperVSettings -HostVMPath $HostVMPath - -# Copy the parent VHDX file to the specified Parent VHDX Path -Write-Verbose "Copying VHDX Files to Host" -$params = @{ - azSHCIVHDXPath = $azSHCIVHDXPath - HostVMPath = $HostVMPath - guiVHDXPath = $guiVHDXPath -} -Copy-VHDXtoHost @params - -# Create Virtual Machines -$vmMacs = @() -foreach ($VM in $VMPlacement) { - Write-Verbose "Generating the VM: $VM" - $params = @{ - VMHost = $VM.VMHost - AzSHOST = $VM.AzSHOST - HostVMPath = $HostVMPath - VMSwitch = $VMSwitch - SDNConfig = $SDNConfig - } - $vmMac = New-NestedVM @params - - Write-Verbose "Returned VMMac is $vmMac" - $vmMacs += [pscustomobject]@{ - Hostname = $VM.AzSHOST - vmMAC = $vmMac - } -} - -# Inject Answer Files and Binaries into Virtual Machines -$params = @{ - VMPlacement = $VMPlacement - HostVMPath = $HostVMPath - SDNConfig = $SDNConfig - guiVHDXPath = $guiVHDXPath - azSHCIVHDXPath = $azSHCIVHDXPath - vmMacs = $vmMacs -} -Add-Files @params - -# Start Virtual Machines -Start-AzSHOSTS -VMPlacement $VMPlacement - -# Wait for AzSHOSTs to come online -Write-Verbose "Waiting for VMs to provision and then come online" -$params = @{ - VMPlacement = $VMPlacement - localcred = $localCred -} -Test-AzSHOSTVMConnection @params - -# Online and Format Data Volumes on Virtual Machines -$params = @{ - VMPlacement = $VMPlacement - SDNConfig = $SDNConfig -} -New-DataDrive @params - -# Install HCIBox Host Software on NestedVMs -$params = @{ - SDNConfig = $SDNConfig - VMPlacement = $VMPlacement - localcred = $localCred -} -Set-SDNserver @params - -# Rename NICs from Ethernet to FABRIC -$params = @{ - scriptpath = 'Get-Netadapter ((Get-NetAdapterAdvancedProperty | Where-Object {$_.DisplayValue -eq "SDN"}).Name) | Rename-NetAdapter -NewName FABRIC' - VMPlacement = $VMPlacement - localcred = $localCred -} - -Start-PowerShellScriptsOnHosts @params - -$params.scriptpath = 'Get-Netadapter ((Get-NetAdapterAdvancedProperty | Where-Object {$_.DisplayValue -eq "SDN2"}).Name) | Rename-NetAdapter -NewName FABRIC2' - -Start-PowerShellScriptsOnHosts @params - -# Restart Machines - -$params.scriptpath = "Restart-Computer -Force" -Start-PowerShellScriptsOnHosts @params -Start-Sleep -Seconds 30 - -# Wait for AzSHOSTs to come online - -Write-Verbose "Waiting for VMs to restart..." - -$params = @{ - - VMPlacement = $VMPlacement - localcred = $localCred - -} - -Test-AzSHOSTVMConnection @params - -# This step has to be done as during the Hyper-V install as hosts reboot twice. -Start-Sleep -Seconds 15 -Write-Verbose "Ensuring that all VMs have been restarted after Hyper-V install.." -Test-AzSHOSTVMConnection @params - -# Create NAT Virtual Switch on AzSMGMT - -if ($natConfigure) { - $SwitchName = $SDNConfig.InternalSwitch - - Write-Verbose "Creating NAT Switch on switch $SwitchName" - $VerbosePreference = "SilentlyContinue" - $params = @{ - SwitchName = $SwitchName - VMPlacement = $VMPlacement - SDNConfig = $SDNConfig - } - - New-NATSwitch @params - $VerbosePreference = "Continue" -} - -# Provision AzSMGMT VMs (DC, Router, and AdminCenter) -Write-Verbose "Configuring Management VM" -$params = @{ - SDNConfig = $SDNConfig - localCred = $localCred - domainCred = $domainCred -} -Set-AzSMGMT @params - -# Provision Hyper-V Logical Switches and Create S2D Cluster on Hosts -$params = @{ - localCred = $localCred - domainCred = $domainCred -} -New-HyperConvergedEnvironment @params -Start-Sleep -Seconds 30 - -# Create S2D Cluster -$params = @{ - SDNConfig = $SDNConfig - DomainCred = $domainCred - AzStackClusterNode = 'AzSHOST2' -} -New-SDNS2DCluster @params - -# Install and Configure Network Controller if specified -If ($SDNConfig.ProvisionNC) { - $params = @{ - SDNConfig = $SDNConfig - domainCred = $domainCred - } - New-SDNEnvironment @params - - # Add Systems to Windows Admin Center - $fqdn = $SDNConfig.SDNDomainFQDN - - $SDNLabSystems = @("bgp-tor-router", "$($SDNConfig.DCName).$fqdn", "NC01.$fqdn", "MUX01.$fqdn", "GW01.$fqdn", "GW02.$fqdn") - - # Add VMs for Domain Admin - - $params = @{ - - SDNLabSystems = $SDNLabSystems - SDNConfig = $SDNConfig - domainCred = $domainCred - - } - - # Add-WACtenants @params - - - # Add VMs for NC Admin - - $params.domainCred = $NCAdminCred - - # Add-WACtenants @params - - # Enable Single Sign On - - Write-Verbose "Enabling Single Sign On in WAC" - enable-singleSignOn -SDNConfig $SDNConfig - -} - - -# Finally - Add RDP Link to Desktop - -Remove-Item C:\Users\Public\Desktop\AdminCenter.lnk -Force -ErrorAction SilentlyContinue -$wshshell = New-Object -ComObject WScript.Shell -$lnk = $wshshell.CreateShortcut("C:\Users\Public\Desktop\AdminCenter.lnk") -$lnk.TargetPath = "%windir%\system32\mstsc.exe" -$lnk.Arguments = "/v:AdminCenter" -$lnk.Description = "AdminCenter link for HCIBox." -$lnk.Save() - -$endtime = Get-Date - -$timeSpan = New-TimeSpan -Start $starttime -End $endtime - -Write-Verbose "`nSuccessfully deployed HCIBox cluster." -Write-Verbose "Now working on enabling HCIBox features." -Write-Host "Infrastructure deployment time was $($timeSpan.Hours) hour and $($timeSpan.Minutes) minutes." -ForegroundColor Green - -$ErrorActionPreference = "Continue" -$VerbosePreference = "SilentlyContinue" -$WarningPreference = "Continue" - -Stop-Transcript - -#endregion \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/Bootstrap.ps1 b/azure_jumpstart_hcibox/artifacts/PowerShell/Bootstrap.ps1 similarity index 57% rename from azure_jumpstart_hcibox/artifacts/Bootstrap.ps1 rename to azure_jumpstart_hcibox/artifacts/PowerShell/Bootstrap.ps1 index 8ee1993a5a..0051d3e774 100644 --- a/azure_jumpstart_hcibox/artifacts/Bootstrap.ps1 +++ b/azure_jumpstart_hcibox/artifacts/PowerShell/Bootstrap.ps1 @@ -3,6 +3,7 @@ param ( [string]$adminPassword, [string]$spnClientId, [string]$spnClientSecret, + [string]$spnProviderId, [string]$spnTenantId, [string]$subscriptionId, [string]$resourceGroup, @@ -14,13 +15,16 @@ param ( [string]$deployAKSHCI, [string]$deployResourceBridge, [string]$natDNS, - [string]$rdpPort + [string]$rdpPort, + [string]$autoDeployClusterResource, + [string]$autoUpgradeClusterResource ) [System.Environment]::SetEnvironmentVariable('adminUsername', $adminUsername,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('spnClientID', $spnClientId,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('spnClientSecret', $spnClientSecret,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('spnTenantId', $spnTenantId,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable('spnProviderId', $spnProviderId,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('SPN_CLIENT_ID', $spnClientId,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('SPN_CLIENT_SECRET', $spnClientSecret,[System.EnvironmentVariableTarget]::Machine) [System.Environment]::SetEnvironmentVariable('SPN_TENANT_ID', $spnTenantId,[System.EnvironmentVariableTarget]::Machine) @@ -32,56 +36,49 @@ 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) -# Creating HCIBox path -Write-Output "Creating HCIBox paths" -$Env:HCIBoxDir = "C:\HCIBox" -$Env:HCIBoxLogsDir = "C:\HCIBox\Logs" -$Env:HCIBoxVMDir = "C:\HCIBox\Virtual Machines" -$Env:HCIBoxIconDir = "C:\HCIBox\Icons" -$Env:HCIBoxVHDDir = "C:\HCIBox\VHD" -$Env:HCIBoxSDNDir = "C:\HCIBox\SDN" -$Env:HCIBoxKVDir = "C:\HCIBox\KeyVault" -$Env:HCIBoxWACDir = "C:\HCIBox\Windows Admin Center" -$Env:agentScript = "C:\HCIBox\agentScript" -$Env:ToolsDir = "C:\Tools" -$Env:tempDir = "C:\Temp" -$Env:VMPath = "C:\VMs" - -New-Item -Path $Env:HCIBoxDir -ItemType directory -Force -New-Item -Path $Env:HCIBoxVHDDir -ItemType directory -Force -New-Item -Path $Env:HCIBoxSDNDir -ItemType directory -Force -New-Item -Path $Env:HCIBoxLogsDir -ItemType directory -Force -New-Item -Path $Env:HCIBoxVMDir -ItemType directory -Force -New-Item -Path $Env:HCIBoxIconDir -ItemType directory -Force -New-Item -Path $Env:HCIBoxWACDir -ItemType directory -Force -New-Item -Path $Env:HCIBoxKVDir -ItemType directory -Force -New-Item -Path $Env:ToolsDir -ItemType Directory -Force -New-Item -Path $Env:tempDir -ItemType directory -Force -New-Item -Path $Env:agentScript -ItemType directory -Force - -Start-Transcript -Path $Env:HCIBoxLogsDir\Bootstrap.log - -$ErrorActionPreference = 'SilentlyContinue' - +####################################################################### +## Setup basic environment +####################################################################### # Copy PowerShell Profile and Reload -Invoke-WebRequest ($templateBaseUrl + "artifacts/PSProfile.ps1") -OutFile $PsHome\Profile.ps1 +Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/PSProfile.ps1") -OutFile $PsHome\Profile.ps1 .$PsHome\Profile.ps1 +# Creating HCIBox path +$HCIPath = "C:\HCIBox" +[System.Environment]::SetEnvironmentVariable('HCIBoxDir', $HCIPath,[System.EnvironmentVariableTarget]::Machine) +New-Item -Path $HCIPath -ItemType directory -Force + +# Downloading configuration file +$ConfigurationDataFile = "$HCIPath\HCIBox-Config.psd1" +[System.Environment]::SetEnvironmentVariable('HCIBoxConfigFile', $ConfigurationDataFile,[System.EnvironmentVariableTarget]::Machine) +Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/HCIBox-Config.psd1") -OutFile $ConfigurationDataFile + +# Importing configuration data +$HCIBoxConfig = Import-PowerShellDataFile -Path $ConfigurationDataFile + +# Create paths +foreach ($path in $HCIBoxConfig.Paths.GetEnumerator()) { + Write-Output "Creating path $($path.Value)" + New-Item -Path $path.Value -ItemType directory -Force | Out-Null +} + +# Begin transcript +Start-Transcript -Path "$($HCIBoxConfig.Paths["LogsDir"])\Bootstrap.log" + +################################################################################# +## Setup host infrastructure and apps +################################################################################# # Extending C:\ partition to the maximum size Write-Host "Extending C:\ partition to the maximum size" Resize-Partition -DriveLetter C -Size $(Get-PartitionSupportedSize -DriveLetter C).SizeMax -# Installing Posh-SSH PowerShell Module -Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Install-Module -Name Posh-SSH -Force - # Installing tools Write-Header "Installing Chocolatey Apps" -$chocolateyAppList = 'az.powershell,kubernetes-cli,vcredist140,microsoft-edge,azcopy10,vscode,git,7zip,kubectx,terraform,putty.install,kubernetes-helm,dotnet-sdk,setdefaultbrowser,zoomit,azure-data-studio' - try { choco config get cacheLocation } @@ -90,56 +87,54 @@ catch { Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) } -Write-Host "Chocolatey Apps Specified" - -$appsToInstall = $chocolateyAppList -split "," | ForEach-Object { "$($_.Trim())" } - -foreach ($app in $appsToInstall) +foreach ($app in $HCIBoxConfig.ChocolateyPackagesList) { Write-Host "Installing $app" & 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 Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet' Remove-Item .\AzureCLI.msi -Write-Header "Downloading Azure Stack HCI configuration scripts" -Invoke-WebRequest "https://raw.githubusercontent.com/Azure/arc_jumpstart_docs/main/img/wallpaper/hcibox_wallpaper_dark.png" -OutFile $Env:HCIBoxDir\wallpaper.png -Invoke-WebRequest https://aka.ms/wacdownload -OutFile $Env:HCIBoxWACDir\WindowsAdminCenter.msi -Invoke-WebRequest ($templateBaseUrl + "artifacts/HCIBoxLogonScript.ps1") -OutFile $Env:HCIBoxDir\HCIBoxLogonScript.ps1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/New-HCIBoxCluster.ps1") -OutFile $Env:HCIBoxDir\New-HCIBoxCluster.ps1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/Register-AzSHCI.ps1") -OutFile $Env:HCIBoxDir\Register-AzSHCI.ps1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/HCIBox-Config.psd1") -OutFile $Env:HCIBoxDir\HCIBox-Config.psd1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/Deploy-AKS.ps1") -OutFile $Env:HCIBoxDir\Deploy-AKS.ps1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/Deploy-SQLMI.ps1") -OutFile $Env:HCIBoxDir\Deploy-SQLMI.ps1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/Uninstall-AKS.ps1") -OutFile $Env:HCIBoxDir\Uninstall-AKS.ps1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/Deploy-ArcResourceBridge.ps1") -OutFile $Env:HCIBoxDir\Deploy-ArcResourceBridge.ps1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/Uninstall-ResourceBridge.ps1") -OutFile $Env:HCIBoxDir\Uninstall-ResourceBridge.ps1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/Deploy-GitOps.ps1") -OutFile $Env:HCIBoxDir\Deploy-GitOps.ps1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/SDN/CertHelpers.ps1") -OutFile $Env:HCIBoxSDNDir\CertHelpers.ps1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/SDN/NetworkControllerRESTWrappers.ps1") -OutFile $Env:HCIBoxSDNDir\NetworkControllerRESTWrappers.ps1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/SDN/NetworkControllerWorkloadHelpers.psm1") -OutFile $Env:HCIBoxSDNDir\NetworkControllerWorkloadHelpers.psm1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/SDN/SDNExplorer.ps1") -OutFile $Env:HCIBoxSDNDir\SDNExplorer.ps1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/SDN/SDNExpress.ps1") -OutFile $Env:HCIBoxSDNDir\SDNExpress.ps1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/SDN/SDNExpressModule.psm1") -OutFile $Env:HCIBoxSDNDir\SDNExpressModule.psm1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/SDN/SDNExpressUI.psm1") -OutFile $Env:HCIBoxSDNDir\SDNExpressUI.psm1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/SDN/Single-NC.psd1") -OutFile $Env:HCIBoxSDNDir\Single-NC.psd1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/LogInstructions.txt") -OutFile $Env:HCIBoxLogsDir\LogInstructions.txt -Invoke-WebRequest ($templateBaseUrl + "artifacts/GetServiceAccountBearerToken.ps1") -OutFile $Env:HCIBoxDir\GetServiceAccountBearerToken.ps1 -Invoke-WebRequest ($templateBaseUrl + "artifacts/jumpstart-user-secret.yaml") -OutFile $Env:HCIBoxDir\jumpstart-user-secret.yaml +Write-Host "Downloading Azure Stack HCI configuration scripts" +Invoke-WebRequest "https://raw.githubusercontent.com/Azure/arc_jumpstart_docs/main/img/wallpaper/hcibox_wallpaper_dark.png" -OutFile $HCIPath\wallpaper.png +Invoke-WebRequest https://aka.ms/wacdownload -OutFile "$($HCIBoxConfig.Paths["WACDir"])\WindowsAdminCenter.msi" +Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/HCIBoxLogonScript.ps1") -OutFile $HCIPath\HCIBoxLogonScript.ps1 +Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/New-HCIBoxCluster.ps1") -OutFile $HCIPath\New-HCIBoxCluster.ps1 +Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/Configure-AKSWorkloadCluster.ps1") -OutFile $HCIPath\Configure-AKSWorkloadCluster.ps1 +Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/Configure-VMLogicalNetwork.ps1") -OutFile $HCIPath\Configure-VMLogicalNetwork.ps1 +Invoke-WebRequest ($templateBaseUrl + "artifacts/PowerShell/Generate-ARM-Template.ps1") -OutFile $HCIPath\Generate-ARM-Template.ps1 +Invoke-WebRequest ($templateBaseUrl + "artifacts/LogInstructions.txt") -OutFile $HCIBoxConfig.Paths["LogsDir"]\LogInstructions.txt +Invoke-WebRequest ($templateBaseUrl + "artifacts/jumpstart-user-secret.yaml") -OutFile $HCIPath\jumpstart-user-secret.yaml +Invoke-WebRequest ($templateBaseUrl + "artifacts/hci.json") -OutFile $HCIPath\hci.json +Invoke-WebRequest ($templateBaseUrl + "artifacts/hci.parameters.json") -OutFile $HCIPath\hci.parameters.json # Replace password and DNS placeholder +Write-Host "Updating config placeholders with injected values." $adminPassword = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($adminPassword)) -(Get-Content -Path $Env:HCIBoxDir\HCIBox-Config.psd1) -replace '%staging-password%',$adminPassword | Set-Content -Path $Env:HCIBoxDir\HCIBox-Config.psd1 -(Get-Content -Path $Env:HCIBoxDir\HCIBox-Config.psd1) -replace '%staging-natDNS%',$natDNS | Set-Content -Path $Env:HCIBoxDir\HCIBox-Config.psd1 +(Get-Content -Path $HCIPath\HCIBox-Config.psd1) -replace '%staging-password%',$adminPassword | Set-Content -Path $HCIPath\HCIBox-Config.psd1 +(Get-Content -Path $HCIPath\HCIBox-Config.psd1) -replace '%staging-natDNS%',$natDNS | Set-Content -Path $HCIPath\HCIBox-Config.psd1 # Disabling Windows Server Manager Scheduled Task +Write-Host "Disabling Windows Server Manager scheduled task." Get-ScheduledTask -TaskName ServerManager | Disable-ScheduledTask # Disable Server Manager WAC prompt +Write-Host "Disabling Server Manager WAC prompt." $RegistryPath = "HKLM:\SOFTWARE\Microsoft\ServerManager" $Name = "DoNotPopWACConsoleAtSMLaunch" $Value = "1" @@ -149,29 +144,30 @@ if (-not (Test-Path $RegistryPath)) { New-ItemProperty -Path $RegistryPath -Name $Name -Value $Value -PropertyType DWORD -Force # Disable Network Profile prompt +Write-Host "Disabling network profile prompt." $RegistryPath = "HKLM:\System\CurrentControlSet\Control\Network\NewNetworkWindowOff" if (-not (Test-Path $RegistryPath)) { New-Item -Path $RegistryPath -Force | Out-Null } # Configuring CredSSP and WinRM +Write-Host "Enabling CredSSP." Enable-WSManCredSSP -Role Server -Force | Out-Null Enable-WSManCredSSP -Role Client -DelegateComputer $Env:COMPUTERNAME -Force | Out-Null # Creating scheduled task for HCIBoxLogonScript.ps1 +Write-Host "Creating scheduled task for HCIBoxLogonScript.ps1" $Trigger = New-ScheduledTaskTrigger -AtLogOn -$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument $Env:HCIBoxDir\HCIBoxLogonScript.ps1 +$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument $HCIPath\HCIBoxLogonScript.ps1 Register-ScheduledTask -TaskName "HCIBoxLogonScript" -Trigger $Trigger -User $adminUsername -Action $Action -RunLevel "Highest" -Force # Disable Edge 'First Run' Setup +Write-Host "Configuring Microsoft Edge." $edgePolicyRegistryPath = 'HKLM:SOFTWARE\Policies\Microsoft\Edge' -$desktopSettingsRegistryPath = 'HKCU:SOFTWARE\Microsoft\Windows\Shell\Bags\1\Desktop' $firstRunRegistryName = 'HideFirstRunExperience' $firstRunRegistryValue = '0x00000001' $savePasswordRegistryName = 'PasswordManagerEnabled' $savePasswordRegistryValue = '0x00000000' -$autoArrangeRegistryName = 'FFlags' -$autoArrangeRegistryValue = '1075839525' if (-NOT (Test-Path -Path $edgePolicyRegistryPath)) { New-Item -Path $edgePolicyRegistryPath -Force | Out-Null @@ -179,10 +175,9 @@ if (-NOT (Test-Path -Path $edgePolicyRegistryPath)) { 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 # Change RDP Port -Write-Host "RDP port number from configuration is $rdpPort" +Write-Host "Updating RDP Port - RDP port number from configuration is $rdpPort" if (($rdpPort -ne $null) -and ($rdpPort -ne "") -and ($rdpPort -ne "3389")) { Write-Host "Configuring RDP port number to $rdpPort" @@ -204,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 @@ -215,13 +210,13 @@ if (($rdpPort -ne $null) -and ($rdpPort -ne "") -and ($rdpPort -ne "3389")) } # Install Hyper-V and reboot -Write-Header "Installing Hyper-V" +Write-Header "Installing Hyper-V." Enable-WindowsOptionalFeature -Online -FeatureName Containers -All -NoRestart Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -NoRestart Install-WindowsFeature -Name Hyper-V -IncludeAllSubFeature -IncludeManagementTools -Restart # Clean up Bootstrap.log -Write-Header "Clean up Bootstrap.log" +Write-Header "Clean up Bootstrap.log." Stop-Transcript -$logSuppress = Get-Content $Env:HCIBoxLogsDir\Bootstrap.log | Where-Object { $_ -notmatch "Host Application: powershell.exe" } -$logSuppress | Set-Content $Env:HCIBoxLogsDir\Bootstrap.log -Force +$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/Configure-AKSWorkloadCluster.ps1 b/azure_jumpstart_hcibox/artifacts/PowerShell/Configure-AKSWorkloadCluster.ps1 new file mode 100644 index 0000000000..f5b0f87eb5 --- /dev/null +++ b/azure_jumpstart_hcibox/artifacts/PowerShell/Configure-AKSWorkloadCluster.ps1 @@ -0,0 +1,56 @@ +$WarningPreference = "SilentlyContinue" +$ErrorActionPreference = "Stop" +$ProgressPreference = 'SilentlyContinue' + +# Set groupObjectID to the object ID of the Microsoft Entra ID group that will be granted access to the AKS workload cluster. +#$groupObjectID="aaaaaaa-bbbb-cccc-db62-fffssfff" # Uncomment this line and change the value to your Microsoft Entra ID group id + +# Set paths +$Env:HCIBoxDir = "C:\HCIBox" + +# Import Configuration Module +$HCIBoxConfig = Import-PowerShellDataFile -Path $Env:HCIBoxConfigFile +Start-Transcript -Path "$($HCIBoxConfig.Paths.LogsDir)\Configure-AKSWorkloadCluster.log" + +$domainCred = new-object -typename System.Management.Automation.PSCredential ` + -argumentlist (($HCIBoxConfig.SDNDomainFQDN.Split(".")[0]) +"\Administrator"), (ConvertTo-SecureString $HCIBoxConfig.SDNAdminPassword -AsPlainText -Force) + +# Generate credential objects +Write-Host 'Creating credentials and connecting to Azure' +$subId = $env:subscriptionId +$rg = $env:resourceGroup +$spnClientId = $env:spnClientId +$spnSecret = $env:spnClientSecret +$spnTenantId = $env:spnTenantId +$location = "eastus" +$lnetName = "hcibox-aks-lnet-vlan110" +$customLocName = $HCIBoxConfig.rbCustomLocationName +$WarningPreference = "SilentlyContinue" +$ErrorActionPreference = "SilentlyContinue" +Invoke-Command -ComputerName "$($HCIBoxConfig.NodeHostConfig[0].Hostname).$($HCIBoxConfig.SDNDomainFQDN)" -Credential $domainCred -Authentication CredSSP -ArgumentList $HCIBoxConfig -ScriptBlock { + $HCIBoxConfig = $args[0] + az login --service-principal --username $using:spnClientID --password=$using:spnSecret --tenant $using:spnTenantId + az config set extension.use_dynamic_install=yes_without_prompt | Out-Null + az extension add --name customlocation + az extension add --name stack-hci-vm + $customLocationID=(az customlocation show --resource-group $using:rg --name $using:customLocName --query id -o tsv) + $switchName='"ConvergedSwitch(hci)"' + + $addressPrefixes = $HCIBoxConfig.AKSIPPrefix + $gateway = $HCIBoxConfig.AKSGWIP + $dnsServers = $HCIBoxConfig.AKSDNSIP + $vlanid = $HCIBoxConfig.AKSVLAN + + az stack-hci-vm network lnet create --subscription $using:subId --resource-group $using:rg --custom-location $customLocationID --location $using:location --name $using:lnetName --vm-switch-name $switchName --ip-allocation-method "static" --ip-pool-start $HCIBoxConfig.AKSNodeStartIP --ip-pool-end $HCIBoxConfig.AKSNodeEndIP --address-prefixes $addressPrefixes --gateway $gateway --dns-servers $dnsServers --vlan $vlanid +} +$WarningPreference = "SilentlyContinue" +$ErrorActionPreference = "Continue" + +az login --service-principal --username $env:spnClientID --password=$env:spnClientSecret --tenant $env:spnTenantId +az extension add --name customlocation +az extension add -n aksarc --upgrade +$customLocationID=(az customlocation show --resource-group $env:resourceGroup --name $HCIBoxConfig.rbCustomLocationName --query id -o tsv) +$lnetId="/subscriptions/$subId/resourceGroups/$env:resourceGroup/providers/Microsoft.AzureStackHCI/logicalnetworks/$lnetName" +az aksarc create -n $HCIBoxConfig.AKSworkloadClusterName -g $env:resourceGroup --custom-location $customlocationID --vnet-ids $lnetId --aad-admin-group-object-ids $groupObjectID --generate-ssh-keys --control-plane-ip $HCIBoxConfig.AKSControlPlaneIP + +Stop-Transcript diff --git a/azure_jumpstart_hcibox/artifacts/PowerShell/Configure-VMLogicalNetwork.ps1 b/azure_jumpstart_hcibox/artifacts/PowerShell/Configure-VMLogicalNetwork.ps1 new file mode 100644 index 0000000000..c90b033128 --- /dev/null +++ b/azure_jumpstart_hcibox/artifacts/PowerShell/Configure-VMLogicalNetwork.ps1 @@ -0,0 +1,38 @@ +$WarningPreference = "SilentlyContinue" +$ErrorActionPreference = "Stop" +$ProgressPreference = 'SilentlyContinue' + +# Set paths +$Env:HCIBoxDir = "C:\HCIBox" + +# Import Configuration Module +$HCIBoxConfig = Import-PowerShellDataFile -Path $Env:HCIBoxConfigFile +Start-Transcript -Path "$($HCIBoxConfig.Paths.LogsDir)\Configure-VMLogicalNetwork.log" + +$domainCred = new-object -typename System.Management.Automation.PSCredential ` + -argumentlist (($HCIBoxConfig.SDNDomainFQDN.Split(".")[0]) +"\Administrator"), (ConvertTo-SecureString $HCIBoxConfig.SDNAdminPassword -AsPlainText -Force) + +$subId = $env:subscriptionId +$rg = $env:resourceGroup +$spnClientId = $env:spnClientId +$spnSecret = $env:spnClientSecret +$spnTenantId = $env:spnTenantId +$location = "eastus" +$customLocName = $HCIBoxConfig.rbCustomLocationName + +# Create logical networks +Invoke-Command -ComputerName "$($HCIBoxConfig.NodeHostConfig[0].Hostname).$($HCIBoxConfig.SDNDomainFQDN)" -Credential $domainCred -Authentication CredSSP -ArgumentList $HCIBoxConfig -ScriptBlock { + $HCIBoxConfig = $args[0] + az login --service-principal --username $using:spnClientID --password=$using:spnSecret --tenant $using:spnTenantId + az config set extension.use_dynamic_install=yes_without_prompt + $customLocationID=(az customlocation show --resource-group $using:rg --name $using:customLocName --query id -o tsv) + + $switchName='"ConvergedSwitch(hci)"' + $lnetName = "hcibox-vm-lnet-vlan200" + $addressPrefixes = $HCIBoxConfig.vmIpPrefix + $gateway = $HCIBoxConfig.vmGateway + $dnsServers = $HCIBoxConfig.vmDNS + $vlanid = $HCIBoxConfig.vmVLAN + + az stack-hci-vm network lnet create --subscription $using:subId --resource-group $using:rg --custom-location $customLocationID --location $using:location --name $lnetName --vm-switch-name $switchName --ip-allocation-method "static" --address-prefixes $addressPrefixes --gateway $gateway --dns-servers $dnsServers --vlan $vlanid +} \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/PowerShell/Generate-ARM-Template.ps1 b/azure_jumpstart_hcibox/artifacts/PowerShell/Generate-ARM-Template.ps1 new file mode 100644 index 0000000000..94c2a10394 --- /dev/null +++ b/azure_jumpstart_hcibox/artifacts/PowerShell/Generate-ARM-Template.ps1 @@ -0,0 +1,97 @@ +$WarningPreference = "SilentlyContinue" +$ErrorActionPreference = "Stop" +$ProgressPreference = 'SilentlyContinue' + +# Set paths +$Env:HCIBoxDir = "C:\HCIBox" + +# Import Configuration Module +$HCIBoxConfig = Import-PowerShellDataFile -Path $Env:HCIBoxConfigFile +Start-Transcript -Path "$($HCIBoxConfig.Paths.LogsDir)\Generate-ARM-Template.log" + +# Add necessary role assignments +$ErrorActionPreference = "Continue" +New-AzRoleAssignment -ObjectId $env:spnProviderId -RoleDefinitionName "Azure Connected Machine Resource Manager" -ResourceGroup $env:resourceGroup -ErrorAction Continue +$ErrorActionPreference = "Stop" + +$arcNodes = Get-AzConnectedMachine -ResourceGroup $env:resourceGroup +$arcNodeResourceIds = $arcNodes.Id | ConvertTo-Json + +foreach ($machine in $arcNodes) { + $ErrorActionPreference = "Continue" + New-AzRoleAssignment -ObjectId $machine.IdentityPrincipalId -RoleDefinitionName "Key Vault Secrets User" -ResourceGroup $env:resourceGroup + New-AzRoleAssignment -ObjectId $machine.IdentityPrincipalId -RoleDefinitionName "Reader" -ResourceGroup $env:resourceGroup + New-AzRoleAssignment -ObjectId $machine.IdentityPrincipalId -RoleDefinitionName "Azure Stack HCI Device Management Role" -ResourceGroup $env:resourceGroup + New-AzRoleAssignment -ObjectId $machine.IdentityPrincipalId -RoleDefinitionName "Azure Connected Machine Resource Manager" -ResourceGroup $env:resourceGroup + $ErrorActionPreference = "Stop" +} + +# 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 +$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)")) + +# Construct OU path +$domainName = $HCIBoxConfig.SDNDomainFQDN.Split('.') +$ouPath = "OU=$($HCIBoxConfig.LCMADOUName)" +foreach ($name in $domainName) { + $ouPath += ",DC=$name" +} + +# Build DNS value +$dns = "[""" + $HCIBoxConfig.vmDNS + """]" + +# Create keyvault name +$guid = ([System.Guid]::NewGuid()).ToString().subString(0,5).ToLower() +$keyVaultName = "hcibox-kv-" + $guid +$secretsLocation = "https://$keyVaultName.vault.azure.net" + +# Set physical nodes +$physicalNodesSettings = "[ " +$storageAIPs = "[ " +$storageBIPs = "[ " +$count = 0 +foreach ($node in $HCIBoxConfig.NodeHostConfig) { + if ($count -gt 0) { + $physicalNodesSettings += ", " + $storageAIPs += ", " + $storageBIPs += ", " + } + $physicalNodesSettings += "{ ""name"": ""$($node.Hostname)"", ""ipv4Address"": ""$($node.IP.Split("/")[0])"" }" + $count = $count + 1 +} +$physicalNodesSettings += " ]" +$storageAIPs += " ]" +$storageBIPs += " ]" + +# Create diagnostics storage account name +$diagnosticsStorageName = "hciboxdiagsa$guid" + +# Replace placeholder values in ARM template with real values +$hciParams = "$env:HCIBoxDir\hci.parameters.json" +(Get-Content -Path $hciParams) -replace 'clusterName-staging', $HCIBoxConfig.ClusterName | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'arcNodeResourceIds-staging', $arcNodeResourceIds | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'localAdminSecretValue-staging', $LocalUser | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'domainAdminSecretValue-staging', $AzureStackLCM | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'arbDeploymentSpnValue-staging', $AzureSPN | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'storageWitnessValue-staging', $storageAccountAccessKey | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'domainFqdn-staging', $($HCIBoxConfig.SDNDomainFQDN) | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'namingPrefix-staging', $($HCIBoxConfig.LCMDeploymentPrefix) | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'adouPath-staging', $ouPath | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'subnetMask-staging', $($HCIBoxConfig.rbSubnetMask) | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'defaultGateway-staging', $HCIBoxConfig.SDNLabRoute | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'startingIp-staging', $HCIBoxConfig.clusterIpRangeStart | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'endingIp-staging', $HCIBoxConfig.clusterIpRangeEnd | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'dnsServers-staging', $dns | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'keyVaultName-staging', $keyVaultName | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'secretsLocation-staging', $secretsLocation | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'physicalNodesSettings-staging', $physicalNodesSettings | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'ClusterWitnessStorageAccountName-staging', $env:stagingStorageAccountName | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'diagnosticStorageAccountName-staging', $diagnosticsStorageName | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'storageNicAVLAN-staging', $HCIBoxConfig.StorageAVLAN | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'storageNicBVLAN-staging', $HCIBoxConfig.StorageBVLAN | Set-Content -Path $hciParams +(Get-Content -Path $hciParams) -replace 'customLocation-staging', $HCIBoxConfig.rbCustomLocationName | Set-Content -Path $hciParams \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/HCIBox-Config.psd1 b/azure_jumpstart_hcibox/artifacts/PowerShell/HCIBox-Config.psd1 similarity index 53% rename from azure_jumpstart_hcibox/artifacts/HCIBox-Config.psd1 rename to azure_jumpstart_hcibox/artifacts/PowerShell/HCIBox-Config.psd1 index 3a20a3cabc..96e12e8448 100644 --- a/azure_jumpstart_hcibox/artifacts/HCIBox-Config.psd1 +++ b/azure_jumpstart_hcibox/artifacts/PowerShell/HCIBox-Config.psd1 @@ -1,30 +1,105 @@ @{ # 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. - - # Version 1.0.0 - # HCI host names - HostList = "AZSHOST1", "AZSHOST2" # DO NOT CHANGE these as they remain hardcoded in places + # HCIBox Folders + Paths = @{ + VMDir = "C:\HCIBox\Virtual Machines" + LogsDir = "C:\HCIBox\Logs" + IconDir = "C:\HCIBox\Icons" + VHDDir = "C:\HCIBox\VHD" + SDNDir = "C:\HCIBox\SDN" + KVDir = "C:\HCIBox\KeyVault" + WACDir = "C:\HCIBox\Windows Admin Center" + AgentScriptDir = "C:\HCIBox\agentScript" + ToolsDir = "C:\Tools" + TempDir = "C:\Temp" + VMPath = "C:\VMs" + } + + ChocolateyPackagesList = @( + 'az.powershell', + 'kubernetes-cli', + 'vcredist140', + 'microsoft-edge', + 'azcopy10', + 'vscode', + 'git', + '7zip', + 'kubectx', + 'terraform', + 'putty.install', + 'kubernetes-helm', + 'dotnet-sdk', + 'setdefaultbrowser', + 'zoomit', + 'azure-data-studio' + ) + + PowerShellModulesList = @( + 'Az.Resources', + 'Az.ConnectedMachine' + ) + + # VSCode extensions + VSCodeExtensions = @( + 'ms-vscode-remote.remote-containers', + 'ms-vscode-remote.remote-wsl', + 'ms-vscode.powershell', + 'redhat.vscode-yaml', + 'ZainChen.json', + 'esbenp.prettier-vscode', + 'ms-kubernetes-tools.vscode-kubernetes-tools' + ) + + 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. + 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" + } + + NodeHostConfig = @( + @{ + Hostname = "AzSHOST1" + IP = "192.168.1.12/24" + StorageAIP = "10.71.1.10" + StorageBIP = "10.71.2.10" + }, + @{ + Hostname = "AzSHOST2" + IP = "192.168.1.13/24" + StorageAIP = "10.71.1.11" + StorageBIP = "10.71.2.11" + } + ) - # VHDX Paths - 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. - # SDN Lab Admin Password SDNAdminPassword = '%staging-password%' # Do not change - this value is replaced during Bootstrap with the password supplied in the ARM deployment # VM Configuration - HostVMPath = "V:\VMs" # This value controls the path where the Nested VMs will be stored on all hosts. NestedVMMemoryinGB = 105GB # This value controls the amount of RAM for each Nested Hyper-V Host (AzSHOST1-2). AzSMGMTMemoryinGB = 28GB # This value controls the amount of RAM for the AzSMGMT Nested VM which contains only the Console, Router, Admincenter, and DC VMs. - InternalSwitch = "InternalSwitch" # Name of internal switch that the HCIBox VMs will use in Single Host mode. This only applies when using a single host. + AzSMGMTProcCount = 20 + InternalSwitch = "InternalSwitch" # Name of internal switch that the HCIBox VMs will use in Single Host mode. + FabricSwitch = "vSwitch-Fabric" + FabricNIC = "FABRIC" + ClusterVSwitchName = "hciSwitch" + ClusterName = "hciboxcluster" + WACVMName = "AdminCenter" + ClusterSharedVolumePath = "C:\ClusterStorage\S2D_vDISK1" + LCMDeployUsername = "HCIBoxDeployUser" + LCMADOUName = "hcioudocs" + LCMDeploymentPrefix = "hcibox" # ProductKeys 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 @@ -35,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. @@ -48,67 +123,49 @@ # AzSMGMT Management VM's Memory Settings MEM_DC = 2GB # Memory provided for the Domain Controller VM MEM_BGP = 2GB # Memory provided for the BGP-ToR-Router - MEM_Console = 3GB # Memory provided for the Windows 10 Console VM MEM_WAC = 10GB # Memory provided for the Windows Admin Center VM - MEM_GRE = 2GB # Memory provided for the gre-target VM - MEM_IPSEC = 2GB # Memory provided for the ipsec-target VM # Cluster S2D Storage Disk Size (per disk) S2D_Disk_Size = 170GB # Disk size for each of the 4 dynamic VHD disks attached to the 3 AzSHOST VMs that will be used to create the SDNCLUSTER - # SDN Host IPs - AzSMGMTIP = "192.168.1.11/24" - AzSHOST1IP = "192.168.1.12/24" - AzSHOST2IP = "192.168.1.13/24" - AzSHOST3IP = "192.168.1.14/24" - # Physical Host Internal IP 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" # Management IPs for Console and Domain Controller DCIP = "192.168.1.254/24" - CONSOLEIP = "192.168.1.10/24" WACIP = "192.168.1.9/24" + WACMAC = "10155D010B00" - # BGP Router Config + # Router Config + BGPRouterName = "vm-router" BGPRouterIP_MGMT = "192.168.1.1/24" BGPRouterIP_ProviderNetwork = "172.16.0.1/24" + BGPRouterIP_VLAN110 = "10.10.0.1/24" BGPRouterIP_VLAN200 = "192.168.200.1/24" BGPRouterIP_SimulatedInternet = "131.127.0.1/24" BGPRouterASN = "65534" # VLANs providerVLAN = 12 + vlan110VLAN = 110 vlan200VLAN = 200 mgmtVLAN = 0 simInternetVLAN = 131 - StorageAVLAN = 20 - StorageBVLAN = 21 + StorageAVLAN = 711 + StorageBVLAN = 712 # Subnets MGMTSubnet = "192.168.1.0/24" - GRESubnet = "50.50.50.0/24" - ProviderSubnet = "172.16.0.0/24" - VLAN200Subnet = "192.168.200.0/24" - VLAN200VMNetworkSubnet = "192.168.44.0/24" - simInternetSubnet = "131.127.0.0/24" - storageAsubnet = "192.168.98.0/24" - storageBsubnet = "192.168.99.0/24" - - # Gateway Target IPs - GRETARGETIP_BE = "192.168.233.100/24" - GRETARGETIP_FE = "131.127.0.35/24" - IPSECTARGETIP_BE = "192.168.111.100/24" - IPSECTARGETIP_FE = "131.127.0.30/24" + storageAsubnet = "255.255.255.0" + storageBsubnet = "255.255.255.0" # VIP Subnets - PrivateVIPSubnet = "30.30.30.0/24" PublicVIPSubnet = "40.40.40.0/24" # SDN ASN @@ -118,34 +175,29 @@ # Windows Admin Center HTTPS Port WACport = 443 - # SDDCInstall - SDDCInstall = $true - # AKS and Resource bridge variables + rbCustomLocationName = "jumpstart" AKSworkloadClusterName = "hcibox-aks" # lowercase only AKSvnetname = "akshcivnet" - AKSvSwitchName = "sdnSwitch" - AKSNodeStartIP = "192.168.200.25" - AKSNodeEndIP = "192.168.200.100" - AKSVIPStartIP = "192.168.200.125" - AKSVIPEndIP = "192.168.200.200" - AKSIPPrefix = "192.168.200.0/24" - AKSGWIP = "192.168.200.1" + AKSNodeStartIP = "10.10.0.101" + AKSNodeEndIP = "10.10.0.199" + AKSVIPStartIP = "10.10.0.10" + AKSVIPEndIP = "10.10.0.100" + AKSIPPrefix = "10.10.0.0/24" + AKSControlPlaneIP = "10.10.0.5" + AKSGWIP = "10.10.0.1" AKSDNSIP = "192.168.1.254" - AKSCSV = "C:\ClusterStorage\S2D_vDISK1" - AKSImagedir = "C:\ClusterStorage\S2D_vDISK1\aks\Images" - AKSWorkingdir = "C:\ClusterStorage\S2D_vDISK1\aks\Workdir" - AKSCloudConfigdir = "C:\ClusterStorage\S2D_vDISK1\aks\CloudConfig" - AKSCloudSvcidr = "192.168.1.15/24" - AKSVlanID = "200" - rbLocation = "eastus" - rbCustomLocationName = "hcibox-rb-cl" - rbIp = "192.168.200.201" - rbIp2 = "192.168.200.202" - rbCpip = "192.168.200.203" - rbVipStart = "192.168.200.200" - rbVipEnd = "192.168.200.249" - rbDHCPExclusionStart = "192.168.200.200" - rbDHCPExclusionEnd = "192.168.200.209" + AKSVLAN = "110" + # rbVipStart = "192.168.200.200" + # rbVipEnd = "192.168.200.249" + # rbDHCPExclusionStart = "192.168.200.200" + # rbDHCPExclusionEnd = "192.168.200.209" dcVLAN200IP = "192.168.200.205" + rbSubnetMask = "255.255.255.0" + clusterIpRangeStart = "192.168.1.100" + clusterIpRangeEnd = "192.168.1.199" + vmGateway = "192.168.200.1" + vmIpPrefix = "192.168.200.0/24" + vmDNS = "192.168.1.254" + vmVLAN = "200" } \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/PowerShell/HCIBoxLogonScript.ps1 b/azure_jumpstart_hcibox/artifacts/PowerShell/HCIBoxLogonScript.ps1 new file mode 100644 index 0000000000..729e35127b --- /dev/null +++ b/azure_jumpstart_hcibox/artifacts/PowerShell/HCIBoxLogonScript.ps1 @@ -0,0 +1,141 @@ +# Script runtime environment: Level-0 Azure virtual machine ("Client VM") +$ProgressPreference = "SilentlyContinue" +Set-PSDebug -Strict + +##################################################################### +# Initialize the environment +##################################################################### + +# Load config file +$HCIBoxConfig = Import-PowerShellDataFile -Path $Env:HCIBoxConfigFile + +Start-Transcript -Path "$($HCIBoxConfig.Paths.LogsDir)\HCIBoxLogonScript.log" + +##################################################################### +# Setup Azure CLI and Azure PowerShell +##################################################################### +$cliDir = New-Item -Path "$Env:ArcBoxDir\.cli\" -Name ".servers" -ItemType Directory + +if(-not $($cliDir.Parent.Attributes.HasFlag([System.IO.FileAttributes]::Hidden))) { + $folder = Get-Item $cliDir.Parent.FullName -ErrorAction SilentlyContinue + $folder.Attributes += [System.IO.FileAttributes]::Hidden +} + +$Env:AZURE_CONFIG_DIR = $cliDir.FullName + +# Login to Azure CLI with service principal provided by user +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 +##################################################################### + +# Register Azure providers +Write-Header "Registering Providers" +az provider register --namespace Microsoft.HybridCompute --wait +az provider register --namespace Microsoft.GuestConfiguration --wait +az provider register --namespace Microsoft.Kubernetes --wait +az provider register --namespace Microsoft.KubernetesConfiguration --wait +az provider register --namespace Microsoft.ExtendedLocation --wait +az provider register --namespace Microsoft.AzureArcData --wait +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 +############################################################# + +Write-Host "[$(Get-Date -Format t)] INFO: Installing VSCode extensions: " + ($HCIBoxConfig.VSCodeExtensions -join ', ') -ForegroundColor Gray +foreach ($extension in $HCIBoxConfig.VSCodeExtensions) { + $WarningPreference = "SilentlyContinue" + code --install-extension $extension 2>&1 | Out-File -Append -FilePath ($HCIBoxConfig.Paths.LogsDir + "\Tools.log") + $WarningPreference = "Continue" +} + +##################################################################### +# Configure virtualization infrastructure +##################################################################### + +# Configure storage pools and data disks +Write-Header "Configuring storage" +New-StoragePool -FriendlyName AsHciPool -StorageSubSystemFriendlyName '*storage*' -PhysicalDisks (Get-PhysicalDisk -CanPool $true) +$disks = Get-StoragePool -FriendlyName AsHciPool -IsPrimordial $False | Get-PhysicalDisk +$diskNum = $disks.Count +New-VirtualDisk -StoragePoolFriendlyName AsHciPool -FriendlyName AsHciDisk -ResiliencySettingName Simple -NumberOfColumns $diskNum -UseMaximumSize +$vDisk = Get-VirtualDisk -FriendlyName AsHciDisk +if ($vDisk | Get-Disk | Where-Object PartitionStyle -eq 'raw') { + $vDisk | Get-Disk | Initialize-Disk -Passthru | New-Partition -DriveLetter $HCIBoxConfig.HostVMDriveLetter -UseMaximumSize | Format-Volume -NewFileSystemLabel AsHciData -AllocationUnitSize 64KB -FileSystem NTFS +} +elseif ($vDisk | Get-Disk | Where-Object PartitionStyle -eq 'GPT') { + $vDisk | Get-Disk | New-Partition -DriveLetter $HCIBoxConfig.HostVMDriveLetter -UseMaximumSize | Format-Volume -NewFileSystemLabel AsHciData -AllocationUnitSize 64KB -FileSystem NTFS +} + +Stop-Transcript + +# Build HCI cluster +& "$Env:HCIBoxDir\New-HCIBoxCluster.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); + } + } + } +'@ + +Write-Header "Changing Wallpaper" +$imgPath="$Env:HCIBoxDir\wallpaper.png" +Add-Type $code +[Win32.Wallpaper]::SetWallpaper($imgPath) + +# Removing the LogonScript Scheduled Task so it won't run on next reboot +Write-Header "Removing Logon Task" +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 { + $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 + Write-Host "`n" + Write-Host "Creating deployment logs bundle" + 7z a $Env:HCIBoxLogsDir\LogsBundle-"$RandomString".zip $Env:HCIBoxLogsDir\*.log +}' + +Stop-Transcript \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/PowerShell/New-HCIBoxCluster.ps1 b/azure_jumpstart_hcibox/artifacts/PowerShell/New-HCIBoxCluster.ps1 new file mode 100644 index 0000000000..7dd853c72a --- /dev/null +++ b/azure_jumpstart_hcibox/artifacts/PowerShell/New-HCIBoxCluster.ps1 @@ -0,0 +1,1828 @@ +# Set paths +$Env:HCIBoxDir = "C:\HCIBox" +$Env:HCIBoxLogsDir = "C:\HCIBox\Logs" + +Start-Transcript -Path $Env:HCIBoxLogsDir\New-HCIBoxCluster.log +$starttime = Get-Date + +# Import Configuration data file +$HCIBoxConfig = Import-PowerShellDataFile -Path $Env:HCIBoxConfigFile + +#region functions +function ConvertFrom-SecureStringToPlainText { + param ( + [Parameter(Mandatory = $true)] + [System.Security.SecureString]$SecureString + ) + + $Ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString) + try { + return [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($Ptr) + } + finally { + [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($Ptr) + } +} + +function BITSRequest { + param ( + [Parameter(Mandatory=$True)] + [hashtable]$Params + ) + $url = $Params['Uri'] + $filename = $Params['Filename'] + $download = Start-BitsTransfer -Source $url -Destination $filename -Asynchronous + $ProgressPreference = "Continue" + while ($download.JobState -ne "Transferred") { + if ($download.JobState -eq "TransientError"){ + 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; + } + Complete-BitsTransfer $download.JobId + Write-Progress -Activity "Downloading File $filename..." -Status "Ready" -Completed + $ProgressPreference = "SilentlyContinue" +} + +function New-InternalSwitch { + param ( + $HCIBoxConfig + ) + $pswitchname = $HCIBoxConfig.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." + } +} + +function Get-FormattedWACMAC { + Param( + $HCIBoxConfig + ) + return $HCIBoxConfig.WACMAC -replace '..(?!$)', '$&-' +} + +function GenerateAnswerFile { + Param( + [Parameter(Mandatory=$True)] $Hostname, + [Parameter(Mandatory=$False)] $IsMgmtVM = $false, + [Parameter(Mandatory=$False)] $IsRouterVM = $false, + [Parameter(Mandatory=$False)] $IsDCVM = $false, + [Parameter(Mandatory=$False)] $IsWACVM = $false, + [Parameter(Mandatory=$False)] $IPAddress = "", + [Parameter(Mandatory=$False)] $VMMac = "", + [Parameter(Mandatory=$True)] $HCIBoxConfig + ) + + $formattedMAC = Get-FormattedWACMAC -HCIBoxConfig $HCIBoxConfig + $encodedPassword = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($($HCIBoxConfig.SDNAdminPassword) + "AdministratorPassword")) + $wacAnswerXML = @" +<?xml version="1.0" encoding="utf-8"?> +<unattend xmlns="urn:schemas-microsoft-com:unattend"> +<settings pass="specialize"> +<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<ProductKey>$($HCIBoxConfig.GUIProductKey)</ProductKey> +<ComputerName>$Hostname</ComputerName> +<RegisteredOwner>$ENV:adminUsername</RegisteredOwner> +</component> +<component name="Microsoft-Windows-TCPIP" processorArchitecture="wow64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<Interfaces> +<Interface wcm:action="add"> +<Ipv4Settings> +<DhcpEnabled>false</DhcpEnabled> +<Metric>20</Metric> +<RouterDiscoveryEnabled>true</RouterDiscoveryEnabled> +</Ipv4Settings> +<UnicastIpAddresses> +<IpAddress wcm:action="add" wcm:keyValue="1">$IPAddress</IpAddress> +</UnicastIpAddresses> +<Identifier>$formattedMAC</Identifier> +<Routes> +<Route wcm:action="add"> +<Identifier>1</Identifier> +<NextHopAddress>$($HCIBoxConfig.SDNLABRoute)</NextHopAddress> +</Route> +</Routes> +</Interface> +</Interfaces> +</component> +<component name="Microsoft-Windows-DNS-Client" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<Interfaces> +<Interface wcm:action="add"> +<DNSServerSearchOrder> +<IpAddress wcm:action="add" wcm:keyValue="1">$($HCIBoxConfig.SDNLABDNS)</IpAddress> +</DNSServerSearchOrder> +<Identifier>$formattedMAC</Identifier> +<DNSDomain>$($HCIBoxConfig.SDNDomainFQDN)</DNSDomain> +<EnableAdapterDomainNameRegistration>true</EnableAdapterDomainNameRegistration> +</Interface> +</Interfaces> +</component> +<component name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<DomainProfile_EnableFirewall>false</DomainProfile_EnableFirewall> +<PrivateProfile_EnableFirewall>false</PrivateProfile_EnableFirewall> +<PublicProfile_EnableFirewall>false</PublicProfile_EnableFirewall> +</component> +<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<fDenyTSConnections>false</fDenyTSConnections> +</component> +<component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<Identification> +<Credentials> +<Domain>$($HCIBoxConfig.SDNDomainFQDN)</Domain> +<Password>$($HCIBoxConfig.SDNAdminPassword)</Password> +<Username>Administrator</Username> +</Credentials> +<JoinDomain>$($HCIBoxConfig.SDNDomainFQDN)</JoinDomain> +</Identification> +</component> +<component name="Microsoft-Windows-IE-ESC" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<IEHardenAdmin>false</IEHardenAdmin> +<IEHardenUser>false</IEHardenUser> +</component> +</settings> +<settings pass="oobeSystem"> +<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<UserAccounts> +<AdministratorPassword> +<PlainText>false</PlainText> +<Value>$encodedPassword</Value> +</AdministratorPassword> +</UserAccounts> +<TimeZone>UTC</TimeZone> +<OOBE> +<HideEULAPage>true</HideEULAPage> +<SkipUserOOBE>true</SkipUserOOBE> +<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> +<HideOnlineAccountScreens>true</HideOnlineAccountScreens> +<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> +<NetworkLocation>Work</NetworkLocation> +<ProtectYourPC>1</ProtectYourPC> +<HideLocalAccountScreen>true</HideLocalAccountScreen> +</OOBE> +</component> +<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<UserLocale>en-US</UserLocale> +<SystemLocale>en-US</SystemLocale> +<InputLocale>0409:00000409</InputLocale> +<UILanguage>en-US</UILanguage> +</component> +</settings> +<cpi:offlineImage cpi:source="" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> +</unattend> +"@ + + $components = @" +<component name="Microsoft-Windows-IE-ESC" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<IEHardenAdmin>false</IEHardenAdmin> +<IEHardenUser>false</IEHardenUser> +</component> +<component name="Microsoft-Windows-TCPIP" processorArchitecture="wow64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<Interfaces> +<Interface wcm:action="add"> +<Identifier>$VMMac</Identifier> +<Ipv4Settings> +<DhcpEnabled>false</DhcpEnabled> +</Ipv4Settings> +<UnicastIpAddresses> +<IpAddress wcm:action="add" wcm:keyValue="1">$IPAddress</IpAddress> +</UnicastIpAddresses> +<Routes> +<Route wcm:action="add"> +<Identifier>1</Identifier> +<NextHopAddress>$($HCIBoxConfig.SDNLABRoute)</NextHopAddress> +<Prefix>0.0.0.0/0</Prefix> +<Metric>100</Metric> +</Route> +</Routes> +</Interface> +</Interfaces> +</component> +<component name="Microsoft-Windows-DNS-Client" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<DNSSuffixSearchOrder> +<DomainName wcm:action="add" wcm:keyValue="1">$($HCIBoxConfig.SDNDomainFQDN)</DomainName> +</DNSSuffixSearchOrder> +<Interfaces> +<Interface wcm:action="add"> +<DNSServerSearchOrder> +<IpAddress wcm:action="add" wcm:keyValue="1">$($HCIBoxConfig.SDNLABDNS)</IpAddress> +</DNSServerSearchOrder> +<Identifier>$VMMac</Identifier> +<DisableDynamicUpdate>false</DisableDynamicUpdate> +<DNSDomain>$($HCIBoxConfig.SDNDomainFQDN)</DNSDomain> +<EnableAdapterDomainNameRegistration>true</EnableAdapterDomainNameRegistration> +</Interface> +</Interfaces> +</component> +"@ + + $azsmgmtProdKey = "" + if ($IsMgmtVM) { + $azsmgmtProdKey = "<ProductKey>$($HCIBoxConfig.GUIProductKey)</ProductKey>" + } + $vmServicing = "" + + if ($IsRouterVM -or $IsDCVM) { + $components = "" + $optionXML = "" + if ($IsRouterVM) { + $optionXML = @" +<selection name="RemoteAccessServer" state="true" /> +<selection name="RasRoutingProtocols" state="true" /> +"@ + } + if ($IsDCVM) { + $optionXML = @" +<selection name="ADCertificateServicesRole" state="true" /> +<selection name="CertificateServices" state="true" /> +"@ + } + $vmServicing = @" +<servicing> +<package action="configure"> +<assemblyIdentity name="Microsoft-Windows-Foundation-Package" version="10.0.14393.0" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="" /> +$optionXML</package> +</servicing> +"@ + } + + $UnattendXML = @" +<?xml version="1.0" encoding="utf-8"?> +<unattend xmlns="urn:schemas-microsoft-com:unattend"> +$vmServicing<settings pass="specialize"> +<component name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<DomainProfile_EnableFirewall>false</DomainProfile_EnableFirewall> +<PrivateProfile_EnableFirewall>false</PrivateProfile_EnableFirewall> +<PublicProfile_EnableFirewall>false</PublicProfile_EnableFirewall> +</component> +<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<ComputerName>$Hostname</ComputerName> +$azsmgmtProdKey</component> +<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<fDenyTSConnections>false</fDenyTSConnections> +</component> +<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<UserLocale>en-us</UserLocale> +<UILanguage>en-us</UILanguage> +<SystemLocale>en-us</SystemLocale> +<InputLocale>en-us</InputLocale> +</component> +$components</settings> +<settings pass="oobeSystem"> +<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<OOBE> +<HideEULAPage>true</HideEULAPage> +<SkipMachineOOBE>true</SkipMachineOOBE> +<SkipUserOOBE>true</SkipUserOOBE> +<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> +</OOBE> +<UserAccounts> +<AdministratorPassword> +<PlainText>false</PlainText> +<Value>$encodedPassword</Value> +</AdministratorPassword> +</UserAccounts> +</component> +</settings> +<cpi:offlineImage cpi:source="" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> +</unattend> +"@ + if ($IsWACVM) { + $UnattendXML = $wacAnswerXML + } + return $UnattendXML +} + +function Restart-VMs { + Param ( + $HCIBoxConfig, + [PSCredential]$Credential + ) + foreach ($VM in $HCIBoxConfig.NodeHostConfig) { + Write-Host "Restarting VM: $($VM.Hostname)" + Invoke-Command -VMName $VM.Hostname -Credential $Credential -ScriptBlock { + Restart-Computer -Force + } + } + Write-Host "Restarting VM: $($HCIBoxConfig.MgmtHostConfig.Hostname)" + Invoke-Command -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Credential $Credential -ScriptBlock { + Restart-Computer -Force + } + Start-Sleep -Seconds 30 +} + +function New-ManagementVM { + Param ( + $Name, + $VHDXPath, + $VMSwitch, + $HCIBoxConfig + ) + Write-Host "Creating VM $Name" + # Create disks + $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 + # Create Nested VM + New-VM -Name $Name -MemoryStartupBytes $HCIBoxConfig.AzSMGMTMemoryinGB -VHDPath $VHDX1.Path -SwitchName $VMSwitch -Generation 2 | Out-Null + Add-VMHardDiskDrive -VMName $Name -Path $VHDX2.Path + Set-VM -Name $Name -ProcessorCount $HCIBoxConfig.AzSMGMTProcCount -AutomaticStartAction Start | Out-Null + + Get-VMNetworkAdapter -VMName $Name | Rename-VMNetworkAdapter -NewName "SDN" + Get-VMNetworkAdapter -VMName $Name | Set-VMNetworkAdapter -DeviceNaming On -StaticMacAddress ("{0:D12}" -f ( Get-Random -Minimum 0 -Maximum 99999 )) + Add-VMNetworkAdapter -VMName $Name -Name SDN2 -DeviceNaming On -SwitchName $VMSwitch + $vmMac = (((Get-VMNetworkAdapter -Name SDN -VMName $Name).MacAddress) -replace '..(?!$)', '$&-') + + Get-VM $Name | Set-VMProcessor -ExposeVirtualizationExtensions $true + Get-VM $Name | Set-VMMemory -DynamicMemoryEnabled $false + 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 + + Enable-VMIntegrationService -VMName $Name -Name "Guest Service Interface" + return $vmMac +} + +function New-HCINodeVM { + param ( + $Name, + $VHDXPath, + $VMSwitch, + $HCIBoxConfig + ) + Write-Host "Creating VM $Name" + # Create disks + $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 + 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 + + # Create Nested VM + New-VM -Name $Name -MemoryStartupBytes $HCIBoxConfig.NestedVMMemoryinGB -VHDPath $VHDX1.Path -SwitchName $VMSwitch -Generation 2 | Out-Null + Add-VMHardDiskDrive -VMName $Name -Path $VHDX2.Path + Add-VMHardDiskDrive -Path "$HostVMPath\$Name-S2D_Disk1.vhdx" -VMName $Name | Out-Null + Add-VMHardDiskDrive -Path "$HostVMPath\$Name-S2D_Disk2.vhdx" -VMName $Name | Out-Null + Add-VMHardDiskDrive -Path "$HostVMPath\$Name-S2D_Disk3.vhdx" -VMName $Name | Out-Null + Add-VMHardDiskDrive -Path "$HostVMPath\$Name-S2D_Disk4.vhdx" -VMName $Name | Out-Null + Add-VMHardDiskDrive -Path "$HostVMPath\$Name-S2D_Disk5.vhdx" -VMName $Name | Out-Null + Add-VMHardDiskDrive -Path "$HostVMPath\$Name-S2D_Disk6.vhdx" -VMName $Name | Out-Null + + Set-VM -Name $Name -ProcessorCount 20 -AutomaticStartAction Start + Get-VMNetworkAdapter -VMName $Name | Rename-VMNetworkAdapter -NewName "SDN" + Get-VMNetworkAdapter -VMName $Name | Set-VMNetworkAdapter -DeviceNaming On -StaticMacAddress ("{0:D12}" -f ( Get-Random -Minimum 0 -Maximum 99999 )) + # Add-VMNetworkAdapter -VMName $Name -Name SDN2 -DeviceNaming On -SwitchName $VMSwitch + $vmMac = ((Get-VMNetworkAdapter -Name SDN -VMName $Name).MacAddress) -replace '..(?!$)', '$&-' + + Add-VMNetworkAdapter -VMName $Name -SwitchName $VMSwitch -DeviceNaming On -Name StorageA + Add-VMNetworkAdapter -VMName $Name -SwitchName $VMSwitch -DeviceNaming On -Name StorageB + + Get-VM $Name | Set-VMProcessor -ExposeVirtualizationExtensions $true + Get-VM $Name | Set-VMMemory -DynamicMemoryEnabled $false + 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 StorageA -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 +} + +function Set-MGMTVHDX { + param ( + $VMMac, + $HCIBoxConfig + ) + $DriveLetter = $($HCIBoxConfig.HostVMPath).Split(':') + $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 + + # Mount VHDX - bunch of kludgey logic in here to deal with different partition layouts on the GUI and HCI VHD images + Write-Host "Mounting VHDX file at $path" + [string]$MountedDrive = "" + $partition = Mount-VHD -Path $path -Passthru | Get-Disk | Get-Partition -PartitionNumber 3 + if (!$partition.DriveLetter) { + $MountedDrive = "X" + $partition | Set-Partition -NewDriveLetter $MountedDrive + } + else { + $MountedDrive = $partition.DriveLetter + } + + # 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" + $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 + + # Creating folder structure on AzSMGMT + Write-Host "Creating VMs\Base folder structure on $($HCIBoxConfig.MgmtHostConfig.Hostname)" + New-Item -Path ($MountedDrive + ":\VMs\Base") -ItemType Directory -Force | Out-Null + + # Injecting configs into VMs + Write-Host "Injecting files into $path" + Copy-Item -Path "$Env:HCIBoxDir\HCIBox-Config.psd1" -Destination ($MountedDrive + ":\") -Recurse -Force + 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 + + # Dismount VHDX + Write-Host "Dismounting VHDX File at path $path" + Dismount-VHD $path +} + +function Set-HCINodeVHDX { + param ( + $Hostname, + $IPAddress, + $VMMac, + $HCIBoxConfig + ) + $DriveLetter = $($HCIBoxConfig.HostVMPath).Split(':') + $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 + Start-Sleep -Seconds 15 + + Write-Host "Mounting VHDX file at $path" + $partition = Mount-VHD -Path $path -Passthru | Get-Disk | Get-Partition -PartitionNumber 3 + 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" + $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)\Ubuntu.vhdx" -Destination ($MountedDrive + ":\VHD") -Recurse -Force + + # Dismount VHDX + Write-Host "Dismounting VHDX File at path $path" + Dismount-VHD $path +} + +function Set-DataDrives { + param ( + $HCIBoxConfig, + [PSCredential]$Credential + ) + $VMs = @() + $VMs += $HCIBoxConfig.MgmtHostConfig.Hostname + foreach ($node in $HCIBoxConfig.NodeHostConfig) { + $VMs += $node.Hostname + } + foreach ($VM in $VMs) { + Invoke-Command -VMName $VM -Credential $Credential -ScriptBlock { + 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 + } + } +} + +function Test-VMAvailable { + param ( + $VMName, + [PSCredential]$Credential + ) + Invoke-Command -VMName $VMName -ScriptBlock { + $ErrorOccurred = $false + do { + try { + $ErrorActionPreference = 'Stop' + Get-VMHost | Out-Null + } + catch { + $ErrorOccurred = $true + } + } while ($ErrorOccurred -eq $true) + } -Credential $Credential -ErrorAction Ignore + Write-Host "VM $VMName is now online" +} + +function Test-AllVMsAvailable + { + param ( + $HCIBoxConfig, + [PSCredential]$Credential + ) + Write-Host "Testing whether VMs are available..." + Test-VMAvailable -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Credential $Credential + foreach ($VM in $HCIBoxConfig.NodeHostConfig) { + 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 + 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 + + #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 + Get-VMNetworkAdapter -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Name VLAN110 | Set-VMNetworkAdapterVlan -Access -VlanId $HCIBoxConfig.vlan110VLAN | Out-Null + + #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 + + #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 ( + $HCIBoxConfig, + [PSCredential]$Credential + ) + + Invoke-Command -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Credential $Credential -ScriptBlock { + Get-NetAdapter ((Get-NetAdapterAdvancedProperty | Where-Object {$_.DisplayValue -eq "SDN"}).Name) | Rename-NetAdapter -NewName FABRIC + # Get-NetAdapter ((Get-NetAdapterAdvancedProperty | Where-Object {$_.DisplayValue -eq "SDN2"}).Name) | Rename-NetAdapter -NewName FABRIC2 + } + + $int = 9 + foreach ($VM in $HCIBoxConfig.NodeHostConfig) { + $int++ + Write-Host "Setting NICs on VM $($VM.Hostname)" + Invoke-Command -VMName $VM.Hostname -Credential $Credential -ArgumentList $HCIBoxConfig, $VM -ScriptBlock { + $HCIBoxConfig = $args[0] + $VM = $args[1] + # Create IP Address of Storage Adapters + $storageAIP = $VM.StorageAIP + $storageBIP = $VM.StorageBIP + + # 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 + } + $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 } + } + + # Enable WinRM + Write-Host "Enabling Windows Remoting in $env:COMPUTERNAME" + Set-Item WSMan:\localhost\Client\TrustedHosts * -Confirm:$false -Force + Enable-PSRemoting | Out-Null + + Start-Sleep -Seconds 60 + + # Rename non-storage adapters + Get-NetAdapter ((Get-NetAdapterAdvancedProperty | Where-Object {$_.DisplayValue -eq "SDN"}).Name) | Rename-NetAdapter -NewName FABRIC + # Get-Netadapter ((Get-NetAdapterAdvancedProperty | Where-Object {$_.DisplayValue -eq "SDN2"}).Name) | Rename-NetAdapter -NewName FABRIC2 + + # Enable CredSSP Settings + Invoke-Command -ComputerName localhost -Credential $using:Credential -ScriptBlock { + $fqdn = $Using:HCIBoxConfig.SDNDomainFQDN + + Write-Host "Enabling CredSSP on $env:COMPUTERNAME" + Enable-WSManCredSSP -Role Server -Force + Enable-WSManCredSSP -Role Client -DelegateComputer localhost -Force + Enable-WSManCredSSP -Role Client -DelegateComputer $env:COMPUTERNAME -Force + Enable-WSManCredSSP -Role Client -DelegateComputer $fqdn -Force + Enable-WSManCredSSP -Role Client -DelegateComputer "*.$fqdn" -Force + 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 + } -InDisconnectedSession | Out-Null + } + } +} + +function Set-FabricNetwork { + param ( + $HCIBoxConfig, + [PSCredential]$localCred + ) + Start-Sleep -Seconds 20 + Write-Host "Configuring Fabric network on Management VM" + Invoke-Command -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Credential $localCred -ScriptBlock { + $localCred = $using:localCred + $domainCred = $using:domainCred + $HCIBoxConfig = $using:HCIBoxConfig + + $ErrorActionPreference = "Stop" + + # 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 + Enable-PSRemoting | Out-Null + + # Disable ServerManager Auto-Start + Get-ScheduledTask -TaskName ServerManager | Disable-ScheduledTask | Out-Null + + # Create Hyper-V Networking for AzSMGMT + 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") + $provIP = $HCIBoxConfig.BGPRouterIP_ProviderNetwork.TrimEnd("1/24") + "254" + $vlan200IP = $HCIBoxConfig.BGPRouterIP_VLAN200.TrimEnd("1/24") + "250" + $vlan110IP = $HCIBoxConfig.BGPRouterIP_VLAN110.TrimEnd("1/24") + "250" + $provGW = $HCIBoxConfig.BGPRouterIP_ProviderNetwork.TrimEnd("/24") + $provpfx = $HCIBoxConfig.BGPRouterIP_ProviderNetwork.Split("/")[1] + $vlan200pfx = $HCIBoxConfig.BGPRouterIP_VLAN200.Split("/")[1] + $vlan110pfx = $HCIBoxConfig.BGPRouterIP_VLAN110.Split("/")[1] + $simInternetIP = $HCIBoxConfig.BGPRouterIP_SimulatedInternet.TrimEnd("1/24") + "254" + $simInternetPFX = $HCIBoxConfig.BGPRouterIP_SimulatedInternet.Split("/")[1] + New-VMSwitch -SwitchName NAT -SwitchType Internal -MinimumBandwidthMode None | Out-Null + New-NetIPAddress -IPAddress $natIP -PrefixLength $Prefix -InterfaceAlias "vEthernet (NAT)" | Out-Null + New-NetNat -Name NATNet -InternalIPInterfaceAddressPrefix $HCIBoxConfig.natSubnet | Out-Null + + Write-Host "Configuring Provider NIC on $env:COMPUTERNAME" + $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq "PROVIDER" } + Rename-NetAdapter -name $NIC.name -newname "PROVIDER" | Out-Null + New-NetIPAddress -InterfaceAlias "PROVIDER" -IPAddress $provIP -PrefixLength $provpfx | Out-Null + + Write-Host "Configuring VLAN200 NIC on $env:COMPUTERNAME" + $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq "VLAN200" } + Rename-NetAdapter -name $NIC.name -newname "VLAN200" | Out-Null + New-NetIPAddress -InterfaceAlias "VLAN200" -IPAddress $vlan200IP -PrefixLength $vlan200pfx | Out-Null + + Write-Host "Configuring VLAN110 NIC on $env:COMPUTERNAME" + $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 + + Write-Host "Configuring simulatedInternet NIC on $env:COMPUTERNAME" + $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 + + 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 + $internetIP = $HCIBoxConfig.natHostSubnet.Replace("0/24", "5") + $internetGW = $HCIBoxConfig.natHostSubnet.Replace("0/24", "1") + Start-Sleep -Seconds 15 + $internetIndex = (Get-NetAdapter | Where-Object { $_.Name -eq "Internet" }).ifIndex + Start-Sleep -Seconds 15 + New-NetIPAddress -IPAddress $internetIP -PrefixLength 24 -InterfaceIndex $internetIndex -DefaultGateway $internetGW -AddressFamily IPv4 | Out-Null + Set-DnsClientServerAddress -InterfaceIndex $internetIndex -ServerAddresses ($HCIBoxConfig.natDNS) | Out-Null + + # Provision Public and Private VIP Route + New-NetRoute -DestinationPrefix $HCIBoxConfig.PublicVIPSubnet -NextHop $provGW -InterfaceAlias PROVIDER | Out-Null + + # Remove 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 + } +} + +function New-DCVM { + Param ( + $HCIBoxConfig, + [PSCredential]$localCred, + [PSCredential]$domainCred + ) + Write-Host "Creating domain controller VM" + $adminUser = $env:adminUsername + $Unattend = GenerateAnswerFile -Hostname $HCIBoxConfig.DCName -IsDCVM $true -HCIBoxConfig $HCIBoxConfig + Invoke-Command -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Credential $localCred -ScriptBlock { + $adminUser = $using:adminUser + $HCIBoxConfig = $using:HCIBoxConfig + $localCred = $using:localcred + $domainCred = $using:domainCred + $ParentDiskPath = "C:\VMs\Base\" + $vmpath = "D:\VMs\" + $OSVHDX = "GUI.vhdx" + $VMName = $HCIBoxConfig.DCName + + # Create Virtual Machine + 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" + New-VM -Name $VMName -VHDPath ($vmpath + $VMName + '\' + $VMName + '.vhdx') -Path ($vmpath + $VMName) -Generation 2 | Out-Null + + Write-Host "Setting $VMName Memory" + Set-VMMemory -VMName $VMName -DynamicMemoryEnabled $true -StartupBytes $HCIBoxConfig.MEM_DC -MaximumBytes $HCIBoxConfig.MEM_DC -MinimumBytes 500MB | Out-Null + + 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." + 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..." + New-Item -Path C:\TempMount\windows -ItemType Directory -Name Panther -Force | Out-Null + Set-Content -Value $using:Unattend -Path "C:\TempMount\Windows\Panther\Unattend.xml" -Force + Write-Host "Dismounting Windows Image" + Dismount-WindowsImage -Path "C:\TempMount" -Save | Out-Null + Remove-Item "C:\TempMount" | Out-Null + + # Start Virtual Machine + 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 } + + Write-Host "Configuring $VMName and Installing Active Directory." + Invoke-Command -VMName $VMName -Credential $localCred -ArgumentList $HCIBoxConfig -ScriptBlock { + $HCIBoxConfig = $args[0] + $DCName = $HCIBoxConfig.DCName + $IP = $HCIBoxConfig.SDNLABDNS + $PrefixLength = ($($HCIBoxConfig.MgmtHostConfig.IP).Split("/"))[1] + $SDNLabRoute = $HCIBoxConfig.SDNLABRoute + $DomainFQDN = $HCIBoxConfig.SDNDomainFQDN + $DomainNetBiosName = $DomainFQDN.Split(".")[0] + + 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 + 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 + + Write-Host "Configuring Trusted Hosts on $DCName" + Set-Item WSMan:\localhost\Client\TrustedHosts * -Confirm:$false -Force + + Write-Host "Installing Active Directory forest on $DCName." + $SecureString = ConvertTo-SecureString $HCIBoxConfig.SDNAdminPassword -AsPlainText -Force + Install-ADDSForest -DomainName $DomainFQDN -DomainMode 'WinThreshold' -DatabasePath "C:\Domain" -DomainNetBiosName $DomainNetBiosName -SafeModeAdministratorPassword $SecureString -InstallDns -Confirm -Force -NoRebootOnCompletion # | Out-Null + } + + Write-Host "Stopping $VMName" + Get-VM $VMName | Stop-VM + Write-Host "Starting $VMName" + 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 } + + Write-Host "Configuring User Accounts and Groups in Active Directory" + Invoke-Command -VMName $VMName -Credential $domainCred -ArgumentList $HCIBoxConfig, $adminUser -ScriptBlock { + $HCIBoxConfig = $args[0] + $adminUser = $args[1] + $SDNDomainFQDN = $HCIBoxConfig.SDNDomainFQDN + $SecureString = ConvertTo-SecureString $HCIBoxConfig.SDNAdminPassword -AsPlainText -Force + Set-ADDefaultDomainPasswordPolicy -ComplexityEnabled $false -Identity $HCIBoxConfig.SDNDomainFQDN -MinPasswordLength 0 + + $params = @{ + Name = 'NC Admin' + GivenName = 'NC' + Surname = 'Admin' + SamAccountName = 'NCAdmin' + UserPrincipalName = "NCAdmin@$SDNDomainFQDN" + AccountPassword = $SecureString + Enabled = $true + ChangePasswordAtLogon = $false + CannotChangePassword = $true + PasswordNeverExpires = $true + } + New-ADUser @params + + $params = @{ + Name = $adminUser + GivenName = 'Jumpstart' + Surname = 'Jumpstart' + SamAccountName = $adminUser + UserPrincipalName = "$adminUser@$SDNDomainFQDN" + AccountPassword = $SecureString + Enabled = $true + ChangePasswordAtLogon = $false + CannotChangePassword = $true + PasswordNeverExpires = $true + } + New-ADUser @params + + $params.Name = 'NC Client' + $params.Surname = 'Client' + $params.SamAccountName = 'NCClient' + $params.UserPrincipalName = "NCClient@$SDNDomainFQDN" + New-ADUser @params + + New-ADGroup -name “NCAdmins” -groupscope Global + New-ADGroup -name “NCClients” -groupscope Global + + Add-ADGroupMember "Domain Admins" "NCAdmin" + Add-ADGroupMember "NCAdmins" "NCAdmin" + Add-ADGroupMember "NCClients" "NCClient" + Add-ADGroupMember "NCClients" $adminUser + Add-ADGroupMember "NCAdmins" $adminUser + Add-ADGroupMember "Domain Admins" $adminUser + Add-ADGroupMember "NCAdmins" $adminUser + Add-ADGroupMember "NCClients" $adminUser + + # Set Administrator Account Not to Expire + Get-ADUser Administrator | Set-ADUser -PasswordNeverExpires $true -CannotChangePassword $true + Get-ADUser $adminUser | Set-ADUser -PasswordNeverExpires $true -CannotChangePassword $true + + # Set DNS Forwarder + Write-Host "Adding DNS Forwarders" + Add-DnsServerForwarder $HCIBoxConfig.natDNS + + # 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 + + # Give WebServer Template Enroll rights for Domain Computers + $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() + + 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) + $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" + } + } +} + +function Set-DHCPServerOnDC { + Param ( + $HCIBoxConfig, + [PSCredential]$domainCred, + [PSCredential]$localCred + ) + Invoke-Command -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Credential $localCred -ScriptBlock { + # Add NIC for VLAN200 for DHCP server (for use with Arc-enabled VMs) + Add-VMNetworkAdapter -VMName $VMName -Name "VLAN200" -SwitchName $HCIBoxConfig.FabricSwitch -DeviceNaming "On" + Get-VMNetworkAdapter -VMName $VMName -Name "VLAN200" | Set-VMNetworkAdapterVLAN -Access -VlanId $HCIBoxConfig.AKSVLAN + } + Write-Host "Configuring DHCP scope on DHCP server." + # 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 + New-NetIPAddress -InterfaceAlias VLAN200 -IPAddress $HCIBoxConfig.dcVLAN200IP -PrefixLength ($HCIBoxConfig.AKSIPPrefix.split("/"))[1] -DefaultGateway $HCIBoxConfig.AKSGWIP | Out-Null + + # Install DHCP feature + Install-WindowsFeature DHCP -IncludeManagementTools + CMD.exe /c "netsh dhcp add securitygroups" + Restart-Service dhcpserver + + # Allow DHCP in domain + $dnsName = $HCIBoxConfig.DCName + $fqdnsName = $HCIBoxConfig.DCName + "." + $HCIBoxConfig.SDNDomainFQDN + Add-DhcpServerInDC -DnsName $fqdnsName -IPAddress $HCIBoxConfig.dcVLAN200IP + Get-DHCPServerInDC + + # Bind DHCP only to VLAN200 NIC + Set-DhcpServerv4Binding -ComputerName $dnsName -InterfaceAlias $dnsName -BindingState $false + Set-DhcpServerv4Binding -ComputerName $dnsName -InterfaceAlias VLAN200 -BindingState $true + + # Add DHCP scope for Resource bridge VMs + Add-DhcpServerv4Scope -name "ResourceBridge" -StartRange $HCIBoxConfig.rbVipStart -EndRange $HCIBoxConfig.rbVipEnd -SubnetMask 255.255.255.0 -State Active + $scope = Get-DhcpServerv4Scope + Add-DhcpServerv4ExclusionRange -ScopeID $scope.ScopeID.IPAddressToString -StartRange $HCIBoxConfig.rbDHCPExclusionStart -EndRange $HCIBoxConfig.rbDHCPExclusionEnd + Set-DhcpServerv4OptionValue -ComputerName $dnsName -ScopeId $scope.ScopeID.IPAddressToString -DnsServer $HCIBoxConfig.SDNLABDNS -Router $HCIBoxConfig.BGPRouterIP_VLAN200.Trim("/24") + } +} + +function New-RouterVM { + Param ( + $HCIBoxConfig, + [PSCredential]$localCred + ) + $Unattend = GenerateAnswerFile -Hostname $HCIBoxConfig.BGPRouterName -IsRouterVM $true -HCIBoxConfig $HCIBoxConfig + Invoke-Command -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Credential $localCred -ScriptBlock { + $HCIBoxConfig = $using:HCIBoxConfig + $localCred = $using:localcred + $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 + 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 + Add-VMNetworkAdapter -VMName $VMName -Name Provider -SwitchName $HCIBoxConfig.FabricSwitch -DeviceNaming On + Add-VMNetworkAdapter -VMName $VMName -Name VLAN110 -SwitchName $HCIBoxConfig.FabricSwitch -DeviceNaming On + Add-VMNetworkAdapter -VMName $VMName -Name VLAN200 -SwitchName $HCIBoxConfig.FabricSwitch -DeviceNaming On + Add-VMNetworkAdapter -VMName $VMName -Name SIMInternet -SwitchName $HCIBoxConfig.FabricSwitch -DeviceNaming On + Set-VMNetworkAdapterVlan -VMName $VMName -VMNetworkAdapterName Provider -Access -VlanId $HCIBoxConfig.providerVLAN + 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 + + # Mount disk and inject Answer File + 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 + 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." + Dismount-WindowsImage -Path "C:\TempBGPMount" -Save | Out-Null + Remove-Item "C:\TempBGPMount" + + # Start the VM + Write-Host "Starting $VMName VM." + 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" + Invoke-Command -VMName $VMName -Credential $localCred -ArgumentList $HCIBoxConfig -ScriptBlock { + $HCIBoxConfig = $args[0] + $DNS = $HCIBoxConfig.SDNLABDNS + $natSubnet = $HCIBoxConfig.natSubnet + $natDNS = $HCIBoxConfig.natSubnet + $MGMTIP = $HCIBoxConfig.BGPRouterIP_MGMT.Split("/")[0] + $MGMTPFX = $HCIBoxConfig.BGPRouterIP_MGMT.Split("/")[1] + $PNVIP = $HCIBoxConfig.BGPRouterIP_ProviderNetwork.Split("/")[0] + $PNVPFX = $HCIBoxConfig.BGPRouterIP_ProviderNetwork.Split("/")[1] + $VLANIP = $HCIBoxConfig.BGPRouterIP_VLAN200.Split("/")[0] + $VLANPFX = $HCIBoxConfig.BGPRouterIP_VLAN200.Split("/")[1] + $VLAN110IP = $HCIBoxConfig.BGPRouterIP_VLAN110.Split("/")[0] + $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 + + # Configure NAT + $NIC = Get-NetAdapterAdvancedProperty -RegistryKeyWord "HyperVNetworkAdapterName" | Where-Object { $_.RegistryValue -eq "NAT" } + Rename-NetAdapter -name $NIC.name -newname "NAT" | Out-Null + $Prefix = ($natSubnet.Split("/"))[1] + $natIP = ($natSubnet.TrimEnd("0./$Prefix")) + (".10") + $natGW = ($natSubnet.TrimEnd("0./$Prefix")) + (".1") + New-NetIPAddress -InterfaceAlias "NAT" -IPAddress $natIP -PrefixLength $Prefix -DefaultGateway $natGW | Out-Null + 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" + 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' + + # Configure BGP Peers - commented during refactor for 23h2 + # if ($HCIBoxConfig.ConfigureBGPpeering -and $HCIBoxConfig.ProvisionNC) { + # Write-Verbose "Peering future MUX/GWs" + # $Mux01IP = ($HCIBoxConfig.BGPRouterIP_ProviderNetwork.TrimEnd("1/24")) + "4" + # $GW01IP = ($HCIBoxConfig.BGPRouterIP_ProviderNetwork.TrimEnd("1/24")) + "5" + # $GW02IP = ($HCIBoxConfig.BGPRouterIP_ProviderNetwork.TrimEnd("1/24")) + "6" + # $params = @{ + # Name = 'MUX01' + # LocalIPAddress = $PNVIP + # PeerIPAddress = $Mux01IP + # PeerASN = $HCIBoxConfig.SDNASN + # OperationMode = 'Mixed' + # PeeringMode = 'Automatic' + # } + # Add-BgpPeer @params -PassThru + # $params.Name = 'GW01' + # $params.PeerIPAddress = $GW01IP + # Add-BgpPeer @params -PassThru + # $params.Name = 'GW02' + # $params.PeerIPAddress = $GW02IP + # Add-BgpPeer @params -PassThru + # } + + } + } +} + +function New-AdminCenterVM { + Param ( + $HCIBoxConfig, + $localCred, + $domainCred + ) + $VMName = $HCIBoxConfig.WACVMName + $UnattendXML = GenerateAnswerFile -HostName $VMName -IsWACVM $true -IPAddress $HCIBoxConfig.WACIP -VMMac $HCIBoxConfig.WACMAC -HCIBoxConfig $HCIBoxConfig + Invoke-Command -VMName AzSMGMT -Credential $localCred -ScriptBlock { + $VMName = $using:VMName + $ParentDiskPath = "C:\VMs\Base\" + $VHDPath = "D:\VMs\" + $OSVHDX = "GUI.vhdx" + $BaseVHDPath = $ParentDiskPath + $OSVHDX + $HCIBoxConfig = $using:HCIBoxConfig + $localCred = $using:localCred + $domainCred = $using:domainCred + + # Create Host OS Disk + Write-Host "Creating $VMName differencing disks" + New-VHD -ParentPath $BaseVHDPath -Path (($VHDPath) + ($VMName) + '\' + $VMName + (".vhdx")) -Differencing | Out-Null + + # Mount VHDX + Import-Module DISM + Write-Host "Mounting $VMName VHD" + New-Item -Path "C:\TempWACMount" -ItemType Directory | Out-Null + Mount-WindowsImage -Path "C:\TempWACMount" -Index 1 -ImagePath (($VHDPath) + ($VMName) + '\' + $VMName + (".vhdx")) | Out-Null + + # Copy Source Files + Write-Host "Copying Application and Script Source Files to $VMName" + Copy-Item 'C:\Windows Admin Center' -Destination C:\TempWACMount\ -Recurse -Force + New-Item -Path C:\TempWACMount\VHDs -ItemType Directory -Force | Out-Null + Copy-Item C:\VMs\Base\AzSHCI.vhdx -Destination C:\TempWACMount\VHDs -Force # I dont think this is needed + Copy-Item C:\VMs\Base\GUI.vhdx -Destination C:\TempWACMount\VHDs -Force # I dont think this is needed + + # Create VM + Write-Host "Provisioning the VM $VMName" + New-VM -Name $VMName -VHDPath (($VHDPath) + ($VMName) + '\' + $VMName + (".vhdx")) -Path $VHDPath -Generation 2 | Out-Null + Set-VMMemory -VMName $VMName -DynamicMemoryEnabled $true -StartupBytes $HCIBoxConfig.MEM_WAC -MaximumBytes $HCIBoxConfig.MEM_WAC -MinimumBytes 500MB | Out-Null + Set-VM -Name $VMName -AutomaticStartAction Start -AutomaticStopAction ShutDown | Out-Null + Write-Host "Configuring $VMName networking" + Remove-VMNetworkAdapter -VMName $VMName -Name "Network Adapter" + Add-VMNetworkAdapter -VMName $VMName -Name "Fabric" -SwitchName $HCIBoxConfig.FabricSwitch -DeviceNaming On + 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." + 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 + Remove-Item "C:\TempWACMount" + + Write-Host "Setting $VMName's VM Configuration" + Set-VMProcessor -VMName $VMname -Count 4 + Set-VM -Name $VMName -AutomaticStopAction TurnOff + + Write-Host "Starting $VMName VM." + Start-VM -Name $VMName + + # Wait until the VM is restarted + while ((Invoke-Command -VMName $VMName -Credential $domainCred { "Test" } -ea SilentlyContinue) -ne "Test") { Start-Sleep -Seconds 5 } + + # Configure WAC + Invoke-Command -VMName $VMName -Credential $domainCred -ArgumentList $HCIBoxConfig, $VMName, $domainCred -ScriptBlock { + $HCIBoxConfig = $args[0] + $VMName = $args[1] + $domainCred = $args[2] + Import-Module NetAdapter + + Write-Host "Enabling Remote Access on $VMName" + 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" + Get-NetAdapter | Rename-NetAdapter -NewName Fabric + + # Set Gateway + $index = (Get-WmiObject Win32_NetworkAdapter | Where-Object { $_.netconnectionid -eq "Fabric" }).InterfaceIndex + $NetInterface = Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object { $_.InterfaceIndex -eq $index } + $NetInterface.SetGateways($HCIBoxConfig.SDNLABRoute) | Out-Null + + # Enable CredSSP + Write-Host "Configuring WSMAN Trusted Hosts on $VMName" + Set-Item WSMan:\localhost\Client\TrustedHosts * -Confirm:$false -Force | Out-Null + Enable-WSManCredSSP -Role Client -DelegateComputer * -Force | Out-Null + Enable-PSRemoting -force | Out-Null + Enable-WSManCredSSP -Role Server -Force | Out-Null + Enable-WSManCredSSP -Role Client -DelegateComputer localhost -Force | Out-Null + Enable-WSManCredSSP -Role Client -DelegateComputer $env:COMPUTERNAME -Force | Out-Null + Enable-WSManCredSSP -Role Client -DelegateComputer $HCIBoxConfig.SDNDomainFQDN -Force | Out-Null + Enable-WSManCredSSP -Role Client -DelegateComputer "*.$($HCIBoxConfig.SDNDomainFQDN)" -Force | Out-Null + New-Item -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\CredentialsDelegation -Name AllowFreshCredentialsWhenNTLMOnly -Force | Out-Null + New-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\CredentialsDelegation\AllowFreshCredentialsWhenNTLMOnly -Name 1 -Value * -PropertyType String -Force | Out-Null + + $WACIP = $HCIBoxConfig.WACIP.Split("/")[0] + + # Install RSAT-NetworkController + $isAvailable = Get-WindowsFeature | Where-Object { $_.Name -eq 'RSAT-NetworkController' } + if ($isAvailable) { + Write-Host "Installing RSAT-NetworkController on $VMName" + 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 + Write-Host "Installing Active Directory RSAT Tools on $VMName" + Install-WindowsFeature -Name RSAT-ADDS -IncludeAllSubFeature -IncludeManagementTools | Out-Null + Write-Host "Installing Failover Clustering RSAT Tools on $VMName" + Install-WindowsFeature -Name RSAT-Clustering-Mgmt, RSAT-Clustering-PowerShell -IncludeAllSubFeature -IncludeManagementTools | Out-Null + Write-Host "Installing DNS Server RSAT Tools on $VMName" + Install-WindowsFeature -Name RSAT-DNS-Server -IncludeAllSubFeature -IncludeManagementTools | Out-Null + Install-RemoteAccess -VPNType RoutingOnly | Out-Null + + # 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] +Signature="`$Windows NT$" + +[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 +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" +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)" +%szOID_ENHANCED_KEY_USAGE% = "{text}%szOID_PKIX_KP_SERVER_AUTH%,%szOID_PKIX_KP_CLIENT_AUTH%" +[RequestAttributes] +CertificateTemplate= WebServer +"@ + + New-Item C:\WACCert -ItemType Directory -Force | Out-Null + 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" + Invoke-Command -ComputerName $VMName -ConfigurationName 'Microsoft.SDNNested' -Credential $domainCred -ArgumentList $HCIBoxConfig -ScriptBlock { + $HCIBoxConfig = $args[0] + # Get the CA Name + $CertDump = certutil -dump + $ca = ((((($CertDump.Replace('`', "")).Replace("'", "")).Replace(":", "=")).Replace('\', "")).Replace('"', "") | ConvertFrom-StringData).Name + $CertAuth = $HCIBOXConfig.SDNDomainFQDN + '\' + $ca + + Write-Host "CA is: $ca" + Write-Host "Certificate Authority is: $CertAuth" + Write-Host "Certdump is $CertDump" + + # 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 -accept WACCert.cer + certutil -q -store my + + Set-Location 'C:\' + Remove-Item C:\WACCert -Recurse -Force + + } -Authentication Credssp + + # Install Windows Admin Center + $pfxThumbPrint = (Get-ChildItem -Path Cert:\LocalMachine\my | Where-Object { $_.FriendlyName -match "HCIBox Windows Admin Cert" }).Thumbprint + Write-Host "Thumbprint: $pfxThumbPrint" + Write-Host "WACPort: $($HCIBoxConfig.WACport)" + $WindowsAdminCenterGateway = "https://$($HCIBoxConfig.WACVMName)." + $HCIBOXConfig.SDNDomainFQDN + Write-Host $WindowsAdminCenterGateway + Write-Host "Installing and Configuring Windows Admin Center" + $PathResolve = Resolve-Path -Path 'C:\Windows Admin Center\*.msi' + $arguments = "/qn /L*v C:\log.txt SME_PORT=$($HCIBoxConfig.WACport) SME_THUMBPRINT=$pfxThumbPrint SSL_CERTIFICATE_OPTION=installed SME_URL=$WindowsAdminCenterGateway" + Start-Process -FilePath $PathResolve -ArgumentList $arguments -PassThru | Wait-Process + + # Install Chocolatey + Write-Host "Installing Chocolatey" + Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) + Start-Sleep -Seconds 10 + + # Install Azure PowerShell + 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" + + # Create Shortcut for Failover-Cluster Manager + Write-Host "Creating Shortcut for Failover-Cluster Manager" + Copy-Item -Path "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools\Failover Cluster Manager.lnk" -Destination "C:\Users\Public\Desktop" + + # Create Shortcut for DNS + Write-Host "Creating Shortcut for DNS Manager" + Copy-Item -Path "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools\DNS.lnk" -Destination "C:\Users\Public\Desktop" + + # 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 + + # 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 + + # Install Kubectl + Write-Host 'Installing kubectl' + $expression = "choco install kubernetes-cli -y --limit-output" + Invoke-Expression $expression + + # Create a shortcut for Windows Admin Center + Write-Host "Creating Shortcut for Windows Admin Center" + if ($HCIBoxConfig.WACport -ne "443") { $TargetPath = "https://$($HCIBoxConfig.WACVMName)." + $HCIBoxConfig.SDNDomainFQDN + ":" + $HCIBoxConfig.WACport } + else { $TargetPath = "https://$($HCIBoxConfig.WACVMName)." + $HCIBoxConfig.SDNDomainFQDN } + $ShortcutFile = "C:\Users\Public\Desktop\Windows Admin Center.url" + $WScriptShell = New-Object -ComObject WScript.Shell + $Shortcut = $WScriptShell.CreateShortcut($ShortcutFile) + $Shortcut.TargetPath = $TargetPath + $Shortcut.Save() + + # Disable Edge 'First Run' Setup + $edgePolicyRegistryPath = 'HKLM:SOFTWARE\Policies\Microsoft\Edge' + $desktopSettingsRegistryPath = 'HKCU:SOFTWARE\Microsoft\Windows\Shell\Bags\1\Desktop' + $firstRunRegistryName = 'HideFirstRunExperience' + $firstRunRegistryValue = '0x00000001' + $savePasswordRegistryName = 'PasswordManagerEnabled' + $savePasswordRegistryValue = '0x00000000' + $autoArrangeRegistryName = 'FFlags' + $autoArrangeRegistryValue = '1075839525' + + if (-NOT (Test-Path -Path $edgePolicyRegistryPath)) { + New-Item -Path $edgePolicyRegistryPath -Force | Out-Null + } + if (-NOT (Test-Path -Path $desktopSettingsRegistryPath)) { + New-Item -Path $desktopSettingsRegistryPath -Force | Out-Null + } + + 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 + } + } +} + +function Test-InternetConnect { + $testIP = $HCIBoxConfig.natDNS + $ErrorActionPreference = "Stop" + $intConnect = Test-NetConnection -ComputerName $testip -Port 53 + + if (!$intConnect.TcpTestSucceeded) { + throw "Unable to connect to DNS by pinging $($HCIBoxConfig.natDNS) - Network access to this IP is required." + } +} + +function Set-HostNAT { + param ( + $HCIBoxConfig + ) + + $switchExist = Get-NetAdapter | Where-Object { $_.Name -match $HCIBoxConfig.natHostVMSwitchName } + if (!$switchExist) { + Write-Host "Creating NAT Switch: $($HCIBoxConfig.natHostVMSwitchName)" + # Create Internal VM Switch for NAT + New-VMSwitch -Name $HCIBoxConfig.natHostVMSwitchName -SwitchType Internal | Out-Null + + Write-Host "Applying IP Address to NAT Switch: $($HCIBoxConfig.natHostVMSwitchName)" + # Apply IP Address to new Internal VM Switch + $intIdx = (Get-NetAdapter | Where-Object { $_.Name -match $HCIBoxConfig.natHostVMSwitchName }).ifIndex + $natIP = $HCIBoxConfig.natHostSubnet.Replace("0/24", "1") + New-NetIPAddress -IPAddress $natIP -PrefixLength 24 -InterfaceIndex $intIdx | Out-Null + + # Create NetNAT + Write-Host "Creating new Net NAT" + New-NetNat -Name $HCIBoxConfig.natHostVMSwitchName -InternalIPInterfaceAddressPrefix $HCIBoxConfig.natHostSubnet | Out-Null + } +} + +function Set-HCIDeployPrereqs { + param ( + $HCIBoxConfig, + [PSCredential]$localCred, + [PSCredential]$domainCred + ) + Invoke-Command -VMName $HCIBoxConfig.MgmtHostConfig.Hostname -Credential $localCred -ScriptBlock { + $HCIBoxConfig = $using:HCIBoxConfig + $localCred = $using:localcred + $domainCred = $using:domainCred + Invoke-Command -VMName $HCIBoxConfig.DCName -Credential $domainCred -ArgumentList $HCIBoxConfig -ScriptBlock { + $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('.') + $ouName = "OU=$($HCIBoxConfig.LCMADOUName)" + foreach ($name in $domainName) { + $ouName += ",DC=$name" + } + $nodes = @() + foreach ($node in $HCIBoxConfig.NodeHostConfig) { + $nodes += $node.Hostname.ToString() + } + Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10)) + 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] + $tenantId = $args[1] + $clientId = $args[2] + $clientSecret = $args[3] + $resourceGroup = $args[4] + $location = $args[5] + + function ConvertFrom-SecureStringToPlainText { + param ( + [Parameter(Mandatory = $true)] + [System.Security.SecureString]$SecureString + ) + + $Ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString) + try { + return [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($Ptr) + } + finally { + [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($Ptr) + } + } + + # 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-Module AzsHCI.ARCinstaller -Force + + #Install required PowerShell modules in your node for registration + Install-Module Az.Accounts -Force + Install-Module Az.ConnectedMachine -Force + Install-Module Az.Resources -Force + $azureAppCred = (New-Object System.Management.Automation.PSCredential $clientId, (ConvertTo-SecureString -String $clientSecret -AsPlainText -Force)) + Connect-AzAccount -ServicePrincipal -SubscriptionId $subId -TenantId $tenantId -Credential $azureAppCred + $armtoken = ConvertFrom-SecureStringToPlainText -SecureString ((Get-AzAccessToken -AsSecureString).Token) + + # 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 -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 +$HostVMPath = $HCIBoxConfig.HostVMPath +$InternalSwitch = $HCIBoxConfig.InternalSwitch +$natDNS = $HCIBoxConfig.natDNS +$natSubnet = $HCIBoxConfig.natSubnet + +Import-Module Hyper-V + +$VerbosePreference = "SilentlyContinue" +$ErrorActionPreference = "Stop" +$WarningPreference = "Continue" +$ProgressPreference = "SilentlyContinue" + +# Create paths +foreach ($path in $HCIBoxConfig.Paths.GetEnumerator()) { + Write-Host "Creating $($path.Key) path at $($path.Value)" + New-Item -Path $path.Value -ItemType Directory -Force | Out-Null +} + +# Download HCIBox VHDs +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" +$hash = Get-Content -Path "$($HCIBoxConfig.Paths.VHDDir)\AZSHCI.sha256" +if ($checksum.Hash -eq $hash) { + Write-Host "AZSCHI.vhdx has valid checksum. Continuing..." +} +else { + Write-Error "AZSCHI.vhdx is corrupt. Aborting deployment. Re-run C:\HCIBox\HCIBoxLogonScript.ps1 to retry" + 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" } +$checksum = Get-FileHash -Path "$($HCIBoxConfig.Paths.VHDDir)\GUI.vhdx" +$hash = Get-Content -Path "$($HCIBoxConfig.Paths.VHDDir)\GUI.sha256" +if ($checksum.Hash -eq $hash) { + Write-Host "GUI.vhdx has valid checksum. Continuing..." +} +else { + Write-Error "GUI.vhdx is corrupt. Aborting deployment. Re-run C:\HCIBox\HCIBoxLogonScript.ps1 to retry" + 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) +# Move-Item -Path "$($HCIBoxConfig.Paths.VHDDir)\livecd.ubuntu-desktop-hyperv.vhdx" -Destination "$($HCIBoxConfig.Paths.VHDDir)\Ubuntu.vhdx" + +# Set credentials +$localCred = new-object -typename System.Management.Automation.PSCredential ` + -argumentlist "Administrator", (ConvertTo-SecureString $HCIBoxConfig.SDNAdminPassword -AsPlainText -Force) + +$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/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 +Enable-WSManCredSSP -Role Client -DelegateComputer "*.$($HCIBoxConfig.SDNDomainFQDN)" -Force + +############################################################################### +# Configure Hyper-V host +############################################################################### +Write-Host "Checking internet connectivity" +Test-InternetConnect + +Write-Host "Creating Internal Switch" +New-InternalSwitch -HCIBoxConfig $HCIBoxConfig + +Write-Host "Creating NAT Switch" +Set-HostNAT -HCIBoxConfig $HCIBoxConfig + +Write-Host "Configuring HCIBox-Client Hyper-V host" +Set-VMHost -VirtualHardDiskPath $HostVMPath -VirtualMachinePath $HostVMPath -EnableEnhancedSessionMode $true + +Write-Host "Copying VHDX Files to Host virtualization drive" +$guipath = "$HostVMPath\GUI.vhdx" +$hcipath = "$HostVMPath\AzSHCI.vhdx" +Copy-Item -Path $HCIBoxConfig.guiVHDXPath -Destination $guipath -Force | Out-Null +Copy-Item -Path $HCIBoxConfig.azSHCIVHDXPath -Destination $hcipath -Force | Out-Null + +################################################################################ +# Create the three nested Virtual Machines +################################################################################ +# First create the Management VM (AzSMGMT) +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/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/11] Starting VMs..." -ForegroundColor Green +Write-Host "Starting VM: $($HCIBoxConfig.MgmtHostConfig.Hostname)" +Start-VM -Name $HCIBoxConfig.MgmtHostConfig.Hostname +foreach ($VM in $HCIBoxConfig.NodeHostConfig) { + Write-Host "Starting VM: $($VM.Hostname)" + Start-VM -Name $VM.Hostname +} + +####################################################################################### +# Prep the virtualization environment +####################################################################################### +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 + +# Create NAT Virtual Switch on AzSMGMT +New-NATSwitch -HCIBoxConfig $HCIBoxConfig + +# Configure fabric network on AzSMGMT +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/11] Build router VM..." -ForegroundColor Green +New-RouterVM -HCIBoxConfig $HCIBoxConfig -localCred $localCred + +# Provision Domain controller VM on AzSMGMT +Write-Host "[Build cluster - Step 8/11] Building Domain Controller VM..." -ForegroundColor Green +New-DCVM -HCIBoxConfig $HCIBoxConfig -localCred $localCred -domainCred $domainCred + +# Provision Admincenter VM +# Write-Host "[Build cluster - Step 9/12] Building Windows Admin Center gateway server VM... (skipping step)" -ForegroundColor Green +#New-AdminCenterVM -HCIBoxConfig $HCIBoxConfig -localCred $localCred -domainCred $domainCred + +####################################################################################### +# Prepare the cluster for deployment +####################################################################################### +# New-S2DCluster -HCIBoxConfig $HCIBoxConfig -domainCred $domainCred +Write-Host "[Build cluster - Step 9/11] Preparing HCI cluster Azure deployment..." -ForegroundColor Green +Set-HCIDeployPrereqs -HCIBoxConfig $HCIBoxConfig -localCred $localCred -domainCred $domainCred + +& "$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 +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 + +#endregion \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/PSProfile.ps1 b/azure_jumpstart_hcibox/artifacts/PowerShell/PSProfile.ps1 similarity index 100% rename from azure_jumpstart_hcibox/artifacts/PSProfile.ps1 rename to azure_jumpstart_hcibox/artifacts/PowerShell/PSProfile.ps1 diff --git a/azure_jumpstart_hcibox/artifacts/Register-AzSHCI.ps1 b/azure_jumpstart_hcibox/artifacts/Register-AzSHCI.ps1 deleted file mode 100644 index 080fbedf0c..0000000000 --- a/azure_jumpstart_hcibox/artifacts/Register-AzSHCI.ps1 +++ /dev/null @@ -1,69 +0,0 @@ -$WarningPreference = "SilentlyContinue" -$ErrorActionPreference = "Stop" -$ProgressPreference = 'SilentlyContinue' -# Set paths -$Env:HCIBoxDir = "C:\HCIBox" -$Env:HCIBoxLogsDir = "C:\HCIBox\Logs" -$Env:HCIBoxVMDir = "C:\HCIBox\Virtual Machines" -$Env:HCIBoxKVDir = "C:\HCIBox\KeyVault" -$Env:HCIBoxGitOpsDir = "C:\HCIBox\GitOps" -$Env:HCIBoxIconDir = "C:\HCIBox\Icons" -$Env:HCIBoxVHDDir = "C:\HCIBox\VHD" -$Env:HCIBoxSDNDir = "C:\HCIBox\SDN" -$Env:HCIBoxWACDir = "C:\HCIBox\Windows Admin Center" -$Env:agentScript = "C:\HCIBox\agentScript" -$Env:ToolsDir = "C:\Tools" -$Env:tempDir = "C:\Temp" -$Env:VMPath = "C:\VMs" - -Start-Transcript -Path $Env:HCIBoxLogsDir\Register-AzSHCI.log - -# Import Configuration Module -$ConfigurationDataFile = "$Env:HCIBoxDir\HCIBox-Config.psd1" -$SDNConfig = Import-PowerShellDataFile -Path $ConfigurationDataFile -$user = "jumpstart.local\administrator" -$password = ConvertTo-SecureString -String $SDNConfig.SDNAdminPassword -AsPlainText -Force -$adcred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user, $password - -Write-Host "Installing Required Modules" -ForegroundColor Green -BackgroundColor Black -Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Install-WindowsFeature -name RSAT-Clustering-Powershell -$ModuleNames = "Az.Accounts", "Az.stackhci" -foreach ($ModuleName in $ModuleNames) { - Install-Module -Name $ModuleName -Force -} - -# Required for CLI commands -Write-Host "Az Login" -$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 - -#Register the Cluster -Write-Host "Registering the Cluster" -ForegroundColor Green -BackgroundColor Black -$armtoken = Get-AzAccessToken -$clustername = 'HCIBox-Cluster' -$azureLocation = 'eastus' -Register-AzStackHCI -SubscriptionId $env:subscriptionId -ComputerName $SDNConfig.HostList[0] -AccountId $env:spnClientID -ArmAccessToken $armtoken.Token -Credential $adcred -Region $azureLocation -ResourceName $clustername -ResourceGroupName $env:resourceGroup -Move-Item -Path RegisterHCI_* -Destination $Env:HCIBoxLogsDir\RegisterHCI_PS_Output.log - -Write-Host "$clustername successfully registered as Az Stack HCI cluster resource in Azure" - -# Set up cluster cloud witness -Connect-AzAccount -ServicePrincipal -Subscription $env:subscriptionId -Tenant $env:spnTenantId -Credential $azureAppCred -$storageKey = Get-AzStorageAccountKey -Name $env:stagingStorageAccountName -ResourceGroup $env:resourceGroup -$saName = $env:stagingStorageAccountName -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - Set-ClusterQuorum -Cluster "hciboxcluster" -CloudWitness -AccountName $using:saName -AccessKey $using:storageKey[0].value -} - -# Install Az CLI and extensions on each node -Invoke-Command -VMName $SDNConfig.HostList -Credential $adcred -ScriptBlock { - Write-Verbose "Installing Az CLI" - $ProgressPreference = "SilentlyContinue" - Invoke-WebRequest -Uri https://aka.ms/installazurecliwindowsx64 -OutFile .\AzureCLI.msi; - Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; - Start-Sleep -Seconds 30 - $ProgressPreference = "Continue" -} - -Stop-Transcript \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/SDN/CertHelpers.ps1 b/azure_jumpstart_hcibox/artifacts/SDN/CertHelpers.ps1 deleted file mode 100644 index 94e0acb7cb..0000000000 --- a/azure_jumpstart_hcibox/artifacts/SDN/CertHelpers.ps1 +++ /dev/null @@ -1,167 +0,0 @@ -# -------------------------------------------------------------- -# Copyright � Microsoft Corporation. All Rights Reserved. -# Microsoft Corporation (or based on where you live, one of its affiliates) licenses this sample code for your internal testing purposes only. -# Microsoft provides the following sample code AS IS without warranty of any kind. The sample code arenot supported under any Microsoft standard support program or services. -# Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. -# The entire risk arising out of the use or performance of the sample code remains with you. -# In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the code be liable for any damages whatsoever -# (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) -# arising out of the use of or inability to use the sample code, even if Microsoft has been advised of the possibility of such damages. -# --------------------------------------------------------------- - - -Function PrettyTime() { - return "[" + (Get-Date -Format o) + "]" -} -Function Log($msg) { - Write-Verbose $( $(PrettyTime) + " " + $msg) -Verbose -} - -function GetSubjectName([bool] $UseManagementAddress) { - if ($UseManagementAddress -eq $true) - { - # When IP Address is specified, we are currently looking just for IPv4 corpnet ip address - # In the final design, only computer names will be used for subject names - $corpIPAddresses = get-netIpAddress -AddressFamily IPv4 -PrefixOrigin Dhcp -ErrorAction Ignore - if ($corpIPAddresses -ne $null -and $corpIPAddresses[0] -ne $null) - { - $mesg = [System.String]::Format("Using IP Address {0} for certificate subject name", $corpIPAddresses[0].IPAddress); - Log $mesg - return $corpIPAddresses[0].IPAddress - } - else - { - Log "Unable to find management IP address "; - } - } - - $hostFqdn = [System.Net.Dns]::GetHostByName(($env:computerName)).HostName; - $mesg = [System.String]::Format("Using computer name {0} for certificate subject name", $hostFqdn); - Log $mesg - return $hostFqdn ; -} -function GenerateSelfSignedCertificate([string] $subjectName) { - $cryptographicProviderName = "Microsoft Base Cryptographic Provider v1.0"; - [int] $privateKeyLength = 1024; - $sslServerOidString = "1.3.6.1.5.5.7.3.1"; - $sslClientOidString = "1.3.6.1.5.5.7.3.2"; - [int] $validityPeriodInYear = 5; - - $name = new-object -com "X509Enrollment.CX500DistinguishedName.1" - $name.Encode("CN=" + $SubjectName, 0) - - $mesg = [System.String]::Format("Generating certificate with subject Name {0}", $subjectName); - Log $mesg - - - #Generate Key - $key = new-object -com "X509Enrollment.CX509PrivateKey.1" - $key.ProviderName = $cryptographicProviderName - $key.KeySpec = 1 #X509KeySpec.XCN_AT_KEYEXCHANGE - $key.Length = $privateKeyLength - $key.MachineContext = 1 - $key.ExportPolicy = 0x2 #X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG - $key.Create() - - #Configure Eku - $serverauthoid = new-object -com "X509Enrollment.CObjectId.1" - $serverauthoid.InitializeFromValue($sslServerOidString) - $clientauthoid = new-object -com "X509Enrollment.CObjectId.1" - $clientauthoid.InitializeFromValue($sslClientOidString) - $ekuoids = new-object -com "X509Enrollment.CObjectIds.1" - $ekuoids.add($serverauthoid) - $ekuoids.add($clientauthoid) - $ekuext = new-object -com "X509Enrollment.CX509ExtensionEnhancedKeyUsage.1" - $ekuext.InitializeEncode($ekuoids) - - # Set the hash algorithm to sha512 instead of the default sha1 - $hashAlgorithmObject = New-Object -ComObject X509Enrollment.CObjectId - $hashAlgorithmObject.InitializeFromAlgorithmName( $ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID, $ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY, $AlgorithmFlags.AlgorithmFlagsNone, "SHA512") - - - #Request Cert - $cert = new-object -com "X509Enrollment.CX509CertificateRequestCertificate.1" - - $cert.InitializeFromPrivateKey(2, $key, "") - $cert.Subject = $name - $cert.Issuer = $cert.Subject - $cert.NotBefore = (get-date).ToUniversalTime() - $cert.NotAfter = $cert.NotBefore.AddYears($validityPeriodInYear); - $cert.X509Extensions.Add($ekuext) - $cert.HashAlgorithm = $hashAlgorithmObject - $cert.Encode() - - $enrollment = new-object -com "X509Enrollment.CX509Enrollment.1" - $enrollment.InitializeFromRequest($cert) - $certdata = $enrollment.CreateRequest(0) - $enrollment.InstallResponse(2, $certdata, 0, "") - - Log "Successfully added cert to local machine store"; -} -function GivePermissionToNetworkService($targetCert) { - $targetCertPrivKey = $targetCert.PrivateKey - $privKeyCertFile = Get-Item -path "$ENV:ProgramData\Microsoft\Crypto\RSA\MachineKeys\*" | where {$_.Name -eq $targetCertPrivKey.CspKeyContainerInfo.UniqueKeyContainerName} - $privKeyAcl = Get-Acl $privKeyCertFile - $permission = "NT AUTHORITY\NETWORK SERVICE","Read","Allow" - $accessRule = new-object System.Security.AccessControl.FileSystemAccessRule $permission - $privKeyAcl.AddAccessRule($accessRule) - Set-Acl $privKeyCertFile.FullName $privKeyAcl -} -Function AddCertToLocalMachineStore($certFullPath, $storeName, $securePassword) { - $rootName = "LocalMachine" - - # create a representation of the certificate file - $certificate = new-object System.Security.Cryptography.X509Certificates.X509Certificate2 - if($securePassword -eq $null) - { - $certificate.import($certFullPath) - } - else - { - # https://msdn.microsoft.com/library/system.security.cryptography.x509certificates.x509keystorageflags(v=vs.110).aspx - $certificate.import($certFullPath, $securePassword, "MachineKeySet,PersistKeySet") - } - - # import into the store - $store = new-object System.Security.Cryptography.X509Certificates.X509Store($storeName, $rootName) - $store.open("MaxAllowed") - $store.add($certificate) - $store.close() -} -Function GetSubjectFqdnFromCertificatePath($certFullPath) { - # create a representation of the certificate file - $certificate = new-object System.Security.Cryptography.X509Certificates.X509Certificate2 - $certificate.import($certFullPath) - return GetSubjectFqdnFromCertificate $certificate ; -} -Function GetSubjectFqdnFromCertificate([System.Security.Cryptography.X509Certificates.X509Certificate2] $certificate) { - $mesg = [System.String]::Format("Parsing Subject Name {0} to get Subject Fqdn ", $certificate.Subject) - Log $mesg - $subjectFqdn = $certificate.Subject.Split('=')[1] ; - return $subjectFqdn; -} -Function GetCertificate($cn, [bool]$generateCert=$false) { - $cert = get-childitem "Cert:\localmachine\my" | where {$_.Subject.ToUpper().StartsWith("CN=$cn")} - - if (($generateCert -eq $true) -and ($cert -eq $null)) { - $mesg = [System.String]::Format("Generating Certificate..."); - Log $mesg - GenerateSelfSignedCertificate $cn - $cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where {$_.Subject.ToUpper().StartsWith("CN=$cn")} - } - - # adding check for cert - if ($cert -eq $null) { - $mesg = [System.String]::Format("Certificate was null, waiting 30 secs and retrying, CN= {0}", $cn); - Log $mesg - Sleep 30 - $cert = get-childitem "Cert:\localmachine\my" | where {$_.Subject.ToUpper().StartsWith("CN=$cn")} - - #last chance - if ($cert -eq $null) { - throw "Certificate not available..." - } - } - return $cert; -} - diff --git a/azure_jumpstart_hcibox/artifacts/SDN/NetworkControllerRESTWrappers.ps1 b/azure_jumpstart_hcibox/artifacts/SDN/NetworkControllerRESTWrappers.ps1 deleted file mode 100644 index 7dc2053674..0000000000 --- a/azure_jumpstart_hcibox/artifacts/SDN/NetworkControllerRESTWrappers.ps1 +++ /dev/null @@ -1,2620 +0,0 @@ -# -------------------------------------------------------------- -# Copyright © Microsoft Corporation. All Rights Reserved. -# Microsoft Corporation (or based on where you live, one of its affiliates) licenses this sample code for your internal testing purposes only. -# Microsoft provides the following sample code AS IS without warranty of any kind. The sample code arenot supported under any Microsoft standard support program or services. -# Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. -# The entire risk arising out of the use or performance of the sample code remains with you. -# In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the code be liable for any damages whatsoever -# (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) -# arising out of the use of or inability to use the sample code, even if Microsoft has been advised of the possibility of such damages. -# --------------------------------------------------------------- -[cmdletbinding()] -Param( - [Parameter(mandatory=$false)] - [String] $ComputerName=$null, - [Parameter(mandatory=$false)] - [String] $Username=$null, - [Parameter(mandatory=$false)] - [String] $Password=$null, - [Parameter(mandatory=$false)] - [PSCredential] $Credential=[System.Management.Automation.PSCredential]::Empty - ) - -$script:urlroot = "https://" -#region Private script variables -$script:NetworkControllerRestIP = $ComputerName -if (![String]::isnullorempty($Username)) { - $securepass = convertto-securestring $Password -asplaintext -force - $script:NetworkControllerCred = new-object -typename System.Management.Automation.PSCredential -argumentlist $Username,$securepass -} else { - $script:NetworkControllerCred = $Credential -} -#endregion - -#region some IPv4 address related helper functions - -function Convert-IPv4StringToInt { - param([string] $addr) - - $ip = $null - $valid = [System.Net.IPAddress]::TryParse($addr, [ref]$ip) - if (!$valid -or $ip.AddressFamily -ne [System.Net.Sockets.AddressFamily]::InterNetwork) { - throw "$addr is not a valid IPv4 address." - } - - $sp = $addr.Split(".", 4) - $bits = [System.Convert]::ToInt64($sp[0]) - $bits = $bits -shl 8 - $bits += [System.Convert]::ToInt64($sp[1]) - $bits = $bits -shl 8 - $bits += [System.Convert]::ToInt64($sp[2]) - $bits = $bits -shl 8 - $bits += [System.Convert]::ToInt64($sp[3]) - - $bits -} - -function Convert-IPv4IntToString { - param([Int64] $addr) - - "{0}.{1}.{2}.{3}" -f (($addr -shr 24) -band 0xff), (($addr -shr 16) -band 0xff), (($addr -shr 8) -band 0xff), ($addr -band 0xff ) - -} - -function IsIpPoolRangeValid { - param( - [Parameter(Mandatory=$true)][string]$startIp, - [Parameter(Mandatory=$true)][string]$endIp - ) - - $startIpInt = Convert-IPv4StringToInt -addr $startIp - $endIpInt = Convert-IPv4StringToInt -addr $endIp - - if( $startIpInt -gt $endIpInt) { - return $false - } - - return $true -} - -function IsIpWithinPoolRange { - param( - [Parameter(Mandatory=$true)][string]$targetIp, - [Parameter(Mandatory=$true)][string]$startIp, - [Parameter(Mandatory=$true)][string]$endIp - ) - - $startIpInt = Convert-IPv4StringToInt -addr $startIp - $endIpInt = Convert-IPv4StringToInt -addr $endIp - $targetIpInt = Convert-IPv4StringToInt -addr $targetIp - - if (($targetIpInt -ge $startIpInt) -and ($targetIpInt -le $endIpInt)) { - return $true - } - - return $false -} - -#endregion - -#region Invoke command wrapper -function Invoke-CommandVerify -{ - param ( - [Parameter(mandatory=$true)] - [ValidateNotNullOrEmpty()][string[]]$ComputerName, - [Parameter(mandatory=$true)] - [ValidateNotNullOrEmpty()][PSCredential]$Credential, - [Parameter(mandatory=$true)] - [ValidateNotNullOrEmpty()][ScriptBlock]$ScriptBlock, - [Parameter(mandatory=$false)] - [Object[]]$ArgumentList = $null, - [Parameter(mandatory=$false)] - [int]$RetryCount = 3 - ) - - # find number of targets - $numberTargets = $ComputerName.Count - - if ($numberTargets -eq 0) - { - throw "Please specify >= 1 target" - } - - do - { - # create sessions - $sessions = New-PSSession -ComputerName $ComputerName -Credential $Credential -ErrorAction Ignore - - # ensure number of sessions match target number - if ($sessions.Count -eq $numberTargets) - { - $readyActive = 0 - # ensure that all the sessions are active - foreach ($session in $sessions) - { - if ( ($session.State -eq "Opened") -and ($session.Availability -eq "Available") ) - { - $readyActive ++ - } - else - { - Write-Verbose "Session: $($session.Name) is $($session.State) and $($session.Availability)" - } - } - - if ($readyActive -eq $numberTargets) - { - Write-Verbose "All sessions are active and ready for $($ComputerName)" - break - } - } - else - { - Write-Verbose "Different number of Session: $($sessions.Count) and $($numberTargets)" - } - - # close any active sessions - Write-Verbose "Not all sessions are active and ready for $($ComputerName), retrying $($RetryCount)" - if ($sessions) - { - $sessions | Remove-PSSession -ErrorAction Ignore - } - $RetryCount -- - $session = $null - Sleep 10 - } while ($RetryCount -gt 0) - - if ($sessions -eq $null) - { - Write-Verbose "Cannot establish all PS sessions for $($ComputerName)" - throw "Cannot establish all PS sessions for $($ComputerName)" - } - - Write-Verbose "Invoking command" - if ($ArgumentList) - { - $returnObject = Invoke-Command -Session $sessions -Argumentlist $ArgumentList -ScriptBlock $ScriptBlock - } - else { - $returnObject = Invoke-Command -Session $sessions -ScriptBlock $ScriptBlock - } - - Write-Verbose "Closing all sessions" - $sessions | Remove-PSSession -ErrorAction Ignore - - return $returnObject -} - -#endregion - -#region Private JSON helper functions - -function Invoke-WebRequestWithRetries { - param( - [System.Collections.IDictionary] $Headers, - [string] $ContentType, - [Microsoft.PowerShell.Commands.WebRequestMethod] $Method, - [System.Uri] $Uri, - [object] $Body, - [Switch] $DisableKeepAlive, - [Switch] $UseBasicParsing, - [System.Management.Automation.PSCredential] $Credential, - [System.Management.Automation.Runspaces.PSSession] $RemoteSession, - [Parameter(mandatory=$false)] - [bool] $shouldRetry = $true - ) - - $params = @{ - 'Headers'=$headers; - 'ContentType'=$content; - 'Method'=$method; - 'uri'=$uri - } - - if($Body -ne $null) { - $params.Add('Body', $Body) - } - if($DisableKeepAlive.IsPresent) { - $params.Add('DisableKeepAlive', $true) - } - if($UseBasicParsing.IsPresent) { - $params.Add('UseBasicParsing', $true) - } - if($Credential -ne [System.Management.Automation.PSCredential]::Empty -and $Credential -ne $null) { - $params.Add('Credential', $Credential) - } - - $retryIntervalInSeconds = 30 - $maxRetry = 6 - $retryCounter = 0 - - do { - try { - if($RemoteSession -eq $null) { - $result = Invoke-WebRequest @params - } - else { - $result = Invoke-Command -Session $RemoteSession -ScriptBlock { - Invoke-WebRequest @using:params - } - } - break - } - catch { - Write-Verbose "Invoke-WebRequestWithRetries: $($Method) Exception: $_" - Write-Verbose "Invoke-WebRequestWithRetries: $($Method) Exception: $($_.Exception.Response)" - - if ($_.Exception.Response.statuscode -eq "NotFound") { - return $null - } - - $retryCounter++ - if($retryCounter -le $maxRetry) { - - Write-verbose "Invoke-WebRequestWithRetries: retry this operation in $($retryIntervalInSeconds) seconds. Retry count: $($retryCounter)." - sleep -Seconds $retryIntervalInSeconds - } - else { - # last retry still fails, so throw the exception - throw $_ - } - } - } while ($shouldRetry -and ($retryCounter -le $maxRetry)) - - return $result -} - -function JSONPost { - param( - [Parameter(position=0,mandatory=$true,ParameterSetName="WithCreds")] - [String] $NetworkControllerRestIP=$script:NetworkControllerRestIP, - [Parameter(position=1,mandatory=$true,ParameterSetName="WithCreds")] - [Parameter(position=0,mandatory=$true,ParameterSetName="CachedCreds")] - [String] $path, #starts with object, does not include server, i.e. "/Credentials" - [Parameter(position=2,mandatory=$true,ParameterSetName="WithCreds")] - [Parameter(position=1,mandatory=$true,ParameterSetName="CachedCreds")] - [Object] $bodyObject, - [Parameter(position=3,mandatory=$true,ParameterSetName="WithCreds")] - [Object] $credential=$script:NetworkControllerCred, - [Parameter(position=4,mandatory=$false,ParameterSetName="WithCreds")] - [String] $computerName=$null - ) - - if ($NetworkControllerRestIP -eq "") - { - write-error "Network controller REST IP not specified. You must first call Set-NCConnection." - return - } - - $headers = @{"Accept"="application/json"} - $content = "application/json; charset=UTF-8" - $uriRoot = "$($script:urlroot)$NetworkControllerRestIP/Networking/v1" - $timeout = 10 - - $method = "Put" - $uri = "$uriRoot$path/$($bodyObject.resourceId)" - $body = convertto-json $bodyObject -Depth 100 - $pssession = $null - - Write-Verbose "JSON Put [$path]" - if ($path -notlike '/Credentials*') { - Write-Verbose "Payload follows:" - Write-Verbose $body - } - - try { - # $computerName is here to workaround product limitation for PUT of LoadBalancer, which is > 35KB and must be done from the REST hosting NC Vm. - if (-not $computerName) { - if ($credential -eq [System.Management.Automation.PSCredential]::Empty -or $credential -eq $null) { - Invoke-WebRequestWithRetries -Headers $headers -ContentType $content -Method $method -Uri $uri -Body $body -DisableKeepAlive -UseBasicParsing | out-null - } else { - Invoke-WebRequestWithRetries -Headers $headers -ContentType $content -Method $method -Uri $uri -Body $body -DisableKeepAlive -UseBasicParsing -Credential $credential | out-null - } - } - else { - $pssession = new-pssession -ComputerName $computerName -Credential $credential - Invoke-WebRequestWithRetries -Headers $headers -ContentType $content -Method $method -Uri $uri -Body $body -DisableKeepAlive -UseBasicParsing -Credential $credential -RemoteSession $pssession | out-null - } - } - catch { - Write-Verbose "PUT Exception: $_" - Write-Verbose "PUT Exception: $($_.Exception.Response)" - } - finally { - if($pssession -ne $null) - { - Remove-PSSession $pssession - } - } -} - -function JSONGet { - param( - [Parameter(mandatory=$true)] - [String] $NetworkControllerRestIP, - [Parameter(mandatory=$true)] - [String] $path, #starts with object and may include resourceid, does not include server, i.e. "/Credentials" or "/Credentials/{1234- - [Parameter(mandatory=$false)] - [Switch] $WaitForUpdate, - [Switch] $Silent, - [PSCredential] $credential - ) - - if ($NetworkControllerRestIP -eq "") - { - write-error "Network controller REST IP not specified. You must first call Set-NCConnection." - return - } - - $headers = @{"Accept"="application/json"} - $content = "application/json; charset=UTF-8" - $uriRoot = "$($script:urlroot)$NetworkControllerRestIP/Networking/v1" - - $method = "Get" - $uri = "$uriRoot$path" - - if (!$Silent) { - Write-Verbose "JSON Get [$path]" - } - - try { - $NotFinished = $true - do { - if ($credential -eq [System.Management.Automation.PSCredential]::Empty -or $credential -eq $null) { - $result = Invoke-WebRequestWithRetries -Headers $headers -ContentType $content -Method $method -Uri $uri -DisableKeepAlive -UseBasicParsing - } else { - $result = Invoke-WebRequestWithRetries -Headers $headers -ContentType $content -Method $method -Uri $uri -DisableKeepAlive -UseBasicParsing -Credential $credential - } - - if($result -eq $null) { - return $null - } - - #Write-Verbose "JSON Result: $result" - $toplevel = convertfrom-json $result.Content - if ($toplevel.value -eq $null) - { - $obj = $toplevel - } else { - $obj = $toplevel.value - } - - if ($WaitForUpdate.IsPresent) { - if ($obj.properties.provisioningState -eq "Updating") - { - Write-Verbose "JSONGet: the object's provisioningState is Updating. Wait 1 second and check again." - sleep 1 #then retry - } - else - { - $NotFinished = $false - } - } - else - { - $notFinished = $false - } - } while ($NotFinished) - - if ($obj.properties.provisioningState -eq "Failed") { - write-error ("Provisioning failed: {0}`nReturned Object: {1}`nObject properties: {2}" -f $uri, $obj, $obj.properties) - } - return $obj - } - catch - { - if (!$Silent) - { - Write-Verbose "GET Exception: $_" - Write-Verbose "GET Exception: $($_.Exception.Response)" - } - return $null - } -} - -function JSONDelete { - param( - [String] $NetworkControllerRestIP, - [String] $path, - [Parameter(mandatory=$false)] - [Switch] $WaitForUpdate, - [PSCredential] $credential - ) - if ($NetworkControllerRestIP -eq "") - { - write-error "Network controller REST IP not specified. You must first call Set-NCConnection." - return - } - - $headers = @{"Accept"="application/json"} - $content = "application/json; charset=UTF-8" - $uriRoot = "$($script:urlroot)$NetworkControllerRestIP/Networking/v1" - - $method = "Delete" - $uri = "$uriRoot$path" - - Write-Verbose "JSON Delete [$path]" - try { - if ($credential -eq [System.Management.Automation.PSCredential]::Empty -or $credential -eq $null) { - Invoke-WebRequestWithRetries -Headers $headers -ContentType $content -Method $method -Uri $uri -DisableKeepAlive -UseBasicParsing - } else { - Invoke-WebRequestWithRetries -Headers $headers -ContentType $content -Method $method -Uri $uri -DisableKeepAlive -UseBasicParsing -Credential $credential - } - } - catch { - Write-Verbose "PUT Exception: $_" - Write-Verbose "PUT Exception: $($_.Exception.Response)" - } - - $maxRecheck = 100 - $currentCheck = 0 - if ($WaitForUpdate.IsPresent) { - try { - $NotFinished = $true - do { - if ($credential -eq [System.Management.Automation.PSCredential]::Empty -or $credential -eq $null) { - $result = Invoke-WebRequestWithRetries -Headers $headers -ContentType $content -Method "GET" -Uri $uri -DisableKeepAlive -UseBasicParsing - } - else { - $result = Invoke-WebRequestWithRetries -Headers $headers -ContentType $content -Method "GET" -Uri $uri -DisableKeepAlive -UseBasicParsing -Credential $credential - } - - if($result -ne $null) { - Write-Verbose "Object still exists, check again in 1 second" - sleep 1 #then retry - $currentCheck++ - } - else { - break - } - } while ($currentCheck -lt $maxRecheck) - } - catch { - if ($_.Exception.Response.statuscode -eq "NotFound") { - return - } - Write-Verbose "GET Exception: $_" - Write-Verbose "GET Exception: $($_.Exception.Response)" - } - } -} - -#endregion - -function Get-NCNetworkInterfaceResourceId -{ - param( - [Parameter(mandatory=$true)] - [String] $InstanceId - ) - if (([String]::IsNullOrEmpty($InstanceId)) -or ($InstanceId -eq "") -or ($InstanceId -eq [System.Guid]::Empty)) { - write-verbose ("Instance id ($InstanceId) either null or empty string or empty guid") - return $InstanceId - } - - write-verbose ("Searching resourceId for instance id [$InstanceId]." ) - - try - { - $interfaces = JSONGet $script:NetworkControllerRestIP "/networkinterfaces" -Credential $script:NetworkControllerCred - - if ($interfaces -ne $null) - { - foreach ($interface in $interfaces) - { - if ($interface.instanceId -eq $InstanceId) - { - return $interface.resourceId - } - } - } - } - catch - { - Write-Error "Failed with error: $_" - } - - return $null -} - -function Get-NCNetworkInterfaceInstanceId -{ - param( - [Parameter(mandatory=$true)] - [String] $ResourceId - ) - if (([String]::IsNullOrEmpty($ResourceId)) -or ($ResourceId -eq "") -or ($ResourceId -eq [System.Guid]::Empty)) { - write-verbose ("Resource id ($ResourceId) either null or empty string or empty guid") - return $ResourceId - } - - write-verbose ("Searching Instance Id for Resource Id [$ResourceId]." ) - - try - { - $interfaces = JSONGet $script:NetworkControllerRestIP "/networkinterfaces" -Credential $script:NetworkControllerCred - - if ($interfaces -ne $null) - { - foreach ($interface in $interfaces) - { - if ($interface.resourceId -eq $ResourceId) - { - return $interface.instanceId - } - } - } - } - catch - { - Write-Error "Failed with error: $_" - } - - return $null -} - -function Set-NCConnection -{ - param( - [Parameter(position=0,mandatory=$true,ParameterSetName="Credential")] - [Parameter(position=0,mandatory=$true,ParameterSetName="NoCreds")] - [string] $RestIP, - [Parameter(mandatory=$true,ParameterSetName="Credential")] - [PSCredential] $Credential=[System.Management.Automation.PSCredential]::Empty - ) - - $script:NetworkControllerRestIP = $RestIP - $script:NetworkControllerCred = $Credential -} - -function New-NCLogicalNetwork { - param( - [Parameter(mandatory=$false)] - [string] $ResourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$false)] - [object[]] $LogicalNetworkSubnets=$null, - [Switch] $EnableNetworkVirtualization - ) - - $LogicalNetwork = @{} - $LogicalNetwork.resourceId = $resourceId - $logicalNetwork.properties = @{} - - if ($LogicalNetworkSubnets -eq $null) - { - $logicalNetwork.properties.subnets = @() - } - else - { - $logicalNetwork.properties.subnets = $LogicalNetworkSubnets - } - - if ($EnableNetworkVirtualization.ispresent) { - $logicalNetwork.properties.networkVirtualizationEnabled = "True" - } else { - $logicalNetwork.properties.networkVirtualizationEnabled = "False" - } - JSONPost $script:NetworkControllerRestIP "/logicalnetworks" $logicalnetwork -Credential $script:NetworkControllerCred | out-null - return JSONGet $script:NetworkControllerRestIP "/logicalnetworks/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred - - -} -function Get-NCLogicalNetwork { - param( - [Parameter(mandatory=$false)] - [string] $ResourceID="" - ) - - return JSONGet $script:NetworkControllerRestIP "/logicalnetworks/$ResourceId" -Credential $script:NetworkControllerCred -} -function Remove-NCLogicalNetwork { - param( - [Parameter(mandatory=$true)] - [string[]] $ResourceIDs - ) - foreach ($resourceId in $ResourceIDs) { - JSONDelete $script:NetworkControllerRestIP "/logicalnetworks/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred| out-null - } -} - -function Get-NCLogicalNetworkSubnet { - param( - [Parameter(mandatory=$true)] - [object] $LogicalNetwork, - [Parameter(mandatory=$false)] - [string] $ResourceID="" - ) - - if ($resourceId -eq "") { - $uri = "/logicalnetworks/$($LogicalNetwork.ResourceId)/subnets" - } else { - $uri = "/logicalnetworks/$($LogicalNetwork.ResourceId)/subnets/$ResourceId" - } - - return JSONGet $script:NetworkControllerRestIP $uri -Credential $script:NetworkControllerCred -} - -function New-NCLogicalNetworkSubnet { -#Creates an in-memory object only. Must pass it into New-NCLogicalNetwork to persist it. - param( - [Parameter(mandatory=$false)] - [string] $ResourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$true)] - [string] $AddressPrefix, - [Parameter(mandatory=$false)] - [string[]] $DNSServers = @(), - [Parameter(mandatory=$false)] - [int] $VLANid = 0, - [Parameter(mandatory=$true)] - [string[]] $defaultGateway, - [Switch] $IsPublic - ) - $subnet = @{} - $subnet.resourceId = $ResourceID - $subnet.properties = @{} - $subnet.properties.addressPrefix = $AddressPrefix - $subnet.properties.vlanid = "$vlanid" - if ($dnsservers -ne $null -and $dnsservers.count -gt 0) { - $subnet.properties.dnsServers = $dnsServers - } - $subnet.properties.defaultGateways = $defaultGateway - $subnet.properties.IsPublic = $IsPublic.IsPresent - - return $subnet -} - -function New-NCCredential { - param( - [Parameter(mandatory=$false,parametersetname='username')] - [Parameter(mandatory=$false,parametersetname='cert')] - [Parameter(mandatory=$false,parametersetname='snmp')] - [string] $resourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$true,parametersetname='username')] - [string] $Username=$null, - [Parameter(mandatory=$true,parametersetname='username')] - [String] $password=$null, - [Parameter(mandatory=$true,parametersetname='snmp')] - [String] $communitystring=$null, - [Parameter(mandatory=$true,parametersetname='cert')] - [string] $Thumbprint - ) - - # create credentials that will be used to talk to host - - $creds = @{} - $creds.resourceID = $resourceID - $creds.properties = @{} - if ($pscmdlet.ParameterSetName -eq 'cert') { - $creds.properties.Type = "X509Certificate" - $creds.properties.Value = $thumbprint - } - elseif ($pscmdlet.ParameterSetName -eq 'username') { - $creds.properties.Type = "UsernamePassword" - $creds.properties.Username = $Username - $creds.properties.Value = $password - } - else { - $creds.properties.Type = "SnmpCommunityString" - $creds.properties.Value = $communitystring - } - - JSONPost $script:NetworkControllerRestIP "/Credentials" $creds -Credential $script:NetworkControllerCred| out-null - return JSONGet $script:NetworkControllerRestIP "/Credentials/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred -} - -function Get-NCCredential { - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/Credentials/$resourceID" -Credential $script:NetworkControllerCred -} -function Remove-NCCredential { - param( - [Parameter(mandatory=$true)] - [string[]] $ResourceIDs - ) - foreach ($resourceId in $ResourceIDs) { - JSONDelete $script:NetworkControllerRestIP "/Credentials/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred| out-null - } -} - -function New-NCServerConnection { - #Creates an in-memory object only. Must pass it into New-NCServer to persist it. - param( - [Parameter(mandatory=$true)] - [string[]] $ComputerNames, - [Parameter(mandatory=$true)] - [object] $Credential - ) - - $connection = @{} - $connection.managementAddresses = $ComputerNames - $connection.credential = @{} - $connection.credential.resourceRef = "/credentials/$($credential.ResourceID)" - $connection.credentialType = $credential.properties.Type - - return $connection -} - -function New-NCServerNetworkInterface { - param( - [Parameter(mandatory=$false)] - [string] $ResourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$false)] - [boolean] $IsBMC=$false, - [Parameter(mandatory=$true,ParameterSetName="LogicalSubnet")] - [object[]] $LogicalNetworkSubnets - ) - - $networkInterface = @{} - $networkInterface.resourceId = $ResourceID - $networkInterface.instanceId = $ResourceID - $networkInterface.properties = @{} - $networkInterface.properties.isBMC = $IsBMC - - $networkInterface.properties.logicalSubnets = @() - foreach ($logicalnetworksubnet in $logicalNetworkSubnets) { - $logicalSubnetref = @{} - $logicalSubnetref.resourceRef = $logicalnetworksubnet.resourceRef - $networkInterface.properties.logicalSubnets += $logicalSubnetref - } - - return $networkInterface -} -function New-NCServer { -#Must pass in ResourceID since it must match id of virtual switch - param( - [Parameter(mandatory=$true)] - [string] $ResourceID, - [Parameter(mandatory=$true)] - [object[]] $Connections, - [Parameter(mandatory=$false)] - [string] $Certificate = $null, - [Parameter(mandatory=$true)] - [object[]] $PhysicalNetworkInterfaces - - ) - - $server = @{} - $server.resourceId = $ResourceID - $server.instanceId = $ResourceID - $server.properties = @{} - - $server.properties.connections = $Connections - $server.properties.networkInterfaces = $PhysicalNetworkInterfaces - if ($certificate -ne $null) { - $server.properties.certificate = $certificate - } - - JSONPost $script:NetworkControllerRestIP "/Servers" $server -Credential $script:NetworkControllerCred | out-null - return JSONGet $script:NetworkControllerRestIP "/Servers/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred -} -function Get-NCServer { - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/Servers/$resourceID" -Credential $script:NetworkControllerCred -} -function Remove-NCServer { - param( - [Parameter(mandatory=$true)] - [string[]] $ResourceIDs - ) - foreach ($resourceId in $ResourceIDs) { - JSONDelete $script:NetworkControllerRestIP "/Servers/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred| out-null - } -} - -function New-NCMACPool { - param( - [Parameter(mandatory=$false)] - [string] $ResourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$true)] - [string] $StartMACAddress, - [Parameter(mandatory=$true)] - [string] $EndMACAddress - ) - - $macpool = @{} - $macpool.resourceId = $ResourceId - $macpool.properties = @{} - $macpool.properties.startMacAddress = $StartMACAddress - $macpool.properties.endMacAddress = $EndMACAddress - - JSONPost $script:NetworkControllerRestIP "/MacPools" $macPool -Credential $script:NetworkControllerCred | out-null - return JSONGet $script:NetworkControllerRestIP "/MacPools/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred -} - -function Get-NCMACPool { - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/MacPools/$resourceID" -Credential $script:NetworkControllerCred -} -function Remove-NCMACPool { - param( - [Parameter(mandatory=$true)] - [string[]] $ResourceIDs - ) - foreach ($resourceId in $ResourceIDs) { - JSONDelete $script:NetworkControllerRestIP "/MacPools/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred | out-null - } -} - -function New-NCIPPool { - param( - [Parameter(mandatory=$false)] - [string] $ResourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$true)] - [object] $LogicalNetworkSubnet, - [Parameter(mandatory=$true)] - [string] $StartIPAddress, - [Parameter(mandatory=$true)] - [string] $EndIPAddress, - [Parameter(mandatory=$false)] - [string[]] $DNSServers, - [Parameter(mandatory=$false)] - [string[]] $DefaultGateways - ) - - #todo: prevalidate that ip addresses and default gateway are within subnet - - $ippool = @{} - $ippool.resourceId = $ResourceId - $ippool.properties = @{} - $ippool.properties.startIpAddress = $StartIPAddress - $ippool.properties.endIpAddress = $EndIPAddress - - $refpath = "$($logicalnetworksubnet.resourceRef)/ippools" - JSONPost $script:NetworkControllerRestIP $refpath $ippool -Credential $script:NetworkControllerCred| out-null - return JSONGet $script:NetworkControllerRestIP "$refpath/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred -} -function Get-NCIPPool { - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "", - [Parameter(mandatory=$true)] - [object] $LogicalNetworkSubnet - ) - $refpath = "$($logicalnetworksubnet.resourceRef)/ippools" - return JSONGet $script:NetworkControllerRestIP "$refpath/$resourceID" -Credential $script:NetworkControllerCred -} -function Remove-NCIPPool { - param( - [Parameter(mandatory=$true)] - [string[]] $ResourceIDs, - [Parameter(mandatory=$true)] - [object] $LogicalNetworkSubnet - ) - $refpath = "$($logicalnetworksubnet.resourceRef)/ippools" - foreach ($resourceId in $ResourceIDs) { - JSONDelete $script:NetworkControllerRestIP "$refpath/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred | out-null - } -} -function New-NCAccessControlListRule { - param( - [Parameter(mandatory=$false)] - [string] $ResourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$true)] - [string] $Protocol, - [Parameter(mandatory=$true)] - [string] $SourcePortRange, - [Parameter(mandatory=$true)] - [string] $DestinationPortRange, - [Parameter(mandatory=$true)] - [string] $SourceAddressPrefix, - [Parameter(mandatory=$true)] - [string] $DestinationAddressPrefix, - [Parameter(mandatory=$true)] - [string] $Action, - [Parameter(mandatory=$true)] - [string] $ACLType, - [Parameter(mandatory=$true)] - [boolean] $Logging, - [Parameter(mandatory=$true)] - [int] $Priority - ) - - $aclRule = @{} - $aclRule.resourceId = $resourceId - - $aclRule.properties = @{} - $aclRule.properties.protocol = $protocol - $aclRule.properties.sourcePortRange = $SourcePortRange - $aclRule.properties.destinationPortRange = $Destinationportrange - $aclRule.properties.sourceAddressPrefix = $SourceAddressPrefix - $aclRule.properties.destinationAddressPrefix = $destinationAddressprefix - $aclRule.properties.action = $action - $aclRule.properties.type = $ACLType - if ($logging) { - $aclRule.properties.logging = "Enabled" - } else { - $aclRule.properties.logging = "Disabled" - } - $aclRule.properties.priority = "$priority" - - return $aclRule -} -function New-NCAccessControlList { - param( - [Parameter(mandatory=$false)] - [string] $ResourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$true)] - [object[]] $AccessControlListRules - ) - - $acls = @{} - $acls.resourceID = $resourceId - $acls.properties = @{} - $acls.properties.aclRules = $AccessControlListRules - - $acls.properties.ipConfigurations = @() - $acls.properties.subnet = @() - - $refpath = "/accessControlLists" - JSONPost $script:NetworkControllerRestIP $refpath $acls -Credential $script:NetworkControllerCred| out-null - return JSONGet $script:NetworkControllerRestIP "$refpath/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred -} -function Get-NCAccessControlList { - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/accessControlLists/$resourceID" -Credential $script:NetworkControllerCred -} -function Remove-NCAccessControlList { - param( - [Parameter(mandatory=$true)] - [string[]] $ResourceIDs - ) - foreach ($resourceId in $ResourceIDs) { - JSONDelete $script:NetworkControllerRestIP "/accessControlLists/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred | out-null - } -} - -function New-NCVirtualSubnet { - param( - [Parameter(mandatory=$false)] - [string] $ResourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$true)] - [string] $addressPrefix, - [Parameter(mandatory=$true)] - [object] $AccessControlList - ) - - $subnet = @{} - $subnet.resourceId = $ResourceId - $subnet.properties = @{} - $subnet.properties.addressPrefix = $AddressPrefix - - $subnet.properties.accessControlList = @{} - $subnet.properties.accessControlList.resourceRef = $AccessControlList.resourceRef - - $subnet.properties.ipConfigurations = @() - - return $subnet -} - -function Get-NCVirtualSubnet { - param( - [Parameter(mandatory=$true)] - [object] $VirtualNetwork, - [Parameter(mandatory=$false)] - [string] $ResourceID="" - ) - - if ($resourceId -eq "") { - $uri = "/VirtualNetworks/$($VirtualNetwork.ResourceId)/subnets" - } else { - $uri = "/VirtualNetworks/$($VirtualNetwork.ResourceId)/subnets/$ResourceId" - } - - return JSONGet $script:NetworkControllerRestIP $uri -Credential $script:NetworkControllerCred -} - -function New-NCVirtualNetwork { - param( - [Parameter(mandatory=$false)] - [string] $ResourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$true)] - [string[]] $addressPrefixes, - [Parameter(mandatory=$true)] - [object] $LogicalNetwork, - [Parameter(mandatory=$true)] - [object[]] $VirtualSubnets - ) - - $vnet = @{} - $vnet.resourceId = $resourceId - $vnet.properties = @{} - $vnet.properties.addressSpace = @{} - $vnet.properties.addressSpace.addressPrefixes = $AddressPrefixes - $vnet.properties.logicalnetwork = @{} - $vnet.properties.logicalnetwork.resourceRef = "/logicalnetworks/$($LogicalNetwork.resourceId)" - $vnet.properties.subnets = $VirtualSubnets - - $refpath = "/virtualnetworks" - JSONPost $script:NetworkControllerRestIP $refpath $vnet -Credential $script:NetworkControllerCred| out-null - return JSONGet $script:NetworkControllerRestIP "$refpath/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred - -} -function Get-NCVirtualNetwork { - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/VirtualNetworks/$resourceID" -Credential $script:NetworkControllerCred -} -function Remove-NCVirtualNetwork { - param( - [Parameter(mandatory=$true)] - [string[]] $ResourceIDs - ) - foreach ($resourceId in $ResourceIDs) { - JSONDelete $script:NetworkControllerRestIP "/VirtualNetworks/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred | out-null - } -} - -function Set-NCLoadBalancerManager { - param( - [Parameter(mandatory=$false)] - [Object[]] $VIPIPPools=$null, - [Parameter(mandatory=$false)] - [String[]] $OutboundNatIPExemptions=$null, - [Parameter(mandatory=$true)] - [String] $IPAddress - -) - - $LBM = @{} - $lbm.resourceId = "config" - $lbm.properties = @{} - $lbm.properties.loadbalancermanageripaddress = $IPAddress - - if ($VIPIPPools -ne $NULL) { - $lbm.properties.vipIpPools = @() - - foreach ($ippool in $VIPIPPools) { - $poolRef = @{} - $poolRef.resourceRef = $ippool.resourceRef - $lbm.properties.vipIpPools += $poolRef - } - } - - if ($OutboundNatIPExemptions -ne $null) { - $lbm.properties.OutboundNatIPExemptions = $OutboundNatIPExemptions - } - - JSONPost $script:NetworkControllerRestIP "/loadbalancermanager" $lbm -Credential $script:NetworkControllerCred| out-null - return JSONGet $script:NetworkControllerRestIP "/loadbalancermanager/config" -WaitForUpdate -Credential $script:NetworkControllerCred - - -} -function Get-NCLoadbalancerManager { - - return JSONGet $script:NetworkControllerRestIP "/LoadBalancerManager/Config" -Credential $script:NetworkControllerCred -} - -function New-NCVirtualServer { - param( - [Parameter(mandatory=$false)] - [string] $resourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$true)] - [object[]] $Connections, - [Parameter(mandatory=$false)] - [string] $Certificate, - [Parameter(mandatory=$true)] - [string] $vmGuid - ) - - $server = @{} - $server.resourceId = $ResourceID - if ($ResourceID.Length -lt 36) - { $server.instanceId = [system.guid]::NewGuid() } - else - { $server.instanceId = $ResourceID } - $server.properties = @{} - - $server.properties.connections = $Connections - if (![string]::IsNullOrEmpty($Certificate)) - { - $server.properties.certificate = $certificate - } - $server.properties.vmGuid = $vmGuid - - JSONPost $script:NetworkControllerRestIP "/VirtualServers" $server -Credential $script:NetworkControllerCred | out-null - return JSONGet $script:NetworkControllerRestIP "/VirtualServers/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred - -} -function Get-NCVirtualServer { - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/VirtualServers/$resourceID" -Credential $script:NetworkControllerCred -} -function Remove-NCVirtualServer { - param( - [Parameter(mandatory=$true)] - [string[]] $ResourceIDs - ) - foreach ($resourceId in $ResourceIDs) { - JSONDelete $script:NetworkControllerRestIP "/VirtualServers/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred| out-null - } -} - -function New-NCLoadBalancerMuxPeerRouterConfiguration { - param( - [Parameter(mandatory=$true)] - [string] $RouterName, - [Parameter(mandatory=$true)] - [string] $RouterIPAddress, - [Parameter(mandatory=$true)] - [int] $PeerASN, - [Parameter(mandatory=$false)] - [string] $LocalIPAddress - ) - - $peer = @{} - $peer.routerName = $RouterName - $peer.routerIPAddress = $RouterIPAddress - $peer.PeerASN = "$PeerASN" - $peer.localIPAddress = $LocalIPAddress - - return $peer -} -function New-NCLoadBalancerMux { - param( - [Parameter(mandatory=$false)] - [string] $resourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$true)] - [int] $LocalASN, - [Parameter(mandatory=$false)] - [object[]] $peerRouterConfigurations, - [Parameter(mandatory=$true)] - [object] $VirtualServer, - [Parameter(mandatory=$false)] - [object[]] $connections - - ) - - # create credentials that will be used to talk to host - - $mux = @{} - $mux.resourceID = $resourceID - $mux.properties = @{} - $mux.properties.routerConfiguration = @{} - $mux.properties.routerConfiguration.localASN = "$LocalASN" - $mux.properties.routerConfiguration.peerRouterConfigurations = @() - foreach ($peerRouterConfiguration in $peerRouterConfigurations) - { - $mux.properties.routerConfiguration.peerRouterConfigurations += $peerRouterConfiguration - } - $mux.properties.virtualServer = @{} - $mux.properties.virtualServer.resourceRef = $VirtualServer.resourceRef - - JSONPost $script:NetworkControllerRestIP "/LoadBalancerMuxes" $mux -Credential $script:NetworkControllerCred| out-null - return JSONGet $script:NetworkControllerRestIP "/LoadBalancerMuxes/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred -} -function Get-NCLoadBalancerMux { - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/LoadBalancerMuxes/$resourceID" -Credential $script:NetworkControllerCred -} -function Remove-NCLoadBalancerMux { - param( - [Parameter(mandatory=$true)] - [string[]] $ResourceIDs - ) - foreach ($resourceId in $ResourceIDs) { - JSONDelete $script:NetworkControllerRestIP "/LoadBalancerMuxes/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred| out-null - } -} - -function New-NCLoadBalancerFrontEndIPConfiguration { - param( - [Parameter(mandatory=$false)] - [string] $resourceID=[system.guid]::NewGuid(), - #[Parameter(mandatory=$true)] - #[object] $LoadBalancer, - [Parameter(mandatory=$true)] - [string] $PrivateIPAddress, - #[Parameter(mandatory=$true)] - #[object[]] $LoadBalancingRules, - [Parameter(mandatory=$true)] - [object] $Subnet - ) - - $frontend= @{} - $frontend.resourceID = $resourceID - $frontend.properties = @{} - $frontend.properties.privateIPAddress = $PrivateIPAddress - $frontend.properties.privateIPAllocationMethod = "Static" - $frontend.properties.Subnet = @{} - $frontend.properties.Subnet.resourceRef = $subnet.resourceRef - - return $frontend -} - -function New-NCLoadBalancerBackendAddressPool { - param( - [Parameter(mandatory=$false)] - [string] $resourceID=[system.guid]::NewGuid() - ) - - $be= @{} - $be.resourceID = $resourceID - $be.properties = @{} - - $be.properties.backendIPConfigurations = @() - return $be -} -function New-NCLoadBalancerLoadBalancingRule { - param( - [Parameter(mandatory=$false)] - [string] $resourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$true)] - [string] $protocol, - [Parameter(mandatory=$true)] - [int] $frontendPort, - [Parameter(mandatory=$true)] - [int] $backendPort, - [Parameter(mandatory=$true)] - [boolean] $enableFloatingIP, - [Parameter(mandatory=$false)] - [int] $IdleTimeoutInMinutes = 4, - [Parameter(mandatory=$false)] - [String] $LoadDistribution = "Default", - [Parameter(mandatory=$true)] - [object[]] $frontEndIPConfigurations = $null, - [Parameter(mandatory=$true)] - [object] $backendAddressPool - ) - - $rule= @{} - $rule.resourceID = $resourceID - $rule.properties = @{} - $rule.properties.protocol = $protocol - $rule.properties.frontEndPort = "$frontendPort" - $rule.properties.backendPort = "$backendPort" - - if ($enableFloatingIP) { - $rule.properties.enableFloatingIP = "true" - } else { - $rule.properties.enableFloatingIP = "false" - } - $rule.properties.idleTimeoutInMinutes = "$idleTImeoutInMinutes" - $rule.properties.loadDistribution = $LoadDistribution - - $rule.properties.frontendIPConfigurations = @() - - foreach ($vip in $frontendipconfigurations) { - $newvip = @{} - $newvip.resourceRef = "/loadbalancers/`{0`}/frontendipconfigurations/$($vip.resourceId)" - $rule.properties.frontendIPConfigurations += $newvip - } - $rule.properties.backendAddressPool = @{} - $rule.properties.backendAddressPool.resourceRef = "/loadbalancers/`{0`}/backendaddresspools/$($backendAddressPool.resourceID)" - - return $rule -} - -function New-NCLoadBalancerProbe { - param( - [Parameter(mandatory=$false)] - [string] $resourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$true)] - [object] $LoadBalancer, - [Parameter(mandatory=$true)] - [string] $protocol, - [Parameter(mandatory=$true)] - [int] $port, - [Parameter(mandatory=$true)] - [int] $intervalInSeconds, - [Parameter(mandatory=$true)] - [int] $numberOfProbes, - [Parameter(mandatory=$false)] - [object[]] $loadBalancingRules = $null - ) - - $probe= @{} - $probe.resourceID = $resourceID - $probe.properties = @{} - $probe.properties.protocol = $protocol - $probe.properties.port = "$port" - $probe.properties.intervalInSeconds= "$intervalInSeconds" - $probe.properties.numberOfProbes = "$numberOfProbes" - - #TODO: what to do with loadbalancingrules - - $refpath = "$($loadbalancer.resourceRef)/probes" - JSONPost $script:NetworkControllerRestIP $refpath $probe -Credential $script:NetworkControllerCred| out-null - return JSONGet $script:NetworkControllerRestIP "$refpath/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred -} -function New-NCLoadBalancerOutboundNatRule { - param( - [Parameter(mandatory=$false)] - [string] $resourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$false)] - [string] $protocol="All", - [Parameter(mandatory=$true)] - [object[]] $frontEndIpConfigurations, - [Parameter(mandatory=$true)] - [object] $backendAddressPool - ) - - $natrule= @{} - $natrule.resourceID = $resourceID - $natrule.properties = @{} - $natrule.properties.protocol = $protocol - $natrule.properties.frontendIPConfigurations = @() - - foreach ($frontendIP in $frontEndIpConfigurations) { - $NewFEIP = @{} - $NewFEIP.resourceRef = "/loadbalancers/`{0`}/frontendipconfigurations/$($frontendIP.resourceId)" - $natrule.properties.frontendIPConfigurations += $NewFEIP - } - - $natrule.properties.backendAddressPool = @{} - $natrule.properties.backendAddressPool.resourceRef = "/loadbalancers/`{0`}/backendaddresspools/$($backendAddressPool.resourceID)" - - return $natrule -} - -function New-NCLoadBalancer -{ - param( - [Parameter(mandatory=$false)] - [string] $resourceID=[system.guid]::NewGuid(), - [parameter(mandatory=$true)] - [object[]] $FrontEndIPConfigurations, - [parameter(mandatory=$true)] - [object[]] $backendAddressPools, - [parameter(mandatory=$false)] - [object[]] $loadBalancingRules = $NULL, - [parameter(mandatory=$false)] - [object[]] $probes = $NULL, - [parameter(mandatory=$false)] - [object[]] $outboundnatrules= $NULL, - [Parameter(mandatory=$false)] - [string] $ComputerName=$script:NetworkControllerRestIP - ) - - $lb = JSONGet $script:NetworkControllerRestIP "/LoadBalancers/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred -Silent:$true - # Note this handles Add updates ONLY for LOAD Balancing RULES (common case in MAS / PoC) - if ($null -ne $lb) - { - # add the obNat, LB Rule, Inbound Rule - - $FrontEndIPConfigurations = @($FrontEndIPConfigurations) - - $lbfeIp = $lb.properties.frontendipconfigurations[0] - - if($null -ne $lbfeIp) - { - $newFeIP = @(New-NCLoadBalancerFrontEndIPConfiguration -resourceID $lbfeIp.resourceid -PrivateIPAddress $lbfeIp.properties.privateIpaddress -Subnet $lbfeIp.properties.subnet) - $FrontEndIPConfigurations = $newFeIp - } - - $lbbepool = $lb.properties.backendaddresspools[0] - - if($null -ne $lbbepool) - { - $newBePool = @(New-NCLoadBalancerBackendAddressPool -resourceID $lbbepool.resourceId) - $backendAddressPools = $newBePool - } - - if ( ($null -ne $lb.properties.OutboundNatRules) -and ($lb.properties.OutboundNatRules.count -ne 0)) - { - $obNatRules =$lb.properties.OutboundNatRules[0] - - $newObNatRule = @(New-NCLoadBalancerOutboundNatRule -resourceID $obNatRules.resourceId -protocol $obNatRules.properties.protocol -frontEndIpConfigurations $FrontEndIPConfigurations -backendAddressPool $backendAddressPools) - $outboundnatrules = $newObNatRule - } - - $loadBalancingRules = @($loadBalancingRules) - $newloadBalancingRules = @() - - foreach ($lbrule in $lb.properties.loadBalancingRules) - { - $newLbRule = @(New-NCLoadBalancerLoadBalancingRule -resourceId $lbrule.resourceId -protocol $lbrule.properties.protocol -frontendPort $lbrule.properties.frontendPort -backendport $lbrule.properties.backendPort -enableFloatingIP $lbrule.properties.enableFloatingIp -frontEndIPConfigurations $FrontEndIPConfigurations -backendAddressPool $backendAddressPools) - $newloadBalancingRules += $newLbRule - } - - $lbRuleCount = $newloadBalancingRules.Count - - #find new unique lb rules - foreach ($lbrule in $loadBalancingRules) - { - $found = $false - foreach ($oldrule in $lb.properties.loadBalancingRules) - { - if(($lbrule.properties.frontendPort -eq $oldrule.properties.frontendPort) -and ($lbrule.properties.backendPort -eq $oldrule.properties.backendPort)) - { - $found = $true - } - } - - if(-not $found) - { - $enableFloat = [Boolean]::Parse("$($lbrule.properties.enableFloatingIp)") - $newLbRule = @(New-NCLoadBalancerLoadBalancingRule -resourceId $lbrule.resourceId -protocol $lbrule.properties.protocol -frontendPort $lbrule.properties.frontendPort -backendport $lbrule.properties.backendPort -enableFloatingIP $enableFloat -frontEndIPConfigurations $FrontEndIPConfigurations -backendAddressPool $backendAddressPools) - $newloadBalancingRules += $newLbRule - } - } - - if($lbRuleCount -eq $newloadBalancingRules.Count) - { - #No change in LB required, skip the update - return $lb - } - - $loadBalancingRules = $newloadBalancingRules - } - else - { - $lb = @{} - $lb.resourceID = $resourceID - $lb.properties = @{} - } - - #Need to populate existing refs with LB resourceID - if ($loadbalancingrules -ne $null) - { - foreach ($rule in $loadbalancingrules) - { - foreach ($frontend in $rule.properties.frontendipconfigurations) - { - $frontend.resourceRef = ($frontend.resourceRef -f $resourceID) - } - $rule.properties.backendaddresspool.resourceRef = ($rule.properties.backendaddresspool.resourceRef -f $resourceID) - } - $lb.properties.loadBalancingRules = $loadbalancingrules - } - - if ($outboundnatrules -ne $null) - { - foreach ($rule in $outboundnatrules) - { - foreach ($frontend in $rule.properties.frontendipconfigurations) - { - $frontend.resourceRef = ($frontend.resourceRef -f $resourceID) - } - $rule.properties.backendaddresspool.resourceRef = ($rule.properties.backendaddresspool.resourceRef -f $resourceID) - } - $lb.properties.outboundnatrules = $outboundnatrules - } - - foreach ($frontend in $frontendipconfigurations) - { - $frontendref = "/loadbalancers/$resourceID/frontendipconfigurations/$($frontend.resourceId)" - - $frontend.properties.loadbalancingrules = @() - if ($loadbalancingrules -ne $null) - { - foreach ($rule in $loadbalancingrules) - { - foreach ($rulefe in $rule.properties.frontendipconfigurations) - { - if ($rulefe.resourceRef -eq $frontendref) - { - $newref = @{} - $newref.resourceRef = "/loadbalancers/$resourceID/loadbalancingrules/$($rule.resourceId)" - - $frontend.properties.loadbalancingrules += $newref - } - } - - } - } - - $frontend.properties.outboundNatRules = @() - if ($oubboundNatRules -ne $null) - { - foreach ($rule in $outboundnatrules) - { - foreach ($rulefe in $rule.properties.frontendipconfigurations) - { - if ($rulefe.resourceRef -eq $frontendref) - { - $newref = @{} - $newref.resourceRef = "/loadbalancers/$resourceID/outboundnatrules/$($rule.resourceId)" - - $frontend.properties.outboundNatRules += $newref - } - } - - } - } - } - $lb.properties.frontendipconfigurations = $frontendipconfigurations - - foreach ($be in $backendaddresspools) - { - $beref = "/loadbalancers/$resourceID/backendaddresspools/$($be.resourceId)" - - $be.properties.loadbalancingrules = @() - if ($loadbalancingrules -ne $null) - { - foreach ($rule in $loadbalancingrules) - { - if ($rule.properties.backendaddresspool.resourceRef -eq $beref) - { - $newref = @{} - $newref.resourceRef = "/loadbalancers/$resourceID/loadbalancingrules/$($rule.resourceId)" - $be.properties.loadbalancingrules += $newref - } - - } - } - $be.properties.outboundnatrules = @() - if ($outboundnatrules -ne $null) - { - foreach ($rule in $outboundnatrules) - { - if ($rule.properties.backendaddresspool.resourceRef -eq $beref) - { - $newref = @{} - $newref.resourceRef = "/loadbalancers/$resourceID/outboundnatrules/$($rule.resourceId)" - $be.properties.outboundnatrules += $newref - } - - } - } - } - $lb.properties.backendaddresspools = $backendaddresspools - - # $computerName is here to workaround product limitation for PUT of LoadBalancer, which is > 35KB and must be done from the REST hosting NC Vm. - JSONPost $script:NetworkControllerRestIP "/LoadBalancers" $lb -Credential $script:NetworkControllerCred -computerName $ComputerName| out-null - return JSONGet $script:NetworkControllerRestIP "/LoadBalancers/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred -} - -function Get-NCLoadBalancer { - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/LoadBalancers/$resourceID" -Credential $script:NetworkControllerCred -} -function Remove-NCLoadBalancer { - param( - [Parameter(mandatory=$true)] - [string[]] $ResourceIDs - ) - foreach ($resourceId in $ResourceIDs) { - JSONDelete $script:NetworkControllerRestIP "/LoadBalancers/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred| out-null - } -} - -function New-NCNetworkInterface { - param( - [Parameter(mandatory=$true,ParameterSetName="ByVirtualNetwork")] - [Parameter(mandatory=$true,ParameterSetName="ByLogicalNetwork")] - [Parameter(mandatory=$true,ParameterSetName="ByNoNetwork")] - [string] $resourceID, - [Parameter(mandatory=$true,ParameterSetName="ByVirtualNetwork")] - [object] $VirtualSubnet = $null, - [Parameter(mandatory=$true,ParameterSetName="ByLogicalNetwork")] - [object] $Subnet = $null, - [Parameter(mandatory=$false,ParameterSetName="ByVirtualNetwork")] - [Parameter(mandatory=$false,ParameterSetName="ByLogicalNetwork")] - [Parameter(mandatory=$false,ParameterSetName="ByNoNetwork")] - [string] $IPAddress = $null, - [Parameter(mandatory=$true,ParameterSetName="ByVirtualNetwork")] - [Parameter(mandatory=$true,ParameterSetName="ByLogicalNetwork")] - [Parameter(mandatory=$true,ParameterSetName="ByNoNetwork")] - [string] $MACAddress, - [Parameter(mandatory=$false,ParameterSetName="ByVirtualNetwork")] - [Parameter(mandatory=$false,ParameterSetName="ByLogicalNetwork")] - [Parameter(mandatory=$false,ParameterSetName="ByNoNetwork")] - [string[]] $DNSServers = @(), - [Parameter(mandatory=$false,ParameterSetName="ByVirtualNetwork")] - [Parameter(mandatory=$false,ParameterSetName="ByLogicalNetwork")] - [Parameter(mandatory=$false,ParameterSetName="ByNoNetwork")] - [object] $acl=$null - ) - - if ($pscmdlet.ParameterSetName -eq 'ByVirtualNetwork') { - $subnet = $virtualsubnet - } - - $interface = @{} - # resource Id - $interface.resourceID = $resourceID - $interface.properties = @{} - - # Mac Address - $interface.properties.privateMacAddress = $macaddress - $interface.properties.privateMacAllocationMethod = "Static" - - # IPConfigurations - if ($Subnet -ne $null -or ![string]::IsNullOrEmpty($IPAddress) -or $acl -ne $null) - { - $interface.properties.ipConfigurations = @() - - $ipconfig = @{} - $ipconfig.resourceId = [System.Guid]::NewGuid().toString() - $ipconfig.properties = @{} - - if ($Subnet -ne $null) - { - $ipconfig.properties.subnet = @{} - $ipconfig.properties.subnet.resourceRef = $Subnet.resourceRef - } - if (![string]::IsNullOrEmpty($IPAddress)) - { - $ipconfig.properties.privateIPAddress = $IPAddress - $ipconfig.properties.privateIPAllocationMethod = "Static" - } - if ($acl -ne $null) { - $ipconfig.properties.accessControlList = @{} - $ipconfig.properties.accessControlList.resourceRef = $acl.resourceRef - } - - $interface.properties.ipConfigurations += $ipconfig - } - - # DNS Servers - if ($DNSServers -ne $null -and $DNSServers.count -gt 0) - { - $interface.properties.dnsSettings = @{} - $interface.properties.dnsSettings.dnsServers = $DNSServers - } - - JSONPost $script:NetworkControllerRestIP "/NetworkInterfaces" $interface -Credential $script:NetworkControllerCred| out-null - return JSONGet $script:NetworkControllerRestIP "/NetworkInterfaces/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred -} -function Get-NCNetworkInterface { - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/NetworkInterfaces/$resourceID" -Credential $script:NetworkControllerCred -} -function Remove-NCNetworkInterface { - param( - [Parameter(mandatory=$true)] - [string[]] $ResourceIDs - ) - foreach ($resourceId in $ResourceIDs) { - JSONDelete $script:NetworkControllerRestIP "/NetworkInterfaces/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred| out-null - } -} - -function Get-ServerResourceId { - param ( - [Parameter(mandatory=$false)] - [string] $ComputerName="localhost", - [Parameter(mandatory=$false)] - [Object] $Credential=$script:NetworkControllerCred - ) - - $resourceId = "" - - write-verbose ("Retrieving server resource id on [$ComputerName]") - - try - { - $pssession = new-pssession -ComputerName $ComputerName -Credential $Credential - - $resourceId = invoke-command -session $pssession -ScriptBlock { - $VerbosePreference = 'Continue' - write-verbose "Retrieving first VMSwitch on [$using:ComputerName]" - - $switches = Get-VMSwitch -ErrorAction Ignore - if ($switches.Count -eq 0) - { - throw "No VMSwitch was found on [$using:ComputerName]" - } - - return $switches[0].Id - } - } - catch - { - Write-Error "Failed with error: $_" - } - finally - { - Remove-PSSession $pssession - } - - write-verbose "Server resource id is [$resourceId] on [$ComputerName]" - return $resourceId -} - -function Set-PortProfileId { - param ( - [Parameter(mandatory=$true)] - [string] $resourceID, - [Parameter(mandatory=$true)] - [string] $VMName, - [Parameter(mandatory=$false)] - [string] $VMNetworkAdapterName, - [Parameter(mandatory=$false)] - [string] $ComputerName="localhost", - [Parameter(mandatory=$false)] - [Object] $credential=$script:NetworkControllerCred, - [Parameter(mandatory=$false)] - [int] $ProfileData = 1, - [Switch] $force - ) - - #do not change these values - write-verbose ("Setting port profile for [$vmname] on [$computername]" ) - - try - { - $pssession = new-pssession -ComputerName $computername -Credential $credential - $isforce = $force.ispresent - - invoke-command -session $pssession -ScriptBlock { - $VerbosePreference = 'Continue' - write-verbose ("Running port profile set script block on host" ) - - $PortProfileFeatureId = "9940cd46-8b06-43bb-b9d5-93d50381fd56" - $NcVendorId = "{1FA41B39-B444-4E43-B35A-E1F7985FD548}" - - $portProfileDefaultSetting = Get-VMSystemSwitchExtensionPortFeature -FeatureId $PortProfileFeatureId - - $portProfileDefaultSetting.SettingData.ProfileId = "{$using:resourceId}" - $portProfileDefaultSetting.SettingData.NetCfgInstanceId = "{56785678-a0e5-4a26-bc9b-c0cba27311a3}" - $portProfileDefaultSetting.SettingData.CdnLabelString = "TestCdn" - $portProfileDefaultSetting.SettingData.CdnLabelId = 1111 - $portProfileDefaultSetting.SettingData.ProfileName = "Testprofile" - $portProfileDefaultSetting.SettingData.VendorId = $NcVendorId - $portProfileDefaultSetting.SettingData.VendorName = "NetworkController" - $portProfileDefaultSetting.SettingData.ProfileData = $using:ProfileData - #$portprofiledefaultsetting.settingdata - - write-verbose ("Retrieving VM network adapter $using:VMNetworkAdapterName" ) - if ([String]::IsNullOrEmpty($using:VMNetworkAdapterName)) - { - write-verbose ("Retrieving all VM network adapters for VM $using:VMName") - $vmNics = Get-VMNetworkAdapter -VMName $using:VMName - } - else - { - write-verbose ("Retrieving VM network adapter $using:VMNetworkAdapterName for VM $using:VMName" ) - $vmNics = @(Get-VMNetworkAdapter -VMName $using:VMName -Name $using:VMNetworkAdapterName) - } - - foreach ($vmNic in $vmNics) { - write-verbose ("Setting port profile on vm network adapter $($vmNic.Name)" ) - $currentProfile = Get-VMSwitchExtensionPortFeature -FeatureId $PortProfileFeatureId -VMNetworkAdapter $vmNic - - if ( $currentProfile -eq $null) - { - write-verbose ("Adding port feature for [{0}] to [{1}]" -f $using:VMName, "{$using:resourceId}") - Add-VMSwitchExtensionPortFeature -VMSwitchExtensionFeature $portProfileDefaultSetting -VMNetworkAdapter $vmNic | out-null - write-verbose "Adding port feature complete" - - } - else - { - if ($using:isforce) { - write-verbose ("Setting port feature for [{0}] to [{1}]" -f $using:VMName, "{$using:resourceId}") - - $currentProfile.SettingData.ProfileId = "{$using:resourceId}" - $currentProfile.SettingData.ProfileData = $using:ProfileData - Set-VMSwitchExtensionPortFeature -VMSwitchExtensionFeature $currentProfile -VMNetworkAdapter $vmNic | out-null - } else { - write-verbose ("Port profile already set for [{0}] use -Force to override." -f $using:VMName) - } - } - } - } - } - catch - { - Write-Error "Failed with error: $_" - } - finally - { - Remove-PSSession $pssession - } -} - -function Remove-PortProfileId -{ - param ( - [Parameter(mandatory=$true)] - [string] $VMName, - [Parameter(mandatory=$false)] - [string] $VMNetworkAdapterName="Network Adapter", - [Parameter(mandatory=$false)] - [string] $ComputerName="localhost" - ) - - write-verbose ("Removing port profile for Network Adapter [$VMNetworkAdapterName] on VM [$vmname] on [$computername]" ) - - try - { - $pssession = new-pssession -ComputerName $computername - - invoke-command -session $pssession -ScriptBlock { - param($VMName, $VMNetworkAdapterName) - $VerbosePreference = 'Continue' - write-verbose ("Running port profile remove script block on host" ) - - #do not change these values - $PortProfileFeatureId = "9940cd46-8b06-43bb-b9d5-93d50381fd56" - $NcVendorId = "{1FA41B39-B444-4E43-B35A-E1F7985FD548}" - - $portProfileCurrentSetting = Get-VMSwitchExtensionPortFeature -FeatureId $PortProfileFeatureId -VMName $VMName -VMNetworkAdapterName $VMNetworkAdapterName - - write-verbose ("Removing port profile from vm network adapter $VMNetworkAdapterName" ) - Remove-VMSwitchExtensionPortFeature -VMSwitchExtensionFeature $portProfileCurrentSetting -VMName $VMName -VMNetworkAdapterName $VMNetworkAdapterName -Confirm:$false - } -ArgumentList @($VMName, $VMNetworkAdapterName) - } - catch - { - Write-Error "Failed with error: $_" - } - finally - { - Remove-PSSession $pssession - } -} - -function New-NCSwitchPort { - param( - [Parameter(mandatory=$false)] - [string] $resourceID=[system.guid]::NewGuid() - ) - - $server = @{} - $server.resourceId = $ResourceID - $server.instanceId = $ResourceID - $server.properties = @{} - - if ($managed.ispresent) { - $server.properties.managementState = "Managed" - } else - { - $server.properties.managementState = "unManaged" - } - $server.properties.roleType = "multiLayerSwitch" - $server.properties.switchType = $switchtype - - $server.properties.connections = $Connections - - JSONPost $script:NetworkControllerRestIP "/Switches" $server -Credential $script:NetworkControllerCred | out-null - return JSONGet $script:NetworkControllerRestIP "/Switches/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred - -} - -function New-NCSwitch { - param( - [Parameter(mandatory=$false)] - [string] $resourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$true)] - [object[]] $Connections, - [Parameter(mandatory=$true)] - [string] $switchType, - [Switch] $Managed - ) - - $server = @{} - $server.resourceId = $ResourceID - $server.instanceId = $ResourceID - $server.properties = @{} - - if ($managed.ispresent) { - $server.properties.managementState = "Managed" - } else - { - $server.properties.managementState = "Unmanaged" - } - $server.properties.roleType = "multiLayerSwitch" - $server.properties.switchType = $switchtype - $server.properties.switchPorts = @() - - $newport = @{} - $newport.ResourceRef = "Port1" - $newport.properties = @{} - $server.properties.switchPorts += $newport - - $newport = @{} - $newport.ResourceRef = "Port2" - $newport.properties = @{} - $server.properties.switchPorts += $newport - - $server.properties.connections = $Connections - - JSONPost $script:NetworkControllerRestIP "/Switches" $server -Credential $script:NetworkControllerCred| out-null - return JSONGet $script:NetworkControllerRestIP "/Switches/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred - -} - -function Get-NCSwitch { - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/Switches/$resourceID" -Credential $script:NetworkControllerCred -} - -function Remove-NCSwitch { - param( - [Parameter(mandatory=$true)] - [string[]] $ResourceIDs - ) - foreach ($resourceId in $ResourceIDs) { - JSONDelete $script:NetworkControllerRestIP "/Switches/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred| out-null - } -} - -# -# iDNS Specific Wrappers -# -function Add-iDnsConfiguration -{ - param( - [Parameter(mandatory=$true)] - [object[]] $connections, - [Parameter(mandatory=$true)] - [string] $zoneName - ) - - $iDnsObj = @{} - # resource Id configuration is fixed - $iDnsObj.resourceID = "configuration" - $iDnsObj.properties = @{} - - $iDnsObj.properties.connections=$connections - $iDnsObj.properties.zone=$zoneName - - - JSONPost $script:NetworkControllerRestIP "/iDnsServer" $iDnsObj -Credential $script:NetworkControllerCred| out-null - return JSONGet $script:NetworkControllerRestIP "/iDnsServer/Configuration" -WaitForUpdate -Credential $script:NetworkControllerCred -} - -function Get-iDnsConfiguration -{ - return JSONGet $script:NetworkControllerRestIP "/iDnsServer/configuration" -Credential $script:NetworkControllerCred -} - - -# -# Gateway Specific Wrappers -# - -function New-NCPublicIPAddress -{ - param( - [Parameter(mandatory=$false)] - [string] $ResourceID=[system.guid]::NewGuid(), - [Parameter(mandatory=$true)] - [string] $PublicIPAddress) - - $publicIP = @{} - $publicIP.resourceId = $ResourceID - $publicIP.properties = @{} - $publicIP.properties.ipAddress = $PublicIPAddress - $publicIP.properties.publicIPAllocationMethod = "Static" - - JSONPost $script:NetworkControllerRestIP "/publicIPAddresses" $publicIP -Credential $script:NetworkControllerCred | out-null - return JSONGet $script:NetworkControllerRestIP "/publicIPAddresses/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred -} - -function Get-NCPublicIPAddress -{ - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/publicIPAddresses/$resourceID" -Credential $script:NetworkControllerCred -} - -function Remove-NCPublicIPAddress -{ - param( - [Parameter(mandatory=$true)] - [string[]] $ResourceIDs - ) - foreach ($resourceId in $ResourceIDs) { - JSONDelete $script:NetworkControllerRestIP "/publicIPAddresses/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred | out-null - } -} - -function New-NCGatewayPool -{ - param( - [Parameter(mandatory=$true)] - [string] $resourceID, - [Parameter(mandatory=$true)] - [string] $Type, - [Parameter(mandatory=$false)] - [string] $GreVipSubnetResourceRef, - [Parameter(mandatory=$false)] - [string] $PublicIPAddressId, - [Parameter(mandatory=$false)] - [System.UInt64] $Capacity = 10000000, - [Parameter(mandatory=$false)] - [System.UInt32] $RedundantGatewayCount = 0 - ) - - $gwPool = @{} - $gwPool.resourceID = $resourceID - - $gwPool.properties = @{} - $gwPool.properties.type = $Type - $gwPool.properties.ipConfiguration = @{} - - if (-not([String]::IsNullOrEmpty($GreVipSubnetResourceRef))) - { - $gwPool.properties.ipConfiguration.greVipSubnets = @() - $greVipSubnet = @{} - $greVipSubnet.resourceRef = $GreVipSubnetResourceRef - $gwPool.properties.ipConfiguration.greVipSubnets += $greVipSubnet - } - - $publicIPAddresses = @{} - if (-not([String]::IsNullOrEmpty($PublicIPAddressId))) - { - $publicIPAddresses.resourceRef = "/publicIPAddresses/$PublicIPAddressId" - $gwPool.properties.ipConfiguration.publicIPAddresses = @() - $gwPool.properties.ipConfiguration.publicIPAddresses += $publicIPAddresses - } - $gwPool.properties.redundantGatewayCount = $RedundantGatewayCount - $gwPool.properties.gatewayCapacityKiloBitsPerSecond = $Capacity - - JSONPost $script:NetworkControllerRestIP "/GatewayPools" $gwPool -Credential $script:NetworkControllerCred | out-null - return JSONGet $script:NetworkControllerRestIP "/GatewayPools/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred -} - -function Get-NCGatewayPool -{ - param( - [Parameter(mandatory=$false)] - [string] $ResourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/GatewayPools/$ResourceID" -Credential $script:NetworkControllerCred -} - -function Remove-NCGatewayPool -{ - param( - [Parameter(mandatory=$true)] - [string[]] $ResourceIDs - ) - foreach ($ResourceId in $ResourceIDs) { - JSONDelete $script:NetworkControllerRestIP "/GatewayPools/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred | out-null - } -} - - -function New-NCGateway -{ - param( - [Parameter(mandatory=$true)] - [string] $resourceID, - [Parameter(mandatory=$true)] - [string] $GatewayPoolRef, - [Parameter(mandatory=$true)] - [string] $Type, - [Parameter(mandatory=$false)] - [object] $BgpConfig, - [Parameter(mandatory=$true)] - [string] $VirtualServerRef, - [Parameter(mandatory=$true)] - [string] $InternalInterfaceRef, - [Parameter(mandatory=$true)] - [string] $ExternalInterfaceRef - ) - - $gateway = @{} - $gateway.resourceID = $resourceID - $gateway.properties = @{} - - $gateway.properties.pool = @{} - $gateway.properties.pool.resourceRef = $GatewayPoolRef - - $gateway.properties.type = $Type - $gateway.properties.bgpConfig = @{} - $gateway.properties.bgpConfig = $BgpConfig - - $gateway.properties.virtualserver = @{} - $gateway.properties.virtualserver.resourceRef = $VirtualServerRef - - $gateway.properties.networkInterfaces = @{} - $gateway.properties.networkInterfaces.externalNetworkInterface = @{} - $gateway.properties.networkInterfaces.externalNetworkInterface.resourceRef = $ExternalInterfaceRef - $gateway.properties.networkInterfaces.internalNetworkInterface = @{} - $gateway.properties.networkInterfaces.internalNetworkInterface.resourceRef = $InternalInterfaceRef - - JSONPost $script:NetworkControllerRestIP "/Gateways" $gateway -Credential $script:NetworkControllerCred | out-null - return JSONGet $script:NetworkControllerRestIP "/Gateways/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred -} - -function Get-NCGateway -{ - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/Gateways/$resourceID" -Credential $script:NetworkControllerCred -} - -function Remove-NCGateway -{ - param( - [Parameter(mandatory=$true)] - [string] $ResourceID - ) - JSONDelete $script:NetworkControllerRestIP "/Gateways/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred | out-null -} - -function New-NCVpnClientAddressSpace -{ - param( - [Parameter(mandatory=$true)] - [string] $TenantName, - [Parameter(mandatory=$true)] - [System.UInt64] $VpnCapacity, - [Parameter(mandatory=$true)] - [string] $Ipv4AddressPool, - [Parameter(mandatory=$true)] - [string] $Ipv6AddressPool - ) - - $vpnClientAddressSpace = @{} - $vpnClientAddressSpace.AddressPrefixes = @() - $vpnClientAddressSpace.AddressPrefixes += @($Ipv4AddressPool, $Ipv6AddressPool) - $vpnClientAddressSpace.Capacity = $VpnCapacity.ToString() - $vpnClientAddressSpace.Realm = $TenantName - - return $vpnClientAddressSpace -} - -function New-NCIPSecTunnel -{ - param( - [Parameter(mandatory=$true)] - [string] $ResourceId, - [Parameter(mandatory=$true)] - [system.uint64] $OutboundCapacity, - [Parameter(mandatory=$true)] - [system.uint64] $InboundCapacity, - [Parameter(mandatory=$false)] - [object[]] $IPAddresses, - [Parameter(mandatory=$false)] - [string[]] $PeerIPAddresses, - [Parameter(mandatory=$false)] - [string] $DestinationIPAddress, - [Parameter(mandatory=$false)] - [string] $SharedSecret, - [Parameter(mandatory=$false)] - [object[]] $IPv4Subnets - ) - - $AuthenticationMethod = "PSK" - - $ipSecVpn = @{} - $ipsecVpn.resourceId = $ResourceId - $ipSecVpn.properties = @{} - - $ipSecVpn.properties.connectionType = "IPSec" - $ipSecVpn.properties.outboundKiloBitsPerSecond = $outboundCapacity - $ipSecVpn.properties.inboundKiloBitsPerSecond = $inboundCapacity - - $routes = @() - foreach ($IPv4Subnet in $IPv4Subnets) - { - $route = @{} - $route.destinationPrefix = $IPv4Subnet.Prefix - $route.metric = $IPv4Subnet.Metric - $routes += $route - } - $ipSecVpn.properties.routes = @() - $ipSecVpn.properties.routes = $routes - - $ipSecVpn.properties.ipSecConfiguration = @{} - $ipSecVpn.properties.ipSecConfiguration.QuickMode = @{} - $ipSecVpn.properties.ipSecConfiguration.MainMode = @{} - - $ipSecVpn.properties.ipSecConfiguration.authenticationMethod = $AuthenticationMethod - if ($AuthenticationMethod -eq "PSK") - { $ipSecVpn.properties.ipSecConfiguration.sharedSecret = $SharedSecret } - - $ipSecVpn.properties.ipSecConfiguration.quickMode.perfectForwardSecrecy = "PFS2048" - $ipSecVpn.properties.ipSecConfiguration.quickMode.authenticationTransformationConstant = "SHA256128" - $ipSecVpn.properties.ipSecConfiguration.quickMode.cipherTransformationConstant = "DES3" - $ipSecVpn.properties.ipSecConfiguration.quickMode.saLifeTimeSeconds = 1233 - $ipSecVpn.properties.ipSecConfiguration.quickMode.idleDisconnectSeconds = 500 - $ipSecVpn.properties.ipSecConfiguration.quickMode.saLifeTimeKiloBytes = 2000 - - $ipSecVpn.properties.ipSecConfiguration.mainMode.diffieHellmanGroup = "Group2" - $ipSecVpn.properties.ipSecConfiguration.mainMode.integrityAlgorithm = "SHA256" - $ipSecVpn.properties.ipSecConfiguration.mainMode.encryptionAlgorithm = "AES256" - $ipSecVpn.properties.ipSecConfiguration.mainMode.saLifeTimeSeconds = 1234 - $ipSecVpn.properties.ipSecConfiguration.mainMode.saLifeTimeKiloBytes = 2000 - - if ($IPAddresses -eq $null) {$IPAddresses = @()} - if ($PeerIPAddresses -eq $null) {$PeerIPAddresses = @()} - - $ipSecVpn.properties.ipAddresses = $IPAddresses - $ipSecVpn.properties.peerIPAddresses = $PeerIPAddresses - $ipSecVpn.properties.destinationIPAddress = $DestinationIPAddress - - return $ipSecVpn -} - -function New-NCGreTunnel -{ - param( - [Parameter(mandatory=$true)] - [string] $ResourceId, - [Parameter(mandatory=$true)] - [system.uint64] $OutboundCapacity, - [Parameter(mandatory=$true)] - [system.uint64] $InboundCapacity, - [Parameter(mandatory=$false)] - [object[]] $IPAddresses, - [Parameter(mandatory=$false)] - [string[]] $PeerIPAddresses, - [Parameter(mandatory=$false)] - [string] $DestinationIPAddress, - [Parameter(mandatory=$false)] - [object[]] $IPv4Subnets, - [Parameter(mandatory=$false)] - [string] $GreKey - ) - - $greTunnel = @{} - $greTunnel.resourceId = $ResourceId - $greTunnel.properties = @{} - - $greTunnel.properties.connectionType = "GRE" - $greTunnel.properties.outboundKiloBitsPerSecond = $outboundCapacity - $greTunnel.properties.inboundKiloBitsPerSecond = $inboundCapacity - - $greTunnel.properties.greConfiguration = @{} - $greTunnel.properties.greConfiguration.GreKey = $GreKey - - if ($IPAddresses -eq $null) {$IPAddresses = @()} - if ($PeerIPAddresses -eq $null) {$PeerIPAddresses = @()} - - $greTunnel.properties.ipAddresses = $IPAddresses - $greTunnel.properties.peerIPAddresses = $PeerIPAddresses - - $routes = @() - foreach ($IPv4Subnet in $IPv4Subnets) - { - $route = @{} - $route.destinationPrefix = $IPv4Subnet.Prefix - $route.metric = $IPv4Subnet.Metric - $routes += $route - } - $greTunnel.properties.routes = @() - $greTunnel.properties.routes = $routes - - $greTunnel.properties.destinationIPAddress = $DestinationIPAddress - - return $greTunnel -} - -function New-NCL3Tunnel -{ - param( - [Parameter(mandatory=$true)] - [string] $ResourceId, - [Parameter(mandatory=$true)] - [system.uint64] $OutboundCapacity, - [Parameter(mandatory=$true)] - [system.uint64] $InboundCapacity, - [Parameter(mandatory=$false)] - [string] $VlanSubnetResourceRef, - [Parameter(mandatory=$false)] - [object[]] $L3IPAddresses, - [Parameter(mandatory=$false)] - [System.UInt16] $PrefixLength, - [Parameter(mandatory=$false)] - [string[]] $L3PeerIPAddresses, - [Parameter(mandatory=$false)] - [object[]] $IPv4Subnets - ) - - $l3Tunnel = @{} - $l3Tunnel.resourceId = $ResourceId - $l3Tunnel.properties = @{} - - $l3Tunnel.properties.connectionType = "L3" - $l3Tunnel.properties.outboundKiloBitsPerSecond = $outboundCapacity - $l3Tunnel.properties.inboundKiloBitsPerSecond = $inboundCapacity - - $l3Tunnel.properties.l3Configuration = @{} - $l3Tunnel.properties.l3Configuration.vlanSubnet = @{} - $l3Tunnel.properties.l3Configuration.vlanSubnet.resourceRef = $VlanSubnetResourceRef - - $l3Tunnel.properties.ipAddresses = @($L3IPAddresses) - $l3Tunnel.properties.peerIPAddresses = @($L3PeerIPAddresses) - - $routes = @() - foreach ($IPv4Subnet in $IPv4Subnets) - { - $route = @{} - $route.destinationPrefix = $IPv4Subnet.Prefix - $route.metric = $IPv4Subnet.Metric - $routes += $route - } - $l3Tunnel.properties.routes = @() - $l3Tunnel.properties.routes = $routes - - return $l3Tunnel -} - -function New-NCBgpRoutingPolicy -{ - param( - [Parameter(mandatory=$true)] - [string] $PolicyName, - [Parameter(mandatory=$true)] - [string] $PolicyType, - [Parameter(mandatory=$true)] - [object[]] $MatchCriteriaList, - [Parameter(mandatory=$false)] - [object[]] $Actions, - [Parameter(mandatory=$false)] - [string] $EgressPolicyMapResourceRef - ) - - $bgpPolicy = @{} - $bgpPolicy.policyName = $PolicyName - $bgpPolicy.policyType = $policyType - - $bgpPolicy.matchCriteria = @() - $bgpPolicy.setActions = @() - - foreach ($criteria in $MatchCriteriaList) - { - $matchCriteria = @{} - $matchCriteria.clause = $criteria.clause - $matchCriteria.operator = "And" - $matchCriteria.value = $criteria.value - - $bgpPolicy.matchCriteria += $matchCriteria - } - - $bgpPolicy.setActions += @($Actions) - - return $bgpPolicy -} - -function New-NCBgpRoutingPolicyMap -{ - param( - [Parameter(mandatory=$true)] - [string] $PolicyMapName, - [Parameter(mandatory=$true)] - [object[]] $PolicyList - ) - - $bgpPolicyMap = @{} - $bgpPolicyMap.resourceId = $PolicyMapName - $bgpPolicyMap.properties = @{} - - $bgpPolicyMap.properties.policyList = @($PolicyList) - - return $bgpPolicyMap -} - -function New-NCBgpPeer -{ - param( - [Parameter(mandatory=$true)] - [string] $PeerName, - [Parameter(mandatory=$true)] - [string] $PeerIP, - [Parameter(mandatory=$true)] - [string] $PeerASN, - [Parameter(mandatory=$false)] - [string] $IngressPolicyMapResourceRef, - [Parameter(mandatory=$false)] - [string] $EgressPolicyMapResourceRef - ) - - $bgpPeer = @{} - $bgpPeer.resourceId = $PeerName - $bgpPeer.properties = @{} - - $bgpPeer.properties.peerIPAddress = $PeerIP - $bgpPeer.properties.peerAsNumber = $PeerASN - $bgpPeer.properties.ExtAsNumber = "0.$PeerASN" - - $bgpPeer.properties.policyMapIn = $null - $bgpPeer.properties.policyMapOut = $null - - if (![string]::IsNullOrEmpty($IngressPolicyMapResourceRef)) - { - $bgpPeer.properties.policyMapIn = @{} - $bgpPeer.properties.policyMapIn.resourceRef = $IngressPolicyMapResourceRef - } - if (![string]::IsNullOrEmpty($EgressPolicyMapResourceRef)) - { - $bgpPeer.properties.policyMapOut = @{} - $bgpPeer.properties.policyMapOut.resourceRef = $EgressPolicyMapResourceRef - } - - return $bgpPeer -} - -function New-NCBgpRouter -{ - param( - [Parameter(mandatory=$true)] - [string] $RouterName, - [Parameter(mandatory=$true)] - [string] $LocalASN, - [Parameter(mandatory=$false)] - [object[]] $BgpPeers - ) - - $bgpRouter = @{} - $bgpRouter.resourceId = $RouterName - $bgpRouter.properties = @{} - - $bgpRouter.properties.isEnabled = "true" - $bgpRouter.properties.requireIGPSync = "true" - $bgpRouter.properties.extAsNumber = "0.$LocalASN" - $bgpRouter.properties.routerIP = @() - $bgpRouter.properties.bgpNetworks = @() - $bgpRouter.properties.isGenerated = $false - - $bgpRouter.properties.bgpPeers = @($BgpPeers) - - return $bgpRouter -} - -function New-NCVirtualGateway -{ - param( - [Parameter(mandatory=$true)] - [string] $resourceID, - [Parameter(mandatory=$true)] - [string[]] $GatewayPools, - [Parameter(mandatory=$true)] - [string] $vNetIPv4SubnetResourceRef, - [Parameter(mandatory=$false)] - [object] $VpnClientAddressSpace, - [Parameter(mandatory=$false)] - [object[]] $NetworkConnections, - [Parameter(mandatory=$false)] - [object[]] $BgpRouters, - [Parameter(mandatory=$false)] - [object[]] $PolicyMaps, - [Parameter(mandatory=$false)] - [string] $RoutingType = "Dynamic" - ) - - $virtualGW = @{} - $virtualGW.resourceID = $resourceID - $virtualGW.properties = @{} - - $virtualGW.properties.gatewayPools = @() - foreach ($gatewayPool in $GatewayPools) - { - - $gwPool = @{} - $gwPool.resourceRef = "/gatewayPools/$gatewayPool" - $virtualGW.properties.gatewayPools += $gwPool - } - - $gatewaySubnetsRef = @{} - $gatewaySubnetsRef.resourceRef = $vNetIPv4SubnetResourceRef - $virtualGW.properties.gatewaySubnets = @() - $virtualGW.properties.gatewaySubnets += $gatewaySubnetsRef - - $virtualGW.properties.vpnClientAddressSpace = @{} - $virtualGW.properties.vpnClientAddressSpace = $VpnClientAddressSpace - - $virtualGW.properties.networkConnections = @() - $virtualGW.properties.networkConnections += @($Networkconnections) - - $virtualGW.properties.bgpRouters = @() - $virtualGW.properties.bgpRouters += $BgpRouters - - $virtualGW.properties.policyMaps = @() - $virtualGW.properties.policyMaps += $PolicyMaps - - $virtualGW.properties.routingType = $RoutingType - - JSONPost $script:NetworkControllerRestIP "/VirtualGateways" $virtualGW -Credential $script:NetworkControllerCred | out-null - return JSONGet $script:NetworkControllerRestIP "/VirtualGateways/$resourceID" -WaitForUpdate -Credential $script:NetworkControllerCred -} -function Get-NCVirtualGateway -{ - param( - [Parameter(mandatory=$false)] - [string] $ResourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/VirtualGateways/$ResourceID" -Credential $script:NetworkControllerCred -} -function Remove-NCVirtualGateway -{ - param( - [Parameter(mandatory=$true)] - [string] $ResourceID - ) - JSONDelete $script:NetworkControllerRestIP "/VirtualGateways/$ResourceId" -Waitforupdate -Credential $script:NetworkControllerCred | out-null -} - -function Add-LoadBalancerToNetworkAdapter -{ - param( - [parameter(mandatory=$false)] - [string] $LoadBalancerResourceID, - [parameter(mandatory=$false)] - [string[]] $VMNicResourceIds - ) - $loadBalancer = Get-NCLoadBalancer -resourceId $LoadBalancerResourceID - $lbbeResourceRef = $loadBalancer.Properties.backendAddressPools.resourceRef - foreach($nicResourceID in $VMNicResourceIds) - { - $nicResource = Get-NCNetworkInterface -resourceid $nicResourceID - $loadBalancerBackendAddressPools = @{} - $loadBalancerBackendAddressPools.resourceRef = $lbbeResourceRef - if(-not $nicResource.properties.ipConfigurations[0].properties.loadBalancerBackendAddressPools) - { - $nicResource.properties.ipConfigurations[0].properties.loadBalancerBackendAddressPools= @() - } - else - { - $found = $false - foreach ($backendAddressPool in $nicResource.properties.ipConfigurations[0].properties.loadBalancerBackendAddressPools) - { - $resourceRef = $backendAddressPool.resourceRef - if ($resourceRef -eq $lbbeResourceRef) - { - $found = $true - break - } - } - - if ($found -eq $true) - { - continue - } - } - $nicResource.properties.ipConfigurations[0].properties.loadBalancerBackendAddressPools += $loadBalancerBackendAddressPools - JSONPost $script:NetworkControllerRestIP "/NetworkInterfaces" $nicResource -Credential $script:NetworkControllerCred | out-null - } -} - - -function Remove-LoadBalancerFromNetworkAdapter -{ - param( - [parameter(mandatory=$false)] - [string[]] $VMNicResourceIds - ) - foreach($nicResourceID in $VMNicResourceIds) - { - $nicResource = Get-NCNetworkInterface -resourceid $nicResourceID - $nicResource.properties.ipConfigurations[0].loadBalancerBackendAddressPools.resourceRef = "" - JSONPost $script:NetworkControllerRestIP "/NetworkInterfaces" $nicResource -Credential $script:NetworkControllerCred | out-null - } -} - -function Get-NCConnectivityCheckResult { - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/diagnostics/ConnectivityCheckResults/$resourceID" -Credential $script:NetworkControllerCred -} -function Get-NCRouteTable { - param( - [Parameter(mandatory=$false)] - [string] $resourceID = "" - ) - return JSONGet $script:NetworkControllerRestIP "/RouteTables/$resourceID" -Credential $script:NetworkControllerCred -} - diff --git a/azure_jumpstart_hcibox/artifacts/SDN/NetworkControllerWorkloadHelpers.psm1 b/azure_jumpstart_hcibox/artifacts/SDN/NetworkControllerWorkloadHelpers.psm1 deleted file mode 100644 index 096f4f24ee..0000000000 --- a/azure_jumpstart_hcibox/artifacts/SDN/NetworkControllerWorkloadHelpers.psm1 +++ /dev/null @@ -1,380 +0,0 @@ -Import-Module ".\NetworkControllerRESTWrappers.ps1" -Force - -function New-ACL -{ - param( - [Parameter(mandatory=$false)] - [String] $ResourceId=[System.Guid]::NewGuid(), - [Parameter(mandatory=$true)] - [object[]] $aclRules - ) - - $ar = @() - foreach ($rule in $aclRules) { - $ar += New-NCAccessControlListRule -Protocol $rule.Protocol -SourcePortRange $rule.SourcePortRange -DestinationPortRange $rule.DestinationPortRange -sourceAddressPrefix $rule.sourceAddressPrefix -destinationAddressPrefix $rule.destinationAddressPrefix -Action $rule.Action -ACLType $rule.Type -Logging $true -Priority $rule.Priority - } - - $acl1 = New-NCAccessControlList -resourceId $ResourceId -AccessControlListRules $ar - return $acl1 -} - -function Get-PortProfileId -{ - param( - [Parameter(mandatory=$true)] - [String] $VMName, - [Parameter(mandatory=$false)] - [String] $VMNetworkAdapterName=$null, - [Parameter(mandatory=$false)] - [String] $ComputerName="localhost" - ) - write-verbose ("Getting port profile for [$vmname] on [$computername]" ) - - try - { - $pssession = new-pssession -ComputerName $computername - - invoke-command -session $pssession -ScriptBlock { - if ([String]::IsNullOrEmpty($using:VMNetworkAdapterName)) - { - $vmNics = Get-VMNetworkAdapter -VMName $using:VMName - } - else - { - $vmNics = @(Get-VMNetworkAdapter -VMName $using:VMName -Name $using:VMNetworkAdapterName) - } - - $result = @() - - foreach ($vmNic in $vmNics) { - $currentProfile = Get-VMSwitchExtensionPortFeature -FeatureId "9940cd46-8b06-43bb-b9d5-93d50381fd56" -VMNetworkAdapter $vmNic - if ( $currentProfile -eq $null) - { - $result += $null - } - else - { - $result += [system.guid]::parse($currentProfile.SettingData.ProfileId).tostring() - } - } - return $result - } - } - catch - { - Write-Error "Failed with error: $_" - } - finally - { - Remove-PSSession $pssession - } -} - -function get-MacAddress -{ - param( - [Parameter(mandatory=$true)] - [String] $VMName, - [Parameter(mandatory=$false)] - [String] $VMNetworkAdapterName=$null, - [Parameter(mandatory=$false)] - [String] $ComputerName="localhost" - ) - write-verbose ("Getting mac address for [$vmname] on [$computername]" ) - - try - { - $pssession = new-pssession -ComputerName $computername - - invoke-command -session $pssession -ScriptBlock { - if ([String]::IsNullOrEmpty($using:VMNetworkAdapterName)) - { - $vmNics = Get-VMNetworkAdapter -VMName $using:VMName - } - else - { - $vmNics = @(Get-VMNetworkAdapter -VMName $using:VMName -Name $using:VMNetworkAdapterName) - } - - $result = @() - - foreach ($vmNic in $vmNics) { - $result += $VMNic.MacAddress - } - return $result - } - } - catch - { - Write-Error "Failed with error: $_" - } - finally - { - Remove-PSSession $pssession - } -} - -function Add-NetworkAdapterToNetwork -{ - param( - [Parameter(mandatory=$true,ParameterSetName="ByVNIC")] - [String] $VMName, - [Parameter(mandatory=$false,ParameterSetName="ByVNIC")] - [String] $VMNetworkAdapterName = $null, - [Parameter(mandatory=$true,ParameterSetName="ByVNIC")] - [String] $ComputerName, - [Parameter(mandatory=$false,ParameterSetName="ByVNIC")] - [Parameter(mandatory=$true,ParameterSetName="ByResourceId")] - [String] $NetworkInterfaceResourceId="", - [Parameter(mandatory=$true,ParameterSetName="ByVNIC")] - [Parameter(mandatory=$true,ParameterSetName="ByResourceId")] - [Object] $LogicalNetworkResourceId="", - [Parameter(mandatory=$true,ParameterSetName="ByVNIC")] - [Parameter(mandatory=$true,ParameterSetName="ByResourceId")] - [String] $SubnetAddressPrefix="", - [Parameter(mandatory=$false,ParameterSetName="ByVNIC")] - [Parameter(mandatory=$false,ParameterSetName="ByResourceId")] - [String] $ACLResourceId=$null, - [Parameter(mandatory=$false,ParameterSetName="ByVNIC")] - [Parameter(mandatory=$false,ParameterSetName="ByResourceId")] - [String] $IPAddress="" - ) - - if ($psCmdlet.ParameterSetName -eq "ByVNIC") { - $NetworkInterfaceInstanceId = Get-PortProfileId -vmname $vmname -vmnetworkadaptername $VMNetworkAdapterName -computername $computername - $NetworkInterfaceResourceId = Get-NCNetworkInterfaceResourceId -InstanceId $NetworkInterfaceInstanceId - } - - $mac = get-macaddress -vmname $vmname -VMNetworkAdapterName $VMNetworkAdapterName -ComputerName $ComputerName - - if ($mac.count -gt 1) { - throw "More than one MACaddress found on VM. You must specify VMNetworkAdapterName if more than one network adapter is present on the VM." - } - - $ln = Get-NCLogicalNetwork -ResourceId $LogicalNetworkResourceId - - foreach ($lnsubnet in $ln.properties.subnets) { - if ($subnetaddressprefix -eq $lnsubnet.properties.Addressprefix) { - $subnet = $lnsubnet - } - } - - if (([String]::IsNullOrEmpty($NetworkInterfaceResourceId)) -or ($NetworkInterfaceResourceId -eq "") -or ($NetworkInterfaceResourceId -eq [System.Guid]::Empty)) { - $NetworkInterfaceResourceId = [System.Guid]::NewGuid() - } - $nic = get-ncnetworkinterface -resourceID $NetworkInterfaceResourceId - - if ($nic -ne $null -and !$Force) { - throw "Network interface [$networkinterfaceresourceid] already exists. Use -Force to replace it." - } - - #TODO: add acl if specified - if (![String]::IsNullOrEmpty($ACLResourceId)) { - $acl = Get-NCAccessControlList -resourceID $ACLResourceId - if ($acl -eq $null) { - throw "ACL with resource id $aclresourceid was not found on the network controller." - } - $nic = New-NCNetworkInterface -resourceId $NetworkInterfaceResourceId -Subnet $subnet -MACAddress $mac -acl $acl -ipaddress $ipaddress - } else { - $nic = New-NCNetworkInterface -resourceId $NetworkInterfaceResourceId -Subnet $subnet -MACAddress $mac -ipaddress $ipaddress - } - set-portprofileid -resourceID $nic.instanceid -VMName $vmname -VMNetworkAdapterName $vmnetworkadaptername -computername $computername -Force - - return $nic - - #TODO: add virtual server for topology -} - -function Unblock-NetworkAdapter -{ - param( - [Parameter(mandatory=$true,ParameterSetName="ByVNIC")] - [String] $VMName, - [Parameter(mandatory=$false,ParameterSetName="ByVNIC")] - [String] $VMNetworkAdapterName = $null, - [Parameter(mandatory=$true,ParameterSetName="ByVNIC")] - [String] $ComputerName - ) - - if ($psCmdlet.ParameterSetName -eq "ByVNIC") { - $NetworkInterfaceInstanceId = Get-PortProfileId $vmname $VMNetworkAdapterName $computername - $NetworkInterfaceResourceId = Get-NCNetworkInterfaceResourceId -InstanceId $NetworkInterfaceInstanceId - } - - #if networkadapter exists, remove it - remove-ncnetworkinterface -resourceid $NetworkInterfaceResourceId - - #remove-ncvirtualserver -resourceid $vsresourceid - - set-portprofileid -resourceID ([guid]::empty) -VMName $vmname -ComputerName $computername -Force -} - - -function Remove-NetworkAdapterFromNetwork -{ - param( - [Parameter(mandatory=$true,ParameterSetName="ByVNIC")] - [String] $VMName, - [Parameter(mandatory=$false,ParameterSetName="ByVNIC")] - [String] $VMNetworkAdapterName = $null, - [Parameter(mandatory=$true,ParameterSetName="ByVNIC")] - [String] $ComputerName, - [Parameter(mandatory=$false,ParameterSetName="ByVNIC")] - [Parameter(mandatory=$true,ParameterSetName="ByResourceId")] - [String] $NetworkInterfaceResourceId="" - ) - - if ($psCmdlet.ParameterSetName -eq "ByVNIC") { - $NetworkInterfaceInstanceId = Get-PortProfileId $vmname $VMNetworkAdapterName $computername - $NetworkInterfaceResourceId = Get-NCNetworkInterfaceResourceId -InstanceId $NetworkInterfaceInstanceId - } - - if($NetworkInterfaceResourceId) - { - remove-ncnetworkinterface -resourceid $NetworkInterfaceResourceId - } - - $nullguid = $([System.Guid]::Empty) - set-portprofileid -ResourceID $nullguid -vmname $vmname -VMNetworkAdapterName $VMNetworkAdapterName -ComputerName $computername -force - #remove-ncvirtualserver -resourceid $vsresourceid -} - -function Set-NetworkAdapterACL -{ - param( - [Parameter(mandatory=$true,ParameterSetName="ByVNIC")] - [String] $VMName, - [Parameter(mandatory=$false,ParameterSetName="ByVNIC")] - [String] $VMNetworkAdapterName = $null, - [Parameter(mandatory=$true,ParameterSetName="ByVNIC")] - [String] $ComputerName, - [Parameter(mandatory=$true,ParameterSetName="ByResourceId")] - [String] $NetworkInterfaceResourceId, - [Parameter(mandatory=$true)] - [String] $ACLResourceId - ) - - if ($psCmdlet.ParameterSetName -eq "ByVNIC") { - $NetworkInterfaceInstanceId = Get-PortProfileId $vmname $VMNetworkAdapterName $computername - if ($NetworkInterfaceInstanceId -eq $null) { - throw "Could not find port profile id. Either $vmname does not exist on $computername, or it does not have a port profile defined which would indicate that it has not been added to the network controller." - } - - if ($NetworkInterfaceInstanceId -ne [System.Guid]::Empty) - { - $NetworkInterfaceResourceId = Get-NCNetworkInterfaceResourceId -InstanceId $NetworkInterfaceInstanceId - } - } - - $nic = get-ncnetworkinterface -resourceid $NetworkInterfaceResourceId - - if ($nic -eq $null) { - throw "ACL can't be set because a network interface was not found for port profile id $NetworkInterfaceResourceId in the network controller." - } - - $acl = Get-NCAccessControlList -resourceID $ACLResourceId - if ($acl -eq $null) { - throw "ACL with resource id $aclresourceid was not found on the network controller." - } - - $nic.properties.ipConfigurations[0].properties | add-member -Name "accessControlList" -MemberType NoteProperty -Value @{ resourceRef = $acl.resourceRef } - - JSONPost -path "/NetworkInterfaces" -bodyObject $nic -} - -function New-LoadBalancerVIP -{ -param( - [Parameter(mandatory=$false)] - [Microsoft.HyperV.PowerShell.VirtualMachine[]]$VMPool = $n, - [Parameter(mandatory=$true)] - [string]$Vip, - [Parameter(mandatory=$false)] - [string]$protocol="TCP", - [Parameter(mandatory=$true)] - [int] $frontendPort, - [Parameter(mandatory=$false)] - [int] $backendPort=$frontendport, - [Parameter(mandatory=$false)] - [Switch] $EnableOutboundNat, - [parameter(mandatory=$false)] - [string] $LoadBalancerResourceID=[system.guid]::NewGuid() -) - $slbm = get-ncloadbalancermanager - - if ($slbm.properties.vipippools.count -lt 1) { - throw "New-LoadBalancerVIP requires at least one VIP pool in the NC Load balancer manager." - } - - $vipPools = $slbm.properties.vipippools - - # check if the input VIP is within range of one of the VIP pools - foreach ($vippool in $vipPools) { - # IP pool's resourceRef is in this format: - # /logicalnetworks/f8f67956-3906-4303-94c5-09cf91e7e311/subnets/aaf28340-30fe-4f27-8be4-40eca97b052d/ipPools/ed48962b-2789-41bf-aa7b-3e6d5b247384 - $sp = $vippool.resourceRef.split("/") - - $ln = Get-NCLogicalNetwork -resourceId $sp[2] #LN resourceid is always the first ID (after /logicalnetwork/) - if (-not $ln) { - throw "Can't find logical network with resourceId $($sp[2]) from NC." - } - - $subnet = $ln.properties.subnets | ? {$_.resourceId -eq $sp[4]} - if (-not $subnet) { - throw "can't find subnet with resourceId $($sp[4]) from NC." - } - - $pool = $subnet.properties.ipPools | ? {$_.resourceId -eq $sp[6]} - if (-not $pool) { - throw "can't find IP pool with resourceId $($sp[6]) from NC." - } - - $startIp = $pool.properties.startIpAddress - $endIp = $pool.properties.endIpAddress - if (IsIpWithinPoolRange -targetIp $Vip -startIp $startIp -endIp $endIp) { - $isPoolPublic = $subnet.properties.isPublic - $vipLn = $ln - break; - } - } - - if (-not $vipLn) { - throw "$Vip is not within range of any of the VIP pools managed by SLB manager." - } - - # if the VIP is within the range of a pool whose subnet is public, we should create a PublicIPAddress in NC for the VIP - # so that NC won't accidentially allocate same VIP for tenant use - if ($isPoolPublic) { - $publicIp = Get-NCPublicIPAddress | ? {$_.properties.ipaddress -eq $Vip} - if ($publicIp -eq $null) { - New-NCPublicIPAddress -PublicIPAddress $Vip - } - } - - $lbfe = @(New-NCLoadBalancerFrontEndIPConfiguration -PrivateIPAddress $Vip -Subnet ($vipLn.properties.Subnets[0])) - - $ips = @() - foreach ($VM in $VMPool) { - $vm_name = $VM.Name - $vm_computer = $VM.ComputerName - $vm_nic = $VM.NetworkAdapters[0].Name - $instanceid = Get-PortProfileId -VMName $vm_name -VMNetworkAdapterName $vm_nic -ComputerName $vm_computer - - #convert port profile id to from instance id to resource id - $ppid = $NetworkInterfaceResourceId = Get-NCNetworkInterfaceResourceId -InstanceId $instanceid - - $vnic = get-ncnetworkinterface -resourceId $ppid - $ips += $vnic.properties.ipConfigurations[0] - } - - $lbbe = @(New-NCLoadBalancerBackendAddressPool -IPConfigurations $ips) - $rules = @(New-NCLoadBalancerLoadBalancingRule -protocol $protocol -frontendPort $frontendPort -backendport $backendPort -enableFloatingIP $False -frontEndIPConfigurations $lbfe -backendAddressPool $lbbe) - - if ($EnableOutboundNat) { - $onats = @(New-NCLoadBalancerOutboundNatRule -frontendipconfigurations $lbfe -backendaddresspool $lbbe) - $lb = New-NCLoadBalancer -ResourceID $LoadBalancerResourceID -frontendipconfigurations $lbfe -backendaddresspools $lbbe -loadbalancingrules $rules -outboundnatrules $onats - } else { - $lb = New-NCLoadBalancer -ResourceID $LoadBalancerResourceID -frontendipconfigurations $lbfe -backendaddresspools $lbbe -loadbalancingrules $rules - } - return $lb -} diff --git a/azure_jumpstart_hcibox/artifacts/SDN/SDNExplorer.ps1 b/azure_jumpstart_hcibox/artifacts/SDN/SDNExplorer.ps1 deleted file mode 100644 index 18a6a73952..0000000000 --- a/azure_jumpstart_hcibox/artifacts/SDN/SDNExplorer.ps1 +++ /dev/null @@ -1,3269 +0,0 @@ - -param( - [string] - $NCIP = "restserver", - [object] - $NCCredential= [System.Management.Automation.PSCredential]::Empty, - [bool] - $EnableMultiWindow = $true, - [bool] - $IsModule = $false -) - -function GenerateMainForm { - -param( - [Parameter(mandatory=$true)] - [object[]] $DataArr - ) - - [reflection.assembly]::loadwithpartialname(“System.Windows.Forms”) | Out-Null - [reflection.assembly]::loadwithpartialname(“System.Drawing”) | Out-Null - #endregion - - #region Generated Form Objects - $ExplorerForm = New-Object System.Windows.Forms.Form - $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState - #endregion Generated Form Objects - - - $OnLoadForm_StateCorrection= - {#Correct the initial state of the form to prevent the .Net maximized form issue - $ExplorerForm.WindowState = $InitialFormWindowState - } - - #———————————————- - #region Generated Form Code - $ExplorerForm.Text = “SDN Explorer NC:$NCIP” - $ExplorerForm.Name = “SDN Explorer NC:$NCIP” - $ExplorerForm.DataBindings.DefaultDataSourceUpdateMode = 0 - - # panel to have scroll bar - $panel = New-Object System.Windows.Forms.Panel - $panel.BackColor = [System.Drawing.Color]::Silver - $panel.BorderStyle = [System.Windows.Forms.BorderStyle]::FixedSingle - $panel.Location = New-Object System.Drawing.Point (5,5) - - - $System_Drawing_Point = New-Object System.Drawing.Point - $System_Drawing_Point.X = 80 - $System_Drawing_Point.Y = 20 - for ($it = 0; $it -lt $DataArr.Count ;$it++) - { - $button = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 240 - $System_Drawing_SizeButton.Height = 23 - $button.TabIndex = 0 - $button.Name = “UnlockAccountButton” - $button.Size = $System_Drawing_SizeButton - $button.UseVisualStyleBackColor = $True - $button.Text = $DataArr[$it].Name - $button.Location = $System_Drawing_Point - $button.DataBindings.DefaultDataSourceUpdateMode = 0 - $scriptBlock = $DataArr[$it].Value[0] - $button.add_Click($scriptBlock) - $panel.Controls.Add($button) - - - $putButton = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 60 - $System_Drawing_SizeButton.Height = 23 - $putButton.TabIndex = 0 - $putButton.Name = "Put” - $putButton.Size = $System_Drawing_SizeButton - $putButton.UseVisualStyleBackColor = $True - $putButton.Text = "Put" - $putButton.Location = New-Object System.Drawing.Size(340,$System_Drawing_Point.Y) - $putButton.DataBindings.DefaultDataSourceUpdateMode = 0 - if($DataArr[$it].Value.Count -gt 1) - { - $scriptBlock = $DataArr[$it].Value[1] - } - else - { - $putButton.Enabled = $false - } - $putButton.add_Click($scriptBlock) - $panel.Controls.Add($putButton) - - - $DeleteButton = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 60 - $System_Drawing_SizeButton.Height = 23 - $DeleteButton.TabIndex = 0 - $DeleteButton.Name = "Delete” - $DeleteButton.Size = $System_Drawing_SizeButton - $DeleteButton.UseVisualStyleBackColor = $True - $DeleteButton.Text = "Delete" - $DeleteButton.Location = New-Object System.Drawing.Size(420,$System_Drawing_Point.Y) - $DeleteButton.DataBindings.DefaultDataSourceUpdateMode = 0 - if($DataArr[$it].Value.Count -gt 2) - { - $scriptBlock = $DataArr[$it].Value[2] - } - else - { - $DeleteButton.Enabled = $false - } - $DeleteButton.add_Click($scriptBlock) - $panel.Controls.Add($DeleteButton) - - - $System_Drawing_Point.Y += 33 - } - - - if($System_Drawing_Point.Y -ge 700) - { - $yPoint = 700 - } - else - { - $yPoint = $System_Drawing_Point.Y + 50 - } - - $System_Drawing_Size = New-Object System.Drawing.Size - $System_Drawing_Size.Width = 560 - $System_Drawing_Size.Height = $yPoint - $ExplorerForm.ClientSize = $System_Drawing_Size - - $System_Drawing_Size.Width -= 10 - $System_Drawing_Size.Height -= 10 - $panel.Size = $System_Drawing_Size - $panel.AutoScroll = $true - $ExplorerForm.Controls.Add($panel) - - #endregion Generated Form Code - - #Save the initial state of the form - $InitialFormWindowState = $ExplorerForm.WindowState - #Init the OnLoad event to correct the initial state of the form - $ExplorerForm.add_Load($OnLoadForm_StateCorrection) - #Show the Form - $ExplorerForm.ShowDialog()| Out-Null - -} - -function GenerateArrayForm { - -param( - [Parameter(mandatory=$true)] - [string] $HandlerFunc, - - [Parameter(mandatory=$true)] - [string] $RemoveFunc, - - [Parameter(mandatory=$true)] - [string] $NCIP, - - [Parameter(mandatory=$false)] - [object] - $NCCredential= [System.Management.Automation.PSCredential]::Empty, - - [Parameter(mandatory=$true)] - [bool] - $EnableMultiWindow=$true - ) - - if ($HandlerFunc -eq "Get-NCConnectivityCheckResult" -and $script:ncVMCredentials -eq [System.Management.Automation.PSCredential]::Empty) - { - $script:ncVMCredentials = Get-Credential -Message "Please give administrator credential of NC" -UserName "Administrator" - } - - - if($EnableMultiWindow) - { - $progress = [powershell]::create() - - $progressScript = { - param( - [string] $HandlerFunc, - [string] $RemoveFunc, - [string] $NCIP, - [object] $NCCredential= [System.Management.Automation.PSCredential]::Empty, - [bool] $EnableMultiWindow=$true, - [string] $CurWorDir, - [object] $NCVMCredential= [System.Management.Automation.PSCredential]::Empty - ) - - try{ - - Set-Location $CurWorDir - Import-Module .\SDNExplorer.ps1 -ArgumentList $NCIP,$NCCredential,$true,$true - GenerateArrayFormHelper -HandlerFunc $HandlerFunc -RemoveFunc $RemoveFunc -NCIP $NCIP -NCCredential $NCCredential -EnableMultiWindow $EnableMultiWindow -NCVMCredential $NCVMCredential - } - catch - { - [System.Windows.Forms.MessageBox]::Show($_) - } - } - - $parameters = @{} - $parameters.HandlerFunc = $HandlerFunc - $parameters.RemoveFunc = $RemoveFunc - $parameters.NCIP = $NCIP - $parameters.NCCredential = $NCCredential - $parameters.EnableMultiWindow = $EnableMultiWindow - $parameters.CurWorDir = $pwd - $parameters.NCVMCredential = $script:ncVMCredentials - - $progress.AddScript($progressScript) - $progress.AddParameters($parameters) - $progress.BeginInvoke() - - } - else{ - GenerateArrayFormHelper -HandlerFunc $HandlerFunc -RemoveFunc $RemoveFunc -NCIP $NCIP -NCCredential $NCCredential -EnableMultiWindow $EnableMultiWindow -NCVMCredential $script:ncVMCredentials - } - -} - -function GenerateArrayFormHelper { - -param( - [Parameter(mandatory=$true)] - [string] $HandlerFunc, - - [Parameter(mandatory=$true)] - [string] $RemoveFunc, - - [Parameter(mandatory=$true)] - [string] $NCIP, - - [Parameter(mandatory=$false)] - [object] - $NCCredential= [System.Management.Automation.PSCredential]::Empty, - - [Parameter(mandatory=$true)] - [bool] - $EnableMultiWindow=$true, - - [Parameter(mandatory=$false)] - [object] - $NCVMCredential= [System.Management.Automation.PSCredential]::Empty - ) - - . .\NetworkControllerRESTWrappers.ps1 -ComputerName $NCIP -Username $null -Password $null -Credential $Script:NCCredential - - [reflection.assembly]::loadwithpartialname(“System.Windows.Forms”) | Out-Null - [reflection.assembly]::loadwithpartialname(“System.Drawing”) | Out-Null - #endregion - - #region Generated Form Objects - $ExplorerForm = New-Object System.Windows.Forms.Form - $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState - #endregion Generated Form Objects - - - $OnLoadForm_StateCorrection= - {#Correct the initial state of the form to prevent the .Net maximized form issue - $ExplorerForm.WindowState = $InitialFormWindowState - } - - #———————————————- - #region Generated Form Code - $ExplorerForm.Text = “$HandlerFunc NC:$NCIP” - $ExplorerForm.Name = “$HandlerFunc NC:$NCIP” - $ExplorerForm.DataBindings.DefaultDataSourceUpdateMode = 0 - - # panel to have scroll bar - $panel = New-Object System.Windows.Forms.Panel - $panel.BackColor = [System.Drawing.Color]::Silver - $panel.BorderStyle = [System.Windows.Forms.BorderStyle]::FixedSingle - $panel.Location = New-Object System.Drawing.Point (5,5) - - $extraSpace = 0 - - $dataArr = @() - $dataArr += &$HandlerFunc - - $failed = $false - foreach ($data in $dataArr) - { - if ($data.PSobject.Properties.name -match "nextLink") - { - $failed = $true - break; - } - } - - $System_Drawing_Point = New-Object System.Drawing.Point - $System_Drawing_Point.X = 80 - $System_Drawing_Point.Y = 20 - - if ($HandlerFunc -eq "Get-NCConnectivityCheckResult") - { - $diagButton = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 240 - $System_Drawing_SizeButton.Height = 23 - $diagButton.TabIndex = 0 - $diagButton.Name = $data.resourceRef - $diagButton.Size = $System_Drawing_SizeButton - $diagButton.UseVisualStyleBackColor = $True - $diagButton.Text = "PUT=>/diagnostics/networkcontrollerstate" - $diagButton.Location = $System_Drawing_Point - $diagButton.DataBindings.DefaultDataSourceUpdateMode = 0 - $diagButton_add = $diagButton.add_Click - $diagButton_add.Invoke({ - - try - { - $verify = VerifyUserAction -String "Do you want to Put networkcontrollerstate?" - - if ($verify -eq $true) - { - $object = @{} - $object.properties = @{} - JSONPost -path "/diagnostics/networkcontrollerstate" -bodyObject $object - [System.Windows.Forms.MessageBox]::Show("Posted!!!") - } - } - catch - { - [System.Windows.Forms.MessageBox]::Show($_) - } - }) - $panel.Controls.Add($diagButton) - $System_Drawing_Point.Y += 33 - - - # Adding network controller statistics button - $ncStatsButton = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 240 - $System_Drawing_SizeButton.Height = 23 - $ncStatsButton.TabIndex = 1 - $ncStatsButton.Name = $data.resourceRef - $ncStatsButton.Size = $System_Drawing_SizeButton - $ncStatsButton.UseVisualStyleBackColor = $True - $ncStatsButton.Text = "GET=>/monitoring/networkcontrollerstatistics" - $ncStatsButton.Location = $System_Drawing_Point - $ncStatsButton.DataBindings.DefaultDataSourceUpdateMode = 0 - $ncStatsButton_add = $ncStatsButton.add_Click - $ncStatsButton_add.Invoke({ - param([object]$sender) - - if($EnableMultiWindow) - { - $ps = [powershell]::create() - - $script = { - param( - [string] $ResourceRef, - - [string] $NCIP, - - [object]$NCCredential= [System.Management.Automation.PSCredential]::Empty, - - [string]$CurWorDir, - - [bool]$EnableMultiWindow - - ) - try{ - - Set-Location $CurWorDir - Import-Module -Force -Name .\SDNExplorer.ps1 -ArgumentList $NCIP,$NCCredential,$true,$true - JsonForm -ResourceRef $ResourceRef -NCIP $NCIP -NCCredential $NCCredential -EnableMultiWindow $true - } - catch{ - [System.Windows.Forms.MessageBox]::Show($_) - } - } - $parameters = @{} - $parameters.ResourceRef = "/monitoring/networkcontrollerstatistics" - $parameters.NCIP = $NCIP - $parameters.NCCredential = $NCCredential - $parameters.CurWorDir = $pwd - $parameters.EnableMultiWindow = $EnableMultiWindow - - $ps.AddScript( - $script - ) - $ps.AddParameters($parameters) - $ps.BeginInvoke() - } - else - { - JsonForm -ResourceRef $sender.Text -NCIP $NCIP -NCCredential $NCCredential - } - - }) - $panel.Controls.Add($ncStatsButton) - $System_Drawing_Point.Y += 33 - - $debugSFNSButton = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 240 - $System_Drawing_SizeButton.Height = 23 - $debugSFNSButton.TabIndex = 0 - $debugSFNSButton.Name = "Debug-ServiceFabricNodeStatus" - $debugSFNSButton.Size = $System_Drawing_SizeButton - $debugSFNSButton.UseVisualStyleBackColor = $True - $debugSFNSButton.Text = "Debug-ServiceFabricNodeStatus" - $debugSFNSButton.Location = $System_Drawing_Point - $debugSFNSButton.DataBindings.DefaultDataSourceUpdateMode = 0 - $debugSFNSButton_add = $debugSFNSButton.add_Click - $debugSFNSButton.Enabled = $false - $debugSFNSButton_add.Invoke({ - - if ($EnableMultiWindow) - { - $ps = [powershell]::create() - - $script = { - param( - - [string]$Cmdlet, - - [object]$NCVMCredential= [System.Management.Automation.PSCredential]::Empty, - - [string] $NCIP, - - [object]$NCCredential= [System.Management.Automation.PSCredential]::Empty, - - [string]$CurWorDir, - - [bool]$EnableMultiWindow - ) - - try{ - Set-Location $CurWorDir - Import-Module .\SDNExplorer.ps1 -ArgumentList $NCIP,$NCCredential,$true,$true - RunNCCMDLet -CmdLet $Cmdlet -NCVMCred $NCVMCredential -NCIP $NCIP - } - catch{ - [System.Windows.Forms.MessageBox]::Show($_) - } - } - $parameters = @{} - $parameters.Cmdlet = "Debug-ServiceFabricNodeStatus" - $parameters.NCVMCredential = $NCVMCredential - $parameters.NCIP = $NCIP - $parameters.NCCredential = $NCCredential - $parameters.CurWorDir = $pwd - $parameters.EnableMultiWindow = $EnableMultiWindow - $ps.AddScript( - $script - ) - $ps.AddParameters($parameters) - $ps.BeginInvoke() - } - else - { - RunNCCMDLet -CmdLet "Debug-ServiceFabricNodeStatus" -NCVMCred $NCVMCredential -NCIP $NCIP - } - }) - $panel.Controls.Add($debugSFNSButton) - $System_Drawing_Point.Y += 33 - - $debugNCCSButton = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 240 - $System_Drawing_SizeButton.Height = 23 - $debugNCCSButton.TabIndex = 0 - $debugNCCSButton.Name = "Debug-NetworkControllerConfigurationState" - $debugNCCSButton.Size = $System_Drawing_SizeButton - $debugNCCSButton.UseVisualStyleBackColor = $True - $debugNCCSButton.Text = "Debug-NetworkControllerConfigurationState" - $debugNCCSButton.Location = $System_Drawing_Point - $debugNCCSButton.DataBindings.DefaultDataSourceUpdateMode = 0 - $debugNCCSButton_add = $debugNCCSButton.add_Click - $debugNCCSButton_add.Invoke({ - - if ($EnableMultiWindow) - { - $ps = [powershell]::create() - - $script = { - param( - - [string]$Cmdlet, - - [object]$NCVMCredential= [System.Management.Automation.PSCredential]::Empty, - - [string] $NCIP, - - [object]$NCCredential= [System.Management.Automation.PSCredential]::Empty, - - [string]$CurWorDir, - - [bool]$EnableMultiWindow - ) - - try{ - Set-Location $CurWorDir - Import-Module .\SDNExplorer.ps1 -ArgumentList $NCIP,$NCCredential,$true,$true - RunNCCMDLet -CmdLet $Cmdlet -NCVMCred $NCVMCredential -NCIP $NCIP - } - catch{ - [System.Windows.Forms.MessageBox]::Show($_) - } - } - $parameters = @{} - $parameters.Cmdlet = "Debug-NetworkControllerConfigurationState" - $parameters.NCVMCredential = $NCVMCredential - $parameters.NCIP = $NCIP - $parameters.NCCredential = $NCCredential - $parameters.CurWorDir = $pwd - $parameters.EnableMultiWindow = $EnableMultiWindow - $ps.AddScript( - $script - ) - $ps.AddParameters($parameters) - $ps.BeginInvoke() - } - else - { - RunNCCMDLet -CmdLet "Debug-NetworkControllerConfigurationState" -NCVMCred $NCVMCredential -NCIP $NCIP - } - }) - $panel.Controls.Add($debugNCCSButton) - $System_Drawing_Point.Y += 33 - - $debugNCButton = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 240 - $System_Drawing_SizeButton.Height = 23 - $debugNCButton.TabIndex = 0 - $debugNCButton.Name = "Debug-NetworkController" - $debugNCButton.Size = $System_Drawing_SizeButton - $debugNCButton.UseVisualStyleBackColor = $True - $debugNCButton.Text = "Debug-NetworkController" - $debugNCButton.Location = $System_Drawing_Point - $debugNCButton.DataBindings.DefaultDataSourceUpdateMode = 0 - $debugNCButton_add = $debugNCButton.add_Click - $debugNCButton_add.Invoke({ - - if ($EnableMultiWindow) - { - $ps = [powershell]::create() - - $script = { - param( - - [string]$Cmdlet, - - [object]$NCVMCredential= [System.Management.Automation.PSCredential]::Empty, - - [string] $NCIP, - - [object]$NCCredential= [System.Management.Automation.PSCredential]::Empty, - - [string]$CurWorDir, - - [bool]$EnableMultiWindow - ) - - try{ - Set-Location $CurWorDir - Import-Module .\SDNExplorer.ps1 -ArgumentList $NCIP,$NCCredential,$true,$true - RunNCCMDLet -CmdLet $Cmdlet -NCVMCred $NCVMCredential -NCIP $NCIP - } - catch{ - [System.Windows.Forms.MessageBox]::Show($_) - } - } - $parameters = @{} - $parameters.Cmdlet = "Debug-NetworkController" - $parameters.NCVMCredential = $NCVMCredential - $parameters.NCIP = $NCIP - $parameters.NCCredential = $NCCredential - $parameters.CurWorDir = $pwd - $parameters.EnableMultiWindow = $EnableMultiWindow - $ps.AddScript( - $script - ) - $ps.AddParameters($parameters) - $ps.BeginInvoke() - } - else - { - RunNCCMDLet -CmdLet "Debug-NetworkController" -NCVMCred $NCVMCredential -NCIP $NCIP - } - - [System.Windows.Forms.MessageBox]::Show("Started your request in background!!") - }) - $panel.Controls.Add($debugNCButton) - $System_Drawing_Point.Y += 33 - - } - elseif ($HandlerFunc -eq "Get-NCServer") - { - $runButton = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 280 - $System_Drawing_SizeButton.Height = 23 - $runButton.TabIndex = 0 - $runButton.Name = $data.resourceRef - $runButton.Size = $System_Drawing_SizeButton - $runButton.UseVisualStyleBackColor = $True - $runButton.Text = "--Run Script Block--" - $locationY = $System_Drawing_Point.Y + 1 - $runButton.Location = New-Object System.Drawing.Size(10,$locationY) - $runButton.DataBindings.DefaultDataSourceUpdateMode = 0 - $runButton_add = $runButton.add_Click - $runButton_add.Invoke({ - if ($EnableMultiWindow) - { - $ps = [powershell]::create() - - $script = { - param( - - [object[]]$jsonObject, - - [string]$CurWorDir, - - [bool]$EnableMultiWindow - ) - - try{ - Set-Location $CurWorDir - Import-Module .\SDNExplorer.ps1 -ArgumentList $NCIP,$NCCredential,$true,$true - RunScriptForm -Servers $jsonObject - } - catch{ - [System.Windows.Forms.MessageBox]::Show($_) - } - } - $parameters = @{} - $parameters.jsonObject = $dataArr - $parameters.CurWorDir = $pwd - $parameters.EnableMultiWindow = $EnableMultiWindow - $ps.AddScript( - $script - ) - $ps.AddParameters($parameters) - $ps.BeginInvoke() - } - else - { - RunScriptForm -Servers $dataArr - } - }) - $panel.Controls.Add($runButton) - $System_Drawing_Point.Y += 33 - } - - - if ($dataArr.Count -gt 0 -and $failed -eq $false) - { - if ($dataArr.Count -ge 1) - { - if ($DataArr[0].resourceRef.Contains("networkInterfaces") -or $DataArr[0].resourceRef.Contains("loadBalancers")) - { - $extraSpace = 40 - } - } - - foreach ($data in $dataArr) - { - $System_Drawing_Point.X = 10 - $button = New-Object System.Windows.Forms.Button - $button.TabIndex = 0 - $button.Name = $data.resourceRef - $button.Size = New-Object System.Drawing.Size(280,23) - $button.UseVisualStyleBackColor = $True - $button.Text = $data.resourceRef - $locationY = $System_Drawing_Point.Y + 1 - $button.Location = New-Object System.Drawing.Size(10,$locationY) - $button.DataBindings.DefaultDataSourceUpdateMode = 0 - $button_add = $button.add_Click - $button_add.Invoke({ - - param([object]$sender,[string]$message) - - if($EnableMultiWindow) - { - $ps = [powershell]::create() - - $script = { - param( - [string] $ResourceRef, - - [string] $NCIP, - - [object]$NCCredential= [System.Management.Automation.PSCredential]::Empty, - - [string]$CurWorDir, - - [bool]$EnableMultiWindow - - ) - try{ - - Set-Location $CurWorDir - Import-Module -Force -Name .\SDNExplorer.ps1 -ArgumentList $NCIP,$NCCredential,$true,$true - JsonForm -ResourceRef $ResourceRef -NCIP $NCIP -NCCredential $NCCredential -EnableMultiWindow $true - } - catch{ - [System.Windows.Forms.MessageBox]::Show($_) - } - } - $parameters = @{} - $parameters.ResourceRef = $sender.Text - $parameters.NCIP = $NCIP - $parameters.NCCredential = $NCCredential - $parameters.CurWorDir = $pwd - $parameters.EnableMultiWindow = $EnableMultiWindow - - $ps.AddScript( - $script - ) - $ps.AddParameters($parameters) - $ps.BeginInvoke() - } - else - { - JsonForm -ResourceRef $sender.Text -NCIP $NCIP -NCCredential $NCCredential - } - - }) - $panel.Controls.Add($button) - $System_Drawing_Point.X += 285 - - if ($data.resourceRef.Contains("networkInterfaces")) - { - $ipBox = New-Object System.Windows.Forms.TextBox - $locationY = $System_Drawing_Point.Y + 1 - $locationX = $System_Drawing_Point.X + 1 - $ipBox.Location = New-Object System.Drawing.Size($locationX,$locationY) - $ipBox.Multiline = $false - $ipBox.WordWrap = $false - $ipBox.Size = New-Object System.Drawing.Size(80,23) - - try - { - $ipBox.Text = $data.properties.ipConfigurations[0].properties.privateIPAddress - } - catch - { - $ipBox.Text = "NULL" - } - $ipBox.Enabled = $false - $panel.Controls.Add($ipBox) - $System_Drawing_Point.X += 85 - } - elseif ($data.resourceRef.Contains("loadBalancers")) - { - $ipBox = New-Object System.Windows.Forms.TextBox - $locationY = $System_Drawing_Point.Y + 1 - $locationX = $System_Drawing_Point.X + 1 - $ipBox.Location = New-Object System.Drawing.Size($locationX,$locationY) - $ipBox.Multiline = $false - $ipBox.WordWrap = $false - $ipBox.Size = New-Object System.Drawing.Size(80,23) - - try - { - $ipBox.Text = $data.properties.frontendIPConfigurations[0].properties.privateIPAddress - } - catch - { - $ipBox.Text = "NULL" - } - $ipBox.Enabled = $false - $panel.Controls.Add($ipBox) - $System_Drawing_Point.X += 85 - } - - $instanceidbox = New-Object System.Windows.Forms.TextBox - $locationY = $System_Drawing_Point.Y + 1 - $locationX = $System_Drawing_Point.X + 1 - $instanceidbox.Location = New-Object System.Drawing.Size($locationX,$locationY) - $instanceidbox.Multiline = $false - $instanceidbox.WordWrap = $false - $instanceidbox.Size = New-Object System.Drawing.Size(210,23) - $instanceidbox.Text = $data.instanceid - $instanceidbox.Enabled = $false - $panel.Controls.Add($instanceidbox) - $System_Drawing_Point.X += 215 - $extraSpace = 100 - - $ProvisioningStateLabel = New-Object System.Windows.Forms.Label - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 80 - $System_Drawing_SizeButton.Height = 21 - $ProvisioningStateLabel.TabIndex = 0 - $ProvisioningStateLabel.Name = "Provisioning State" - $ProvisioningStateLabel.Size = $System_Drawing_SizeButton - $ProvisioningStateLabel.BorderStyle = [System.Windows.Forms.BorderStyle]::FixedSingle - - $provisioningState = "" - if ($data.resourceRef.Contains("connectivityCheckResults")) - { - $provisioningState = $data.properties.result.status - switch($data.properties.result.status) - { - "Success" { $ProvisioningStateLabel.ForeColor = [System.Drawing.Color]::Green } - "Failure" { $ProvisioningStateLabel.ForeColor = [System.Drawing.Color]::Red } - default { $ProvisioningStateLabel.ForeColor = [System.Drawing.Color]::Yellow } - } - } - else - { - $provisioningState = $data.properties.provisioningState - switch($data.properties.provisioningState) - { - "Succeeded" { $ProvisioningStateLabel.ForeColor = [System.Drawing.Color]::Green } - "Failed" { $ProvisioningStateLabel.ForeColor = [System.Drawing.Color]::Red } - default { $ProvisioningStateLabel.ForeColor = [System.Drawing.Color]::Yellow } - } - } - $ProvisioningStateLabel.BackColor = [System.Drawing.Color]::Silver; - $ProvisioningStateLabel.Text = $provisioningState - $ProvisioningStateLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter - $locationX = $System_Drawing_Point.X + 5 - $ProvisioningStateLabel.Location = New-Object System.Drawing.Size($locationX,($System_Drawing_Point.Y + 1)) - $ProvisioningStateLabel.DataBindings.DefaultDataSourceUpdateMode = 0 - $panel.Controls.Add($ProvisioningStateLabel) - $System_Drawing_Point.X += 85 - - $DeleteButton = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 60 - $System_Drawing_SizeButton.Height = 23 - $DeleteButton.TabIndex = 0 - $DeleteButton.Name = $data.resourceId - $DeleteButton.Size = $System_Drawing_SizeButton - $DeleteButton.UseVisualStyleBackColor = $True - $DeleteButton.Text = "Delete" - $locationX = $System_Drawing_Point.X + 5 - $DeleteButton.Location = New-Object System.Drawing.Size($locationX,$System_Drawing_Point.Y) - $DeleteButton.DataBindings.DefaultDataSourceUpdateMode = 0 - - $DeleteButton_add = $DeleteButton.add_Click - $DeleteButton_add.Invoke({ - param([object]$sender,[string]$message) - - $verify = VerifyUserAction - - if ($verify -eq $true) - { - &$RemoveFunc -ResourceIDs $sender.Name - $ExplorerForm.Close() - } - }) - $panel.Controls.Add($DeleteButton) - $System_Drawing_Point.Y += 33 - } - } - elseif ($HandlerFunc -ne "Get-NCConnectivityCheckResult") - { - [System.Windows.Forms.MessageBox]::Show("Not Configured!!!!") - return; - } - - if($System_Drawing_Point.Y -ge 700) - { - $yPoint = 700 - } - else - { - $yPoint = $System_Drawing_Point.Y + 50 - } - - $System_Drawing_Size = New-Object System.Drawing.Size - $System_Drawing_Size.Width = 600 + ( 2 * $extraSpace) - $System_Drawing_Size.Height = $yPoint - $ExplorerForm.ClientSize = $System_Drawing_Size - - $System_Drawing_Size.Width -= 10 - $System_Drawing_Size.Height -= 10 - $panel.Size = $System_Drawing_Size - $panel.AutoScroll = $true - $ExplorerForm.Controls.Add($panel) - - #endregion Generated Form Code - - #Save the initial state of the form - $InitialFormWindowState = $ExplorerForm.WindowState - #Init the OnLoad event to correct the initial state of the form - $ExplorerForm.add_Load($OnLoadForm_StateCorrection) - #Show the Form - $ExplorerForm.ShowDialog()| Out-Null - -} - -function RemoveObjForm { - -param( - [Parameter(mandatory=$true)] - [string] $HandlerFunc, - - [Parameter(mandatory=$true)] - [string] $GetFunc, - - [Parameter(mandatory=$true)] - [string] $NCIP, - - [Parameter(mandatory=$false)] - [object]$NCCredential= [System.Management.Automation.PSCredential]::Empty - ) - - . .\NetworkControllerRESTWrappers.ps1 -ComputerName $NCIP -Username $null -Password $null -Credential $Script:NCCredential - - try - { - $Allobjects = &$GetFunc - - $resourceIds = @() - $resourceIds += "None" - foreach ($obj in $Allobjects) - { - $resourceIds += $obj.resourceId - } - - $selectedResource = RadioForm -Name "$HandlerFunc" -Values $resourceIds - - if ($selectedResource -ne "None") - { - &$HandlerFunc -ResourceIDs $selectedResource - } - - } - catch - { - [System.Windows.Forms.MessageBox]::Show($_) - } - -} - -function PutNetworkInterface { - -param( - [Parameter(mandatory=$true)] - [string] $HandlerFunc, - - [Parameter(mandatory=$true)] - [string] $NCIP, - - [Parameter(mandatory=$false)] - [object]$NCCredential= [System.Management.Automation.PSCredential]::Empty - ) - - . .\NetworkControllerRESTWrappers.ps1 -ComputerName $NCIP -Username $null -Password $null -Credential $Script:NCCredential - - - try{ - $value = RadioForm -Name "Network type" -Values "Logical Network","Virtual Network" - - if ($value -eq "Logical Network") - { - $networks = Get-NCLogicalNetwork - } - else - { - $networks = Get-NCVirtualNetwork - } - - $networkRefArray = @() - foreach ($network in $networks) - { - $networkRefArray += $network.resourceId - } - - $selectedNetworkId = RadioForm -Name "Network" -Values $networkRefArray - - if ($value -eq "Logical Network") - { - $network = Get-NCLogicalNetwork -ResourceID $selectedNetworkId - } - else - { - $network = Get-NCVirtualNetwork -resourceID $selectedNetworkId - } - - $subnets = @() - foreach ($subnet in $network.properties.subnets) - { - $subnets += $subnet.resourceId - } - - $selectedSubnetId = RadioForm -Name "Subnet" -Values $subnets - - foreach ($snet in $network.properties.subnets) - { - if($snet.resourceId -eq $selectedSubnetId) - { - $subnet = $snet - #break - } - } - - $ip = GetValueForm -Name "IP Address" - if ([string]::IsNullOrEmpty($ip)) - { - throw "Missing IP!!!!" - } - - $mac = GetValueForm -Name "Mac Address" - if ([string]::IsNullOrEmpty($mac)) - { - throw "Missing Mac!!!!" - } - - $DNSServer = GetValueForm -Name "DNS Server" - - $acls = Get-NCAccessControlList - - $aclRes = @() - $aclRes += "None" - foreach ( $aclObj in $acls) - { - $aclRes += $aclObj.resourceId - } - $selectedAclId = RadioForm -Name "Acl" -Values $aclRes - - $acl = $null - foreach ( $aclObj in $acls) - { - if ($aclObj.resourceId -eq $selectedAclId) - { - $acl = $aclObj - } - } - - $resId = [system.guid]::NewGuid() - if ($value -eq "Logical Network") - { - $networkInterface = New-NCNetworkInterface -Subnet $subnet -IPAddress $ip -MACAddress $mac -DNSServers $DNSServer -acl $acl -resourceID $resId - } - else - { - $networkInterface = New-NCNetworkInterface -VirtualSubnet $subnet -IPAddress $ip -MACAddress $mac -DNSServers $DNSServer -acl $acl -resourceID $resId - } - - [System.Windows.Forms.MessageBox]::Show("Done!!!") - - JsonForm -ResourceRef $networkInterface.resourceRef -NCIP $NCIP -NCCredential $NCCredential - - } - catch - { - [System.Windows.Forms.MessageBox]::Show($_) - } - -} - -function PutLoadBalancer { - -param( - [Parameter(mandatory=$true)] - [string] $HandlerFunc, - - [Parameter(mandatory=$true)] - [string] $NCIP, - - [Parameter(mandatory=$false)] - [object]$NCCredential= [System.Management.Automation.PSCredential]::Empty - ) - - Import-Module .\NetworkControllerWorkloadHelpers.psm1 -Force - . .\NetworkControllerRESTWrappers.ps1 -ComputerName $NCIP -Username $null -Password $null -Credential $Script:NCCredential - - - try{ - - $vip = GetValueForm -Name "VIP Address" - if ([string]::IsNullOrEmpty($vip)) - { - throw "Missing VIP!!!!" - } - - $protocol = RadioForm -Name "Protocol" -Values "Tcp","Udp" - - $frontendPort = [int](GetValueForm -Name "Front End Port") - - $backendPort = [int](GetValueForm -Name "Front End Port") - - $enableOutboundNatstr = RadioForm -Name "Enable Outbound Nat" -Values "true","false" - - $enableOutboundNat = $false - if ($enableOutboundNatstr -eq "true") - { - $enableOutboundNat = $true - } - - #just to load all dependencies - $slbm = get-ncloadbalancermanager - - if ($slbm.properties.vipippools.count -lt 1) { - throw "New-LoadBalancerVIP requires at least one VIP pool in the NC Load balancer manager." - } - - $vipPools = $slbm.properties.vipippools - - # check if the input VIP is within range of one of the VIP pools - foreach ($vippool in $vipPools) { - # IP pool's resourceRef is in this format: - # /logicalnetworks/f8f67956-3906-4303-94c5-09cf91e7e311/subnets/aaf28340-30fe-4f27-8be4-40eca97b052d/ipPools/ed48962b-2789-41bf-aa7b-3e6d5b247384 - $sp = $vippool.resourceRef.split("/") - - $ln = Get-NCLogicalNetwork -resourceId $sp[2] #LN resourceid is always the first ID (after /logicalnetwork/) - if (-not $ln) { - throw "Can't find logical network with resourceId $($sp[2]) from NC." - } - - $subnet = $ln.properties.subnets | ? {$_.resourceId -eq $sp[4]} - if (-not $subnet) { - throw "can't find subnet with resourceId $($sp[4]) from NC." - } - - $pool = $subnet.properties.ipPools | ? {$_.resourceId -eq $sp[6]} - if (-not $pool) { - throw "can't find IP pool with resourceId $($sp[6]) from NC." - } - - $startIp = $pool.properties.startIpAddress - $endIp = $pool.properties.endIpAddress - if (IsIpWithinPoolRange -targetIp $vip -startIp $startIp -endIp $endIp) { - $isPoolPublic = $subnet.properties.isPublic - $vipLn = $ln - break; - } - } - - if (-not $vipLn) { - throw "$vip is not within range of any of the VIP pools managed by SLB manager." - } - - $lbfe = @(New-NCLoadBalancerFrontEndIPConfiguration -PrivateIPAddress $vip -Subnet ($vipLn.properties.Subnets[0])) - - $ips = @() - - $lbbe = @(New-NCLoadBalancerBackendAddressPool -IPConfigurations $ips) - $rules = @(New-NCLoadBalancerLoadBalancingRule -protocol $protocol -frontendPort $frontendPort -backendport $backendPort -enableFloatingIP $False -frontEndIPConfigurations $lbfe -backendAddressPool $lbbe) - - $LoadBalancerResourceID = [system.guid]::NewGuid() - - if ($enableOutboundNat) { - $onats = @(New-NCLoadBalancerOutboundNatRule -frontendipconfigurations $lbfe -backendaddresspool $lbbe) - $lb = New-NCLoadBalancer -ResourceID $LoadBalancerResourceID -frontendipconfigurations $lbfe -backendaddresspools $lbbe -loadbalancingrules $rules -outboundnatrules $onats - } else { - $lb = New-NCLoadBalancer -ResourceID $LoadBalancerResourceID -frontendipconfigurations $lbfe -backendaddresspools $lbbe -loadbalancingrules $rules - } - - [System.Windows.Forms.MessageBox]::Show("Done!!!") - - JsonForm -ResourceRef $lb.resourceRef -NCIP $NCIP -NCCredential $NCCredential - - } - catch - { - [System.Windows.Forms.MessageBox]::Show($_) - } - -} - -function RadioForm { - -[OutputType([string])] -param( - [string] $Name, - [string[]] $Values - ) - - - [reflection.assembly]::loadwithpartialname(“System.Windows.Forms”) | Out-Null - [reflection.assembly]::loadwithpartialname(“System.Drawing”) | Out-Null - #endregion - - #region Generated Form Objects - $ExplorerForm = New-Object System.Windows.Forms.Form - $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState - #endregion Generated Form Objects - - - $OnLoadForm_StateCorrection= - {#Correct the initial state of the form to prevent the .Net maximized form issue - $ExplorerForm.WindowState = $InitialFormWindowState - } - - #———————————————- - #region Generated Form Code - $ExplorerForm.Text = “Please select $Name” - $ExplorerForm.Name = “$Name” - $ExplorerForm.DataBindings.DefaultDataSourceUpdateMode = 0 - - # panel to have scroll bar - $panel = New-Object System.Windows.Forms.Panel - $panel.BackColor = [System.Drawing.Color]::Silver - $panel.BorderStyle = [System.Windows.Forms.BorderStyle]::FixedSingle - $panel.Location = New-Object System.Drawing.Point (5,5) - - $System_Drawing_Point = New-Object System.Drawing.Point - $System_Drawing_Point.X = 80 - $System_Drawing_Point.Y = 20 - $radioButtons = @() - - for ($it = 0; $it -lt $Values.Count ;$it++) - { - $radioButton = New-Object System.Windows.Forms.RadioButton - $radioButton.Location = $System_Drawing_Point - $radioButton.Name = $Values[$it] - $radioButton.TabIndex = 5 - $radioButton.Text = $Values[$it] - $radioButton.Size = New-Object System.Drawing.Size(500, 20) - $radioButtons += $radioButton - - if ([string]::IsNullOrEmpty($selectedValue)) - { - $selectedValue = $Values[$it] - $radioButton.Checked = $true - } - - $System_Drawing_Point.Y += 33 - } - - $groupBox1 = New-Object System.Windows.Forms.GroupBox - $groupBox1.Location = New-Object System.Drawing.Point(60, 10) - $groupBox1.Name = "groupBox1 $Name" - $groupBox1.Size = New-Object System.Drawing.Size(500, $System_Drawing_Point.Y) - $groupBox1.TabIndex = 0 - $groupBox1.TabStop = $false - $groupBox1.Text = "Select $Name" - $groupBox1.Controls.AddRange($radioButtons) - $panel.Controls.Add($groupBox1) - $System_Drawing_Point.Y += 33 - - $button = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 240 - $System_Drawing_SizeButton.Height = 23 - $button.TabIndex = 0 - $button.Name = “Select” - $button.Size = $System_Drawing_SizeButton - $button.UseVisualStyleBackColor = $True - $button.Text = "Select $Name" - $button.Location = $System_Drawing_Point - $button.DataBindings.DefaultDataSourceUpdateMode = 0 - #$scriptBlock = $DataArr[$it].Value - - - $button.add_Click( - { - $ExplorerForm.Close() - }) - $panel.Controls.Add($button) - - $System_Drawing_Point.Y += 33 - - if($System_Drawing_Point.Y -ge 700) - { - $yPoint = 700 - } - else - { - $yPoint = $System_Drawing_Point.Y + 50 - } - - $System_Drawing_Size = New-Object System.Drawing.Size - $System_Drawing_Size.Width = 600 - $System_Drawing_Size.Height = $yPoint - $ExplorerForm.ClientSize = $System_Drawing_Size - - $System_Drawing_Size.Width -= 10 - $System_Drawing_Size.Height -= 10 - $panel.Size = $System_Drawing_Size - $panel.AutoScroll = $true - $ExplorerForm.Controls.Add($panel) - - #endregion Generated Form Code - - #Save the initial state of the form - $InitialFormWindowState = $ExplorerForm.WindowState - #Init the OnLoad event to correct the initial state of the form - $ExplorerForm.add_Load($OnLoadForm_StateCorrection) - #Show the Form - $ExplorerForm.ShowDialog()| Out-Null - - foreach ($radioButton in $radioButtons) - { - if ($radioButton.Checked) - { - $selectedValue = $radioButton.Text - } - } - - return $selectedValue - -} #End Function RadioForm - -function GetValueForm { - -param( - [string] $Name - ) - - [reflection.assembly]::loadwithpartialname(“System.Windows.Forms”) | Out-Null - [reflection.assembly]::loadwithpartialname(“System.Drawing”) | Out-Null - #endregion - - #region Generated Form Objects - $ExplorerForm = New-Object System.Windows.Forms.Form - $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState - $selectedValue = $null - #endregion Generated Form Objects - - - $OnLoadForm_StateCorrection= - {#Correct the initial state of the form to prevent the .Net maximized form issue - $ExplorerForm.WindowState = $InitialFormWindowState - } - - #———————————————- - #region Generated Form Code - $ExplorerForm.Text = “Please select $Name” - $ExplorerForm.Name = “$Name” - $ExplorerForm.DataBindings.DefaultDataSourceUpdateMode = 0 - - $System_Drawing_Point = New-Object System.Drawing.Point - $System_Drawing_Point.X = 80 - $System_Drawing_Point.Y = 20 - - - $getBox = New-Object System.Windows.Forms.TextBox - $getBox.Location = $System_Drawing_Point - $getBox.Multiline = $false - $getBox.WordWrap = $false - $getBox.Size = New-Object System.Drawing.Size(240,23) - $getBox.Text = "" - $ExplorerForm.Controls.Add($getBox) - - $System_Drawing_Point.Y += 33 - - $button = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 240 - $System_Drawing_SizeButton.Height = 23 - $button.TabIndex = 0 - $button.Name = “Select” - $button.Size = $System_Drawing_SizeButton - $button.UseVisualStyleBackColor = $True - $button.Text = "Select $Name" - $button.Location = $System_Drawing_Point - $button.DataBindings.DefaultDataSourceUpdateMode = 0 - $scriptBlock = $DataArr[$it].Value - $button.add_Click( - { - $ExplorerForm.Close() - }) - $ExplorerForm.Controls.Add($button) - - $System_Drawing_Point.Y += 33 - - $System_Drawing_Size = New-Object System.Drawing.Size - $System_Drawing_Size.Width = 600 - $System_Drawing_Size.Height = $System_Drawing_Point.Y + 50 - $ExplorerForm.ClientSize = $System_Drawing_Size - - #endregion Generated Form Code - - #Save the initial state of the form - $InitialFormWindowState = $ExplorerForm.WindowState - #Init the OnLoad event to correct the initial state of the form - $ExplorerForm.add_Load($OnLoadForm_StateCorrection) - #Show the Form - $ExplorerForm.ShowDialog()| Out-Null - - return $getBox.Text - -} #End Function GetValueForm - -function VerifyUserAction { - param( - [string] $String - ) - - [reflection.assembly]::loadwithpartialname(“System.Windows.Forms”) | Out-Null - [reflection.assembly]::loadwithpartialname(“System.Drawing”) | Out-Null - #endregion - - #region Generated Form Objects - $ExplorerForm = New-Object System.Windows.Forms.Form - $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState - $ret = $false - #endregion Generated Form Objects - - - $OnLoadForm_StateCorrection= - {#Correct the initial state of the form to prevent the .Net maximized form issue - $ExplorerForm.WindowState = $InitialFormWindowState - } - - #———————————————- - #region Generated Form Code - if ([string]::IsNullOrEmpty($String)) - { - $String = “DO YOU WANT TO CONTINUE???” - } - $ExplorerForm.Text = $String - $ExplorerForm.Name = “Verify!!!” - $ExplorerForm.DataBindings.DefaultDataSourceUpdateMode = 0 - - $System_Drawing_Point = New-Object System.Drawing.Point - $System_Drawing_Point.X = 80 - $System_Drawing_Point.Y = 20 - - $Stop = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 200 - $System_Drawing_SizeButton.Height = 23 - $Stop.TabIndex = 0 - $Stop.Name = “Stop” - $Stop.Size = $System_Drawing_SizeButton - $Stop.UseVisualStyleBackColor = $True - $Stop.Text = "Stop" - $Stop.Location = $System_Drawing_Point - $Stop.DataBindings.DefaultDataSourceUpdateMode = 0 - $Stop.add_Click( - { - $ret = $false - $ExplorerForm.Close() - }) - $ExplorerForm.Controls.Add($Stop) - - $System_Drawing_Point.X += 210 - - $Continue = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 200 - $System_Drawing_SizeButton.Height = 23 - $Continue.TabIndex = 0 - $Continue.Name = “Continue” - $Continue.Size = $System_Drawing_SizeButton - $Continue.UseVisualStyleBackColor = $True - $Continue.Text = "Continue" - $Continue.Location = $System_Drawing_Point - $Continue.DataBindings.DefaultDataSourceUpdateMode = 0 - $Continue.add_Click( - { - $Continue.Text = "true" - $ExplorerForm.Close() - }) - $ExplorerForm.Controls.Add($Continue) - - $System_Drawing_Point.Y += 33 - - $System_Drawing_Size = New-Object System.Drawing.Size - $System_Drawing_Size.Width = 570 - $System_Drawing_Size.Height = $System_Drawing_Point.Y + 50 - $ExplorerForm.ClientSize = $System_Drawing_Size - - #endregion Generated Form Code - - #Save the initial state of the form - $InitialFormWindowState = $ExplorerForm.WindowState - #Init the OnLoad event to correct the initial state of the form - $ExplorerForm.add_Load($OnLoadForm_StateCorrection) - #Show the Form - - $ExplorerForm.ShowDialog()| Out-Null - - if ($Continue.Text -eq "true") - { - $ret = $True - } - - return $ret - -} #End Function VerifyUserAction - -function JsonForm { - -param( - [Parameter(mandatory=$true)] - [string] $ResourceRef, - - [Parameter(mandatory=$true)] - [string] $NCIP, - - [Parameter(mandatory=$true)] - [object]$NCCredential= [System.Management.Automation.PSCredential]::Empty, - - [Parameter(mandatory=$false)] - [bool] $EnableMultiWindow=$true - ) - - . .\NetworkControllerRESTWrappers.ps1 -ComputerName $NCIP -Username $null -Password $null -Credential $Script:NCCredential - - [reflection.assembly]::loadwithpartialname(“System.Windows.Forms”) | Out-Null - [reflection.assembly]::loadwithpartialname(“System.Drawing”) | Out-Null - #endregion - - #region Generated Form Objects - $ExplorerForm = New-Object System.Windows.Forms.Form - $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState - $objTextBoxVFP = $null - #endregion Generated Form Objects - - - $OnLoadForm_StateCorrection= - {#Correct the initial state of the form to prevent the .Net maximized form issue - $ExplorerForm.WindowState = $InitialFormWindowState - } - - #———————————————- - #region Generated Form Code - $ExplorerForm.Text = “$ResourceRef NC:$NCIP $pwd” - $ExplorerForm.Name = “$ResourceRef NC:$NCIP” - $ExplorerForm.DataBindings.DefaultDataSourceUpdateMode = 0 - - $ExplorerForm.ClientSize = New-Object System.Drawing.Size(700,800) - - $jsonObject = JSONGet -NetworkControllerRestIP $NCIP -path $ResourceRef -credential $NCCredential - - $getBox = New-Object System.Windows.Forms.TextBox - $getBox.Location = New-Object System.Drawing.Size(40,20) - $getBox.Multiline = $true - $getBox.ScrollBars = [System.Windows.Forms.ScrollBars]::Both - - $getBox.WordWrap = $false - $getBox.Size = New-Object System.Drawing.Size(250,60) - $getBox.Text = "Any Resource reference" - $ExplorerForm.Controls.Add($getBox) - - - $button1 = New-Object System.Windows.Forms.Button - $button1.TabIndex = 0 - $button1.Name = “Get” - $button1.Size = New-Object System.Drawing.Size(80,60) - $button1.UseVisualStyleBackColor = $True - $button1.Text = "Get" - $button1.Location = New-Object System.Drawing.Size(310,20) - $button1.DataBindings.DefaultDataSourceUpdateMode = 0 - - $scriptBlock = { - - if ($EnableMultiWindow) - { - $ps = [powershell]::create() - - $script = { - param( - [string] $ResourceRef, - - [string] $NCIP, - - [object]$NCCredential= [System.Management.Automation.PSCredential]::Empty, - - [string]$CurWorDir, - - [bool]$EnableMultiWindow - ) - try{ - Set-Location $CurWorDir - Import-Module -Force -Name .\SDNExplorer.ps1 -ArgumentList $NCIP,$NCCredential,$true,$true - JsonForm -ResourceRef $ResourceRef -NCIP $NCIP -NCCredential $NCCredential -EnableMultiWindow $true - } - catch{ - [System.Windows.Forms.MessageBox]::Show($_) - } - } - $parameters = @{} - $parameters.ResourceRef = $getBox.Text - $parameters.NCIP = $NCIP - $parameters.NCCredential = $NCCredential - $parameters.CurWorDir = $pwd - $parameters.EnableMultiWindow = $EnableMultiWindow - $ps.AddScript( - $script - ) - $ps.AddParameters($parameters) - $ps.BeginInvoke() - } - else - { - JsonForm -ResourceRef $getBox.Text -NCIP $NCIP -NCCredential $NCCredential - } - } - - $button1.add_Click($scriptBlock) - $ExplorerForm.Controls.Add($button1) - - $button2 = New-Object System.Windows.Forms.Button - $button2.TabIndex = 0 - $button2.Name = “Enable Editing” - $button2.Size = New-Object System.Drawing.Size(80,60) - $button2.UseVisualStyleBackColor = $True - $button2.Text = "Enable Editing" - $button2.Location = New-Object System.Drawing.Size(410,20) - $button2.DataBindings.DefaultDataSourceUpdateMode = 0 - $scriptBlockEnable = { - $objTextBox.Enabled = $true - $ExplorerForm.Controls.Remove($button2) - $ExplorerForm.Controls.Add($buttonPost) - $objTextBox.ReadOnly = $false - - } - $button2.add_Click($scriptBlockEnable) - $ExplorerForm.Controls.Add($button2) - - $buttonPost = New-Object System.Windows.Forms.Button - $buttonPost.TabIndex = 0 - $buttonPost.Name = “Post” - $buttonPost.Size = New-Object System.Drawing.Size(80,60) - $buttonPost.UseVisualStyleBackColor = $True - $buttonPost.Text = "Post" - $buttonPost.Location = New-Object System.Drawing.Size(410,20) - $buttonPost.DataBindings.DefaultDataSourceUpdateMode = 0 - $scriptBlockPost = { - $body = ConvertFrom-Json $objTextBox.Text - $parse = $ResourceRef.Split("/") - $rid = "" - - for($it = 0 ;$it -lt $parse.Count -1; $it++) - { - if( -not [string]::IsNullOrEmpty($parse[$it])) - { - $rid += "/" - $rid += $parse[$it] - } - } - - try - { - JSONPost -path $rid -bodyObject $body - [System.Windows.Forms.MessageBox]::Show("Done!!!!") - } - catch - { - [System.Windows.Forms.MessageBox]::Show("Post Failed!!!!") - } - } - $buttonPost.add_Click($scriptBlockPost) - - - - $objTextBox = New-Object System.Windows.Forms.RichTextBox - $objTextBox.Location = New-Object System.Drawing.Size(40,120) - $objTextBox.Multiline = $true - $objTextBox.ScrollBars = [System.Windows.Forms.ScrollBars]::Both - - $objTextBox.WordWrap = $false - $objTextBox.Size = New-Object System.Drawing.Size(600,630) - $objTextBox.Text = $jsonObject | ConvertTo-Json -Depth 20 - $objTextBox.ReadOnly = $true - $ExplorerForm.Controls.Add($objTextBox) - - - $SearchBox1 = New-Object System.Windows.Forms.RichTextBox - $SearchBox1.Location = New-Object System.Drawing.Size(40,100) - $SearchBox1.Multiline = $false - - $SearchBox1.WordWrap = $false - $SearchBox1.Size = New-Object System.Drawing.Size(500,20) - $SearchBox1.Text = "Enter Text to search here..." - $SearchBox1.ForeColor = [Drawing.Color]::Gray - $SearchBox1.add_KeyPress({ - - #Event Argument: $_ = [System.Windows.Forms.KeyEventArgs] - if($_.KeyChar -eq 13) - { - &$scriptBlockfindButton - } - elseif ($SearchBox1.Text -eq "Enter Text to search here...") - { - $SearchBox1.Text = "" - } - }) - $ExplorerForm.Controls.Add($SearchBox1) - - $findButton = New-Object System.Windows.Forms.Button - $findButton.TabIndex = 0 - $findButton.Name = “Find” - $findButton.Size = New-Object System.Drawing.Size(100,20) - $findButton.UseVisualStyleBackColor = $True - $findButton.Text = "Find" - $findButton.Location = New-Object System.Drawing.Size(540,100) - $findButton.DataBindings.DefaultDataSourceUpdateMode = 0 - $scriptBlockfindButton = { - $textBoxes = @() - $textBoxes += $objTextBox - - if ($objTextBoxVFP -ne $null) - { - $textBoxes += $objTextBoxVFP - } - $searchStr = $SearchBox1.Text - $found = $false - foreach ( $textBox in $textBoxes) - { - $i = 0 - $textBox.Text -Split '\n' | % { - $textBox.SelectionStart = $i - $line = $_ - $textBox.SelectionLength = $line.Length - if (Select-String -Pattern $searchStr -InputObject $line) - { - $textBox.SelectionColor = [Drawing.Color]::DarkGreen - $textBox.SelectionBackColor = [Drawing.Color]::White - - if (-not $found) - { - $textBox.ScrollToCaret() - $found = $true - } - } - else - { - $textBox.SelectionColor = [Drawing.Color]::Black - $textBox.SelectionBackColor = [System.Drawing.Color]::FromArgb(240,240,240) - } - $i += $line.Length + 1 - } - } - - $searchBox1.ForeColor = [Drawing.Color]::Black - - if ($found) - { - $SearchBox1.ForeColor = [Drawing.Color]::DarkGreen - } - else - { - $SearchBox1.ForeColor = [Drawing.Color]::Red - $SearchBox1.Text += " <-- NOT FOUND" - } - - } - $findButton.add_Click($scriptBlockfindButton) - $ExplorerForm.Controls.Add($findButton) - - #endregion Generated Form Code - - #Save the initial state of the form - $InitialFormWindowState = $ExplorerForm.WindowState - #Init the OnLoad event to correct the initial state of the form - $ExplorerForm.add_Load($OnLoadForm_StateCorrection) - - - if ($ResourceRef.Contains("virtualServers")) - { - $runCommand = New-Object System.Windows.Forms.Button - $runCommand.TabIndex = 0 - $runCommand.Name = “Run Command” - $runCommand.Size = New-Object System.Drawing.Size(80,60) - $runCommand.UseVisualStyleBackColor = $True - $runCommand.Text = "Run Command" - $runCommand.Location = New-Object System.Drawing.Size(510,20) - $runCommand.DataBindings.DefaultDataSourceUpdateMode = 0 - $scriptBlock = { - - foreach ($address in $jsonObject.properties.connections[0].managementAddresses) - { - try - { - [ipaddress]$address - - try - { - $ServerName = ([System.Net.Dns]::GetHostByAddress($address)).hostname - } - catch - { - [System.Windows.Forms.MessageBox]::Show("GetHostByAddress failed!!!!") - return - } - } - catch - { - $ServerName = $address - break; - } - } - - if([string]::IsNullOrEmpty($ServerName)) - { - [System.Windows.Forms.MessageBox]::Show("Server Name Missing!!!!") - return - } - - start-process -FilePath powershell -ArgumentList @('-NoExit',"-command etsn $ServerName -Credential Get-Credential") - } - $runCommand.add_Click($scriptBlock) - $ExplorerForm.Controls.Add($runCommand) - } - elseif ($ResourceRef.Contains("server")) - { - $ExplorerForm.ClientSize = New-Object System.Drawing.Size(900,800) - - $ovsdb = New-Object System.Windows.Forms.Button - $ovsdb.TabIndex = 0 - $ovsdb.Name = “OVSDB Policies” - $ovsdb.Size = New-Object System.Drawing.Size(100,40) - $ovsdb.UseVisualStyleBackColor = $True - $ovsdb.Text = "OVSDB Policies" - $ovsdb.Location = New-Object System.Drawing.Size(700,100) - $ovsdb.DataBindings.DefaultDataSourceUpdateMode = 0 - $scriptBlock1 = { - - if ($EnableMultiWindow) - { - $ps = [powershell]::create() - - $script = { - param( - - [object]$jsonObject, - - [string]$CurWorDir, - - [bool]$EnableMultiWindow - ) - - try{ - Set-Location $CurWorDir - Import-Module .\SDNExplorer.ps1 -ArgumentList $NCIP,$NCCredential,$true,$true - OvsdbForm -Server $jsonObject -EnableMultiWindow $true - } - catch{ - [System.Windows.Forms.MessageBox]::Show($_) - } - } - $parameters = @{} - $parameters.jsonObject = $jsonObject - $parameters.CurWorDir = $pwd - $parameters.EnableMultiWindow = $EnableMultiWindow - $ps.AddScript( - $script - ) - $ps.AddParameters($parameters) - $ps.BeginInvoke() - } - else - { - OvsdbForm -Server $jsonObject - } - } - $ovsdb.add_Click($scriptBlock1) - $ExplorerForm.Controls.Add($ovsdb) - - $vfp = New-Object System.Windows.Forms.Button - $vfp.TabIndex = 0 - $vfp.Name = “VFP Policies” - $vfp.Size = New-Object System.Drawing.Size(100,40) - $vfp.UseVisualStyleBackColor = $True - $vfp.Text = "VFP Policies" - $vfp.Location = New-Object System.Drawing.Size(700,160) - - $vfp.DataBindings.DefaultDataSourceUpdateMode = 0 - $scriptBlock2 = { - if ($EnableMultiWindow) - { - $ps = [powershell]::create() - - $script = { - param( - - [object]$jsonObject, - - [string]$CurWorDir - ) - - try{ - Set-Location $CurWorDir - Import-Module .\SDNExplorer.ps1 -ArgumentList $NCIP,$NCCredential,$true,$true - GetAllVFPPolices -Server $jsonObject -EnableMultiWindow $true - } - catch{ - [System.Windows.Forms.MessageBox]::Show($_) - } - } - $parameters = @{} - $parameters.jsonObject = $jsonObject - $parameters.CurWorDir = $pwd - $ps.AddScript( - $script - ) - $ps.AddParameters($parameters) - $ps.BeginInvoke() - } - else - { - GetAllVFPPolices -Server $jsonObject - } - } - $vfp.add_Click($scriptBlock2) - $ExplorerForm.Controls.Add($vfp) - - $runCommand = New-Object System.Windows.Forms.Button - $runCommand.TabIndex = 0 - $runCommand.Name = “Run Command” - $runCommand.Size = New-Object System.Drawing.Size(100,40) - $runCommand.UseVisualStyleBackColor = $True - $runCommand.Text = "Run Command" - $runCommand.Location = New-Object System.Drawing.Size(700,220) - $runCommand.DataBindings.DefaultDataSourceUpdateMode = 0 - $scriptBlock3 = { - - foreach ($address in $jsonObject.properties.connections[0].managementAddresses) - { - try - { - [ipaddress]$address - } - catch - { - $ServerName = $address - break; - } - } - - if([string]::IsNullOrEmpty($ServerName)) - { - [System.Windows.Forms.MessageBox]::Show("Server Name Missing!!!!") - return - } - - start-process -FilePath powershell -ArgumentList @('-NoExit',"-command etsn $ServerName") - } - $runCommand.add_Click($scriptBlock3) - $ExplorerForm.Controls.Add($runCommand) - - - $RDMA = New-Object System.Windows.Forms.Button - $RDMA.TabIndex = 0 - $RDMA.Name = “Verify RDMA” - $RDMA.Size = New-Object System.Drawing.Size(100,40) - $RDMA.UseVisualStyleBackColor = $True - $RDMA.Text = "Verify RDMA" - $RDMA.Location = New-Object System.Drawing.Size(700,280) - $RDMA.DataBindings.DefaultDataSourceUpdateMode = 0 - $scriptBlock4 = { - - - foreach ($address in $jsonObject.properties.connections[0].managementAddresses) - { - try - { - [ipaddress]$address - } - catch - { - $ServerName = $address - break; - } - } - - - RDMAValidation -ServerName $ServerName - } - $RDMA.add_Click($scriptBlock4) - $ExplorerForm.Controls.Add($RDMA) - - $Cert = New-Object System.Windows.Forms.Button - $Cert.TabIndex = 0 - $Cert.Name = “Verify Certs” - $Cert.Size = New-Object System.Drawing.Size(100,40) - $Cert.UseVisualStyleBackColor = $True - $Cert.Text = "Verify Certs" - $Cert.Location = New-Object System.Drawing.Size(700,340) - $Cert.DataBindings.DefaultDataSourceUpdateMode = 0 - $scriptBlock5 = { - - foreach ($address in $jsonObject.properties.connections[0].managementAddresses) - { - try - { - [ipaddress]$address - } - catch - { - $ServerName = $address - break; - } - } - - VerifyCerts -NCIP $NCIP -ServerName $ServerName -ServerObject $jsonObject -NCCredential $NCCredential - } - $Cert.add_Click($scriptBlock5) - $ExplorerForm.Controls.Add($Cert) - - $jumboPkt = New-Object System.Windows.Forms.Button - $jumboPkt.TabIndex = 0 - $jumboPkt.Name = “Verify Jumbo pkt” - $jumboPkt.Size = New-Object System.Drawing.Size(100,40) - $jumboPkt.UseVisualStyleBackColor = $True - $jumboPkt.Text = "Verify Jumbo pkt" - $jumboPkt.Location = New-Object System.Drawing.Size(700,400) - $jumboPkt.DataBindings.DefaultDataSourceUpdateMode = 0 - $scriptBlock6 = { - - foreach ($address in $jsonObject.properties.connections[0].managementAddresses) - { - try - { - [ipaddress]$address - } - catch - { - $ServerName = $address - break; - } - } - - VerifyJumboPkt -ServerName $ServerName -NCCredential $NCCredential - } - $jumboPkt.add_Click($scriptBlock6) - $ExplorerForm.Controls.Add($jumboPkt) - } - elseif($ResourceRef.Contains("networkInterfaces")) - { - $ExplorerForm.ClientSize = New-Object System.Drawing.Size(1400,800) - - $scriptBlockVfpRules = { - - $objTextBoxVFP.text = "Extracting VFP Rules..." - - $ServerResource = $jsonObject.properties.server.resourceRef - - if(-not [string]::IsNullOrEmpty($ServerResource)) - { - $server = JSONGet -NetworkControllerRestIP $NCIP -path $ServerResource -credential $NCCredential - - foreach ($address in $server.properties.connections[0].managementAddresses) - { - try - { - [ipaddress]$address - } - catch - { - $ServerName = $address - break; - } - } - - if(-not [string]::IsNullOrEmpty($ServerName)) - { - - $mac = $jsonObject.properties.privateMacAddress - $mac = $mac -replace "-" - $mac = $mac -replace ":" - - $scriptBlockVFP = { - - param( - [Parameter(mandatory=$true)] - [string] $Mac - ) - $vms = gwmi -na root\virtualization\v2 msvm_computersystem | Where Description -Match "Virtual" - $port = $null - $vms | foreach { - $vm=$_; $vm.GetRelated("Msvm_SyntheticEthernetPort") | foreach { - $vma = $_; - if($vma.PermanentAddress -eq $Mac) - { - $port = $vma.GetRelated("Msvm_SyntheticEthernetPortSettingData").GetRelated("Msvm_EthernetPortAllocationSettingData").GetRelated("Msvm_EthernetSwitchPort"); - } - } - } - - $portGuid = $port.Name - $vfpCtrlExe = "vfpctrl.exe" - echo "Policy for port : " $portGuid - & $vfpCtrlExe /list-space /port $portGuid - & $vfpCtrlExe /list-mapping /port $portGuid - & $vfpCtrlExe /list-rule /port $portGuid - & $vfpCtrlExe /port $portGuid /get-port-state - } - - $text = @() - $text = RunServerCommand -ServerName $ServerName -scriptBlock $scriptBlockVFP -argumentList $mac - - $objTextBoxVFP.Enabled = $true - - $objTextBoxVFP.text = "" - - foreach ($line in $text) { - $objTextBoxVFP.Appendtext($line) - $objTextBoxVFP.AppendText("`n") - } - } - else - { - [System.Windows.Forms.MessageBox]::Show("Server Name Missing!!!!") - } - } - else - { - [System.Windows.Forms.MessageBox]::Show("Server Resource Missing!!!!") - } - } - - $buttonVfpRule = New-Object System.Windows.Forms.Button - $buttonVfpRule.TabIndex = 0 - $buttonVfpRule.Name = “VFP Rules” - $buttonVfpRule.Size = New-Object System.Drawing.Size(80,60) - $buttonVfpRule.UseVisualStyleBackColor = $True - $buttonVfpRule.Text = “VFP Rules” - $buttonVfpRule.Location = New-Object System.Drawing.Size(800,20) - $buttonVfpRule.DataBindings.DefaultDataSourceUpdateMode = 0 - $buttonVfpRule.add_Click($scriptBlockVfpRules) - $ExplorerForm.Controls.Add($buttonVfpRule) - - $buttonVfpRule = New-Object System.Windows.Forms.Button - $buttonVfpRule.TabIndex = 0 - $buttonVfpRule.Name = “Start-CAPing” - $buttonVfpRule.Size = New-Object System.Drawing.Size(80,60) - $buttonVfpRule.UseVisualStyleBackColor = $True - $buttonVfpRule.Text = “Start-CAPing” - $buttonVfpRule.Location = New-Object System.Drawing.Size(700,20) - $buttonVfpRule.DataBindings.DefaultDataSourceUpdateMode = 0 - $buttonVfpRule.add_Click( - { - CAPing -NCIP $NCIP -Source $jsonObject -NCCredential $NCCredential - }) - $ExplorerForm.Controls.Add($buttonVfpRule) - - $objTextBoxVFP = New-Object System.Windows.Forms.RichTextBox - $objTextBoxVFP.Location = New-Object System.Drawing.Size(700,100) - $objTextBoxVFP.Multiline = $true - $objTextBoxVFP.ScrollBars = [System.Windows.Forms.ScrollBars]::Both - - $objTextBoxVFP.WordWrap = $false - $objTextBoxVFP.Size = New-Object System.Drawing.Size(600,650) - $objTextBoxVFP.font = "lucida console" - $objTextBoxVFP.Enabled = $false - $objTextBoxVFP.ReadOnly = $true - $ExplorerForm.Controls.Add($objTextBoxVFP) - - } - - - #Show the Form - $ExplorerForm.ShowDialog()| Out-Null - -} #End Function JsonForm - -function RunNCCMDLet { - -param( - [Parameter(mandatory=$true)] - [string] $Cmdlet, - [Parameter(mandatory=$true)] - [object] $NCVMCred, - [Parameter(mandatory=$true)] - [string] $NCIP - ) - try - { - # Generate Random names for prefix - $rand = New-Object System.Random - $prefixLen = 8 - [string]$namingPrefix = '' - for($i = 0; $i -lt $prefixLen; $i++) - { - $namingPrefix += [char]$rand.Next(65,90) - } - - - if ($Cmdlet -eq "Debug-NetworkController") - { - if ($NCIP -eq "restserver") - { - $ip = ([System.Net.Dns]::GetHostAddresses("restserver"))[0].IPAddressToString - } - else - { - $ip = $NCIP - } - $copyfolder = "Debug-NetworkController_$namingPrefix" - $cmdstring += "$Cmdlet -NetworkController $ip -OutputDirectory c:\temp\Debug-NetworkController_$namingPrefix" - } - elseif ($Cmdlet -eq "Debug-NetworkControllerConfigurationState") - { - if ($Script:NCIP -eq "restserver") - { - $cmdstring += " echo `"`n`r192.14.0.22 restserver`" > C:\Windows\System32\drivers\etc\hosts;" - } - - $cmdstring += "$Cmdlet -NetworkController $NCIP" - } - - $scriptBlock = ([scriptblock]::Create($cmdstring)) - - $result = Invoke-Command -ComputerName $NCIP -ScriptBlock $scriptBlock -Credential $NCVMCred - - if ($copyfolder) - { - $psDriver = New-PSDrive -Name Y -PSProvider filesystem -Root \\$ip\c$\temp -Credential $NCVMCred - - Copy-Item Y:\$copyfolder .\$copyfolder -Recurse - } - - [System.Windows.Forms.MessageBox]::Show("$Cmdlet completed") - - if ($copyfolder) - { - start .\$copyfolder - } - else - { - DisplayTextForm -FormName $Cmdlet -Text $result - } - - } - catch - { - [System.Windows.Forms.MessageBox]::Show($_) - } - finally - { - if ($psDriver) - { - Remove-PSDrive -Name Y - } - } - -} - -function OvsdbForm { - -param( - [Parameter(mandatory=$true)] - [object] $Server, - [Parameter(mandatory=$false)] - [bool] $EnableMultiWindow=$true - ) - - [reflection.assembly]::loadwithpartialname(“System.Windows.Forms”) | Out-Null - [reflection.assembly]::loadwithpartialname(“System.Drawing”) | Out-Null - #endregion - - if($EnableMultiWindow) - { - $progress = [powershell]::create() - - $progressScript = { - [System.Windows.Forms.MessageBox]::Show("Fetching Policies, it will take a few seconds to complete") - } - $progress.AddScript($progressScript) - - $progressObj = $progress.BeginInvoke() - } - - foreach ($address in $Server.properties.connections[0].managementAddresses) - { - try - { - [ipaddress]$address - } - catch - { - $ServerName = $address - break; - } - } - - if([string]::IsNullOrEmpty($ServerName)) - { - [System.Windows.Forms.MessageBox]::Show("Server Name Missing!!!!") - return - } - - #region Generated Form Objects - $ExplorerForm = New-Object System.Windows.Forms.Form - $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState - #endregion Generated Form Objects - - - $OnLoadForm_StateCorrection= - {#Correct the initial state of the form to prevent the .Net maximized form issue - $ExplorerForm.WindowState = $InitialFormWindowState - } - - #———————————————- - #region Generated Form Code - $ExplorerForm.Text = $ServerName - $ExplorerForm.Name = $ServerName - $ExplorerForm.DataBindings.DefaultDataSourceUpdateMode = 0 - - $ExplorerForm.ClientSize = New-Object System.Drawing.Size(900,800) - - $vtep = @() - $vtep = GetOvsDBVtep -ServerName $ServerName - - $firewall = @() - $firewall = GetOvsDBfirewall -ServerName $ServerName - - - $objTextBoxVtep = New-Object System.Windows.Forms.RichTextBox - $objTextBoxVtep.Location = New-Object System.Drawing.Size(40,100) - $objTextBoxVtep.Multiline = $true - $objTextBoxVtep.ScrollBars = [System.Windows.Forms.ScrollBars]::Both - - $objTextBoxVtep.WordWrap = $false - $objTextBoxVtep.Size = New-Object System.Drawing.Size(390,650) - $objTextBoxVtep.font = "lucida console" - foreach ($line in $vtep) { - $objTextBoxVtep.Appendtext($line) - $objTextBoxVtep.AppendText("`n") - } - $ExplorerForm.Controls.Add($objTextBoxVtep) - - $objTextBoxFirewall = New-Object System.Windows.Forms.TextBox - $objTextBoxFirewall.Location = New-Object System.Drawing.Size(470,100) - $objTextBoxFirewall.Multiline = $true - $objTextBoxFirewall.ScrollBars = [System.Windows.Forms.ScrollBars]::Both - - $objTextBoxFirewall.WordWrap = $false - $objTextBoxFirewall.Size = New-Object System.Drawing.Size(390,650) - foreach ($line in $firewall) { - $objTextBoxFirewall.Appendtext($line) - $objTextBoxFirewall.AppendText("`n") - } - $ExplorerForm.Controls.Add($objTextBoxFirewall) - - #endregion Generated Form Code - - #Save the initial state of the form - $InitialFormWindowState = $ExplorerForm.WindowState - #Init the OnLoad event to correct the initial state of the form - $ExplorerForm.add_Load($OnLoadForm_StateCorrection) - - #Show the Form - $ExplorerForm.ShowDialog()| Out-Null - if($EnableMultiWindow) - { - $progress.Dispose() - } - -} #End Function OvsdbForm - -function RunScriptForm { - -param( - [Parameter(mandatory=$false)] - [object[]] $Servers - ) - - [reflection.assembly]::loadwithpartialname(“System.Windows.Forms”) | Out-Null - [reflection.assembly]::loadwithpartialname(“System.Drawing”) | Out-Null - #endregion - - #region Generated Form Objects - $ExplorerForm = New-Object System.Windows.Forms.Form - $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState - #endregion Generated Form Objects - - - $OnLoadForm_StateCorrection= - {#Correct the initial state of the form to prevent the .Net maximized form issue - $ExplorerForm.WindowState = $InitialFormWindowState - } - - #———————————————- - #region Generated Form Code - $ExplorerForm.Text = "Run Script Block on all Servers" - $ExplorerForm.Name = $ServerName - $ExplorerForm.DataBindings.DefaultDataSourceUpdateMode = 0 - - $ExplorerForm.ClientSize = New-Object System.Drawing.Size(900,800) - - - $runScript = New-Object System.Windows.Forms.Button - $System_Drawing_SizeButton = New-Object System.Drawing.Size - $System_Drawing_SizeButton.Width = 240 - $System_Drawing_SizeButton.Height = 23 - $runScript.TabIndex = 0 - $runScript.Name = “Select” - $runScript.Size = $System_Drawing_SizeButton - $runScript.UseVisualStyleBackColor = $True - $runScript.Text = "--Run Script Block--" - $runScript.Location = New-Object System.Drawing.Size(50,50) - $runScript.DataBindings.DefaultDataSourceUpdateMode = 0 - $scriptBlock = { - $objTextBoxOutPut.text = "" - $objTextInput.ReadOnly = $true - - foreach ($server in $Servers) - { - try - { - foreach ($address in $server.properties.connections[0].managementAddresses) - { - try - { - [ipaddress]$address - } - catch - { - $ServerName = $address - break; - } - } - - $line = "==============================`n" - $objTextBoxOutPut.text += $line - $line = "Running Command on $ServerName `n" - $objTextBoxOutPut.text += $line - $line = "==============================`n" - $objTextBoxOutPut.text += $line - - $command = "try{" - $command += $objTextInput.text - $command += "}catch {return `$_}" - - $data = RunServerCommand -ServerName $ServerName -scriptBlock $command - - foreach ($line in $data) { - if ($line.Length -ne 0) - { - $formattedData = $line | Format-Table - $formattedData = $formattedData | Out-String - $objTextBoxOutPut.Appendtext($formattedData) - $objTextBoxOutPut.ScrollToCaret() - } - } - } - catch - { - [System.Windows.Forms.MessageBox]::Show($_) - } - } - - $i = 0 - $objTextBoxOutPut.Text -Split '\n' | % { - $objTextBoxOutPut.SelectionStart = $i - $line = $_ - $searchStr1 = "Running Command on " - $searchStr2 = "====================" - $objTextBoxOutPut.SelectionLength = $line.Length - if (Select-String -Pattern $searchStr1 -InputObject $line) - { - $objTextBoxOutPut.SelectionColor = [Drawing.Color]::Blue - } - elseif (Select-String -Pattern $searchStr2 -InputObject $line) - { - $objTextBoxOutPut.SelectionColor = [Drawing.Color]::DarkBlue - } - else - { - $objTextBoxOutPut.SelectionColor = [Drawing.Color]::Black - } - $i += $line.Length + 1 - } - - $objTextInput.ReadOnly = $false - } - $runScript.add_Click($scriptBlock) - $ExplorerForm.Controls.Add($runScript) - - $objTextInput = New-Object System.Windows.Forms.RichTextBox - $objTextInput.Location = New-Object System.Drawing.Size(50,100) - $objTextInput.Multiline = $true - $objTextInput.ScrollBars = [System.Windows.Forms.ScrollBars]::Both - - $objTextInput.WordWrap = $false - $objTextInput.Size = New-Object System.Drawing.Size(800,300) - $objTextInput.font = "lucida console" - foreach ($line in $vtep) { - $objTextInput.Appendtext($line) - $objTextInput.AppendText("`n") - } - $ExplorerForm.Controls.Add($objTextInput) - - $objTextBoxOutPut = New-Object System.Windows.Forms.RichTextBox - $objTextBoxOutPut.Location = New-Object System.Drawing.Size(50,450) - $objTextBoxOutPut.Multiline = $true - $objTextBoxOutPut.ScrollBars = [System.Windows.Forms.ScrollBars]::Both - $objTextBoxOutPut.ReadOnly = $true - - $objTextBoxOutPut.WordWrap = $false - $objTextBoxOutPut.Size = New-Object System.Drawing.Size(800,300) - foreach ($line in $firewall) { - $objTextBoxOutPut.Appendtext($line) - $objTextBoxOutPut.AppendText("`n") - } - $ExplorerForm.Controls.Add($objTextBoxOutPut) - - #endregion Generated Form Code - - #Save the initial state of the form - $InitialFormWindowState = $ExplorerForm.WindowState - #Init the OnLoad event to correct the initial state of the form - $ExplorerForm.add_Load($OnLoadForm_StateCorrection) - - #Show the Form - $ExplorerForm.ShowDialog()| Out-Null - -} #End Function RunScriptForm - -function GetOvsDBVtep { - -param( - [Parameter(mandatory=$true)] - [string] $ServerName - ) - - - return RunServerCommand -ServerName $ServerName -scriptBlock {C:\windows\system32\ovsdb-client.exe dump tcp:127.0.0.1:6641 ms_vtep} - -} #End Function GetOvsDBVtep - -function GetOvsDBfirewall { - -param( - [Parameter(mandatory=$true)] - [string] $ServerName - ) - - return RunServerCommand -ServerName $ServerName -scriptBlock {C:\windows\system32\ovsdb-client.exe dump tcp:127.0.0.1:6641 ms_firewall} - -} #End Function GetOvsDBfirewall - -function GetAllVFPPolices { - -param( - [Parameter(mandatory=$true)] - [object] $Server, - [Parameter(mandatory=$false)] - [object] $EnableMultiWindow=$true - - - ) - - if($EnableMultiWindow) - { - $progress = [powershell]::create() - - $progressScript = { - [System.Windows.Forms.MessageBox]::Show("Fetching Policies, it will take a few seconds to complete") - } - $progress.AddScript($progressScript) - - $progressObj = $progress.BeginInvoke() - } - - foreach ($address in $Server.properties.connections[0].managementAddresses) - { - try - { - [ipaddress]$address - } - catch - { - $ServerName = $address - break; - } - } - - if([string]::IsNullOrEmpty($ServerName)) - { - [System.Windows.Forms.MessageBox]::Show("Server Name Missing!!!!") - return - } - - $scriptBlock = { - - $switches = Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_VirtualEthernetSwitch - foreach ($switch in $switches) { - $vfpCtrlExe = "vfpctrl.exe" - $ports = $switch.GetRelated("Msvm_EthernetSwitchPort", "Msvm_SystemDevice", $null, $null, $null, $null, $false, $null) - foreach ($port in $ports) { - $portGuid = $port.Name - echo "Policy for port : " $portGuid - & $vfpCtrlExe /list-space /port $portGuid - & $vfpCtrlExe /list-mapping /port $portGuid - & $vfpCtrlExe /list-rule /port $portGuid - & $vfpCtrlExe /port $portGuid /get-port-state - } - } - } - - $text = @() - $text = RunServerCommand -ServerName $ServerName -scriptBlock $scriptBlock - - DisplayTextForm -FormName $ServerName -Text $text - - if($EnableMultiWindow) - { - $progress.Dispose() - } - -} #End Function GetAllVFPPolices - -function RunServerCommand { - -param( - [Parameter(mandatory=$true)] - [string] $ServerName, - - [Parameter(mandatory=$false)] - [string] $scriptBlock, - - [Parameter(mandatory=$false)] - [object[]] $argumentList = $null - ) - - - $text = @() - $script = ([scriptblock]::Create($scriptBlock)) - - if (-not $argumentList) - { - $text = Invoke-Command -ComputerName $ServerName -ScriptBlock $script - } - else - { - $text = Invoke-Command -ComputerName $ServerName -ScriptBlock $script -ArgumentList $argumentList - } - - return $text -} #End Function RunServerCommand - -function DisplayTextForm { - -param( - [Parameter(mandatory=$true)] - [string] $FormName, - - [Parameter(mandatory=$false)] - [string[]] $Text - ) - - [reflection.assembly]::loadwithpartialname(“System.Windows.Forms”) | Out-Null - [reflection.assembly]::loadwithpartialname(“System.Drawing”) | Out-Null - #endregion - - try - { - foreach ($address in $Server.properties.connections[0].managementAddresses) - { - try - { - [ipaddress]$address - } - catch - { - $ServerName = $address - break; - } - } - - if([string]::IsNullOrEmpty($ServerName)) - { - [System.Windows.Forms.MessageBox]::Show("Server Name Missing!!!!") - } - - $FormName = $ServerName - } - catch - { - } - - #region Generated Form Objects - $ExplorerForm = New-Object System.Windows.Forms.Form - $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState - #endregion Generated Form Objects - - - $OnLoadForm_StateCorrection= - {#Correct the initial state of the form to prevent the .Net maximized form issue - $ExplorerForm.WindowState = $InitialFormWindowState - } - - #———————————————- - #region Generated Form Code - $ExplorerForm.Text = $FormName - $ExplorerForm.Name = $FormName - $ExplorerForm.DataBindings.DefaultDataSourceUpdateMode = 0 - - $ExplorerForm.ClientSize = New-Object System.Drawing.Size(700,800) - - - $objTextBoxVtep = New-Object System.Windows.Forms.RichTextBox - $objTextBoxVtep.Location = New-Object System.Drawing.Size(40,100) - $objTextBoxVtep.Multiline = $true - $objTextBoxVtep.ScrollBars = [System.Windows.Forms.ScrollBars]::Both - - $objTextBoxVtep.WordWrap = $false - $objTextBoxVtep.Size = New-Object System.Drawing.Size(600,650) - $objTextBoxVtep.font = "lucida console" - foreach ($line in $Text) { - $objTextBoxVtep.Appendtext($line) - $objTextBoxVtep.AppendText("`n") - } - $ExplorerForm.Controls.Add($objTextBoxVtep) - - #endregion Generated Form Code - - #Save the initial state of the form - $InitialFormWindowState = $ExplorerForm.WindowState - #Init the OnLoad event to correct the initial state of the form - $ExplorerForm.add_Load($OnLoadForm_StateCorrection) - - #Show the Form - $ExplorerForm.ShowDialog()| Out-Null - -} #End Function DisplayTextForm - -function CAPing -{ - param($NCIP, $Source, $NCCredential= [System.Management.Automation.PSCredential]::Empty) - - . .\NetworkControllerRESTWrappers.ps1 -ComputerName $NCIP -Username $null -Password $null -Credential $Script:NCCredential - - $headers = @{"Accept"="application/json"} - $content = "application/json; charset=UTF-8" - $network = "https://$NCIP/Networking/v1" - $retry = 30 - - $method = "Put" - $uri = "$network/diagnostics/ConnectivityCheck" - $body = $caJson - - $networkInterfaces = Get-NCNetworkInterface - - $selectIps = @() - - foreach ($ni in $networkInterfaces) - { - try - { - if($Source.properties.ipConfigurations[0].properties.privateIPAddress -ne $ni.properties.ipConfigurations[0].properties.privateIPAddress) - { - $selectIps += $ni.properties.ipConfigurations[0].properties.privateIPAddress - } - } - catch - { - #skip - } - } - - $selectedIp = RadioForm -Name "Dest IP" -Values $selectIps - - foreach ($ni in $networkInterfaces) - { - try - { - if ( $selectedIp -eq $ni.properties.ipConfigurations[0].properties.privateIPAddress) - { - $destination = $ni - } - } - catch - { - #skip - } - } - - $caJson = @{} - $caJson.resourceId = "" - $caJson.properties = @{} - $caJson.properties.senderIpAddress = $Source.properties.ipConfigurations[0].properties.privateIPAddress - $caJson.properties.receiverIpAddress = $destination.properties.ipConfigurations[0].properties.privateIPAddress - - - $parse = $Source.properties.ipConfigurations[0].properties.subnet.resourceRef.Split("/") - $Vnet = "" - for($it = 0 ;$it -lt 3; $it++) - { - if( -not [string]::IsNullOrEmpty($parse[$it])) - { - $Vnet += "/" - $Vnet += $parse[$it] - } - } - $caJson.properties.sendervirtualNetwork = @{} - $caJson.properties.sendervirtualNetwork.resourceRef = $Vnet - $caJson.properties.receivervirtualNetwork = @{} - $caJson.properties.receivervirtualNetwork.resourceRef = $Vnet - $caJson.properties.disableTracing = $false - $caJson.properties.protocol = "Icmp" - $caJson.properties.icmpProtocolConfig = @{} - $caJson.properties.icmpProtocolConfig.sequenceNumber = 1 - $caJson.properties.icmpProtocolConfig.length = 0 - - $body = ConvertTo-Json -Depth 20 $caJson - try - { - $result = Invoke-WebRequest -Headers $headers -ContentType $content -Method $method -Uri $uri -Body $body -DisableKeepAlive -UseBasicParsing - - $body = ConvertFrom-Json $result.Content - - $operationId = $body.properties.operationId - - [System.Windows.Forms.MessageBox]::Show("CAPing started:$operationId") - } - catch - { - [System.Windows.Forms.MessageBox]::Show("$_") - } -} #End Function CAPing - -function RDMAValidation -{ - Param( - [Parameter(Mandatory=$True, Position=1, HelpMessage="Interface index of the adapter for which RDMA config is to be verified")] - [string] $ServerName - ) - - $vnics = Invoke-Command -ComputerName $ServerName -ScriptBlock { Get-NetAdapter | Where-Object {$_.DriverName -eq "\SystemRoot\System32\drivers\vmswitch.sys" } } - - - $vnicNames = @() - - foreach ($vnic in $vnics) - { - $vnicNames += $vnic.Name - } - try{ - $selectedVName = RadioForm -Name "Adapter Name" -Values $vnicNames - } - catch{ - [System.Windows.Forms.MessageBox]::Show("Main error" + $_) - return - } - - foreach ($vnic in $vnics) - { - if ($selectedVName -eq $vnic.Name) - { - $selectedVNic= $vnic - break - } - } - - $IsRoceStr = RadioForm -Name "IsRoce" -Values "true","false" - - $IsRoce = $false - if ($IsRoceStr -eq "true") - { - $IsRoce = $true - } - - - $scriptBlock = { - - Param( - [string] $IfIndex, - [bool] $IsRoCE - ) - - $rdmaAdapter = Get-NetAdapter -IfIndex $IfIndex - - if ($rdmaAdapter -eq $null) - { - Write-Host "ERROR: The adapter with interface index $IfIndex not found" - return - } - - $rdmaAdapterName = $rdmaAdapter.Name - $virtualAdapter = Get-VMNetworkAdapter -ManagementOS | where DeviceId -eq $rdmaAdapter.DeviceID - - if ($virtualAdapter -eq $null) - { - $isRdmaAdapterVirtual = $false - Write-Host "VERBOSE: The adapter $rdmaAdapterName is a physical adapter" - } - else - { - $isRdmaAdapterVirtual = $true - Write-Host "VERBOSE: The adapter $rdmaAdapterName is a virtual adapter" - } - - $rdmaCapabilities = Get-NetAdapterRdma -InterfaceDescription $rdmaAdapter.InterfaceDescription - - if ($rdmaCapabilities -eq $null -or $rdmaCapabilities.Enabled -eq $false) - { - return "ERROR: The adapter $rdmaAdapterName is not enabled for RDMA" - } - - if ($rdmaCapabilities.MaxQueuePairCount -eq 0) - { - return "ERROR: RDMA capabilities for adapter $rdmaAdapterName are not valid : MaxQueuePairCount is 0" - - } - - if ($rdmaCapabilities.MaxCompletionQueueCount -eq 0) - { - return "ERROR: RDMA capabilities for adapter $rdmaAdapterName are not valid : MaxCompletionQueueCount is 0" - - } - - $smbClientNetworkInterfaces = Get-SmbClientNetworkInterface - - if ($smbClientNetworkInterfaces -eq $null) - { - return "ERROR: No network interfaces detected by SMB (Get-SmbClientNetworkInterface)" - } - - $rdmaAdapterSmbClientNetworkInterface = $null - foreach ($smbClientNetworkInterface in $smbClientNetworkInterfaces) - { - if ($smbClientNetworkInterface.InterfaceIndex -eq $IfIndex) - { - $rdmaAdapterSmbClientNetworkInterface = $smbClientNetworkInterface - } - } - - if ($rdmaAdapterSmbClientNetworkInterface -eq $null) - { - return "ERROR: No network interfaces found by SMB for adapter $rdmaAdapterName (Get-SmbClientNetworkInterface)" - } - - if ($rdmaAdapterSmbClientNetworkInterface.RdmaCapable -eq $false) - { - return "ERROR: SMB did not detect adapter $rdmaAdapterName as RDMA capable. Make sure the adapter is bound to TCP/IP and not to other protocol like vmSwitch." - } - - $rdmaAdapters = $rdmaAdapter - if ($isRdmaAdapterVirtual -eq $true) - { - Write-Host "VERBOSE: Retrieving vSwitch bound to the virtual adapter" - $switchName = $virtualAdapter.SwitchName - Write-Host "VERBOSE: Found vSwitch: $switchName" - $vSwitch = Get-VMSwitch -Name $switchName - $rdmaAdapters = Get-NetAdapter -InterfaceDescription $vSwitch.NetAdapterInterfaceDescriptions - $vSwitchAdapterMessage = "VERBOSE: Found the following physical adapter(s) bound to vSwitch: " - $index = 1 - foreach ($qosAdapter in $rdmaAdapters) - { - $qosAdapterName = $qosAdapter.Name - $vSwitchAdapterMessage = $vSwitchAdapterMessage + [string]$qosAdapterName - if ($index -lt $rdmaAdapters.Length) - { - $vSwitchAdapterMessage = $vSwitchAdapterMessage + ", " - } - $index = $index + 1 - } - Write-Host $vSwitchAdapterMessage - } - - - if ($IsRoCE -eq $true) - { - Write-Host "VERBOSE: Underlying adapter is RoCE. Checking if QoS/DCB/PFC is configured on each physical adapter(s)" - foreach ($qosAdapter in $rdmaAdapters) - { - $qosAdapterName = $qosAdapter.Name - $qos = Get-NetAdapterQos -Name $qosAdapterName - if ($qos.Enabled -eq $false) - { - return "ERROR: QoS is not enabled for adapter $qosAdapterName" - } - - if ($qos.OperationalFlowControl -eq "All Priorities Disabled") - { - return "ERROR: Flow control is not enabled for adapter $qosAdapterName" - } - } - Write-Host "VERBOSE: QoS/DCB/PFC configuration is correct." - } - - return " RDMA configuration on the host is correct, please check switch configuration for E2E RDMA to work." - } - - $strOutput = Invoke-Command -ComputerName $ServerName -ScriptBlock $scriptBlock -ArgumentList $selectedVNic.ifIndex,$IsRoce - - [System.Windows.Forms.MessageBox]::Show("$strOutput") -} #End Function RDMAValidation - -function VerifyJumboPkt -{ - Param( - [string] $ServerName, - [object]$NCCredential= [System.Management.Automation.PSCredential]::Empty - ) - try - { - $allServers = Get-NCServer - - $serverNames = @() - - foreach ($server in $allServers) - { - foreach ($address in $server.properties.connections[0].managementAddresses) - { - try - { - [ipaddress]$address - } - catch - { - if ($address -ne $ServerName) - { - $serverNames += $address - } - break; - } - } - } - - $destServer = RadioForm -Name "Dest Server" -Values $serverNames - - if ($NCCredential -eq [System.Management.Automation.PSCredential]::Empty) - { - $cred = Get-Credential -Message "Enter Server Creds" - } - else - { - $cred = $NCCredential - } - - $result = Test-LogicalNetworkSupportsJumboPacket -SourceHost $ServerName -DestinationHost $destServer -SourceHostCreds $cred -DestinationHostCreds $cred - - $result = $result | ConvertTo-Json -Depth 10 - - [System.Windows.Forms.MessageBox]::Show($result) - } - catch - { - [System.Windows.Forms.MessageBox]::Show($_) - } - -} - -function VerifyCerts -{ - Param( - [string] $NCIP, - [string] $ServerName, - [object] $ServerObject, - [object] $NCCredential= [System.Management.Automation.PSCredential]::Empty - ) - . .\NetworkControllerRESTWrappers.ps1 -ComputerName $NCIP -Username $null -Password $null -Credential $Script:NCCredential - - $NCCertHash = $null - foreach ($conn in $ServerObject.properties.connections) - { - $cred = JSONGet -path $conn.credential.resourceRef -NetworkControllerRestIP $NCIP -credential $NCCredential - - if ($cred.properties.type -eq "X509Certificate") - { - $NCCertHash = $cred.properties.value - } - } - - if ([string]::IsNullOrEmpty($NCCertHash)) - { - [System.Windows.Forms.MessageBox]::Show("NC Cert is not configured in Server($ServerName) Json.") - } - - $ServerCert = $ServerObject.properties.certificate - - $scriptBlock = - { - Param( - [string] $NCCertHash, - [string] $ServerCert - ) - - $rootCerts = dir Cert:\LocalMachine\Root - - $NCCert = $null - foreach ($cert in $rootCerts) - { - if ($cert.Thumbprint -eq $NCCertHash) - { - $NCCert = $cert - } - } - - if (-not $NCCert) - { - return "NC Cert is Missing" - } - - $myCerts = dir Cert:\LocalMachine\my - $serverCertificate = $null - foreach ($cert in $myCerts) - { - $base64 = [System.Convert]::ToBase64String($cert.RawData) - if ($base64 -eq $ServerCert) - { - $serverCertificate = $cert - } - } - - if (-not $serverCertificate) - { - return "Server Cert is Missing" - } - - $certToVerify = @() - $certToVerify += $NCCert - $certToVerify += $serverCertificate - - foreach ($cert in $certToVerify) - { - - $server = $false - $client = $false - foreach ($eku in $cert.EnhancedKeyUsageList) - { - if ($eku.FriendlyName -eq "Server Authentication") - { - $server = $true - } - - if ($eku.FriendlyName -eq "Client Authentication") - { - $client = $true - } - } - - $thumbprint = $cert.Thumbprint - - if ($server -eq $false) - { - return "Server EKU is missing on NC Cert($thumbprint) on Server." - } - - if ($client -eq $false) - { - return "Client EKU is missing on NC Cert($thumbprint) on Server." - } - } - - $key = 'HKLM:\SYSTEM\CurrentControlSet\Services\NcHostAgent\Parameters\' - $peerCertificateCName = "CN=" - $peerCertificateCName += (Get-ItemProperty -Path $key -Name PeerCertificateCName).PeerCertificateCName - - if ($peerCertificateCName -ne $NCCert.Subject) - { - $subject = $NCCert.Subject - return "NCHostAgent has wrong PeerCertificateCName($peerCertificateCName) instead of $subject" - } - - $hostAgentCertificateCName = "CN=" - $hostAgentCertificateCName += (Get-ItemProperty -Path $key -Name HostAgentCertificateCName).HostAgentCertificateCName - - if ($hostAgentCertificateCName -ne $serverCertificate.Subject) - { - $subject = $serverCertificate.Subject - return "NCHostAgent has wrong PeerCertificateCName($hostAgentCertificateCName) instead of $subject" - } - - return "Certificates are configured correctly!!" - } - - $strOutput = Invoke-Command -ComputerName $ServerName -ScriptBlock $scriptBlock -ArgumentList $NCCertHash,$ServerCert - - [System.Windows.Forms.MessageBox]::Show("$strOutput") - -} #End Function VerifyCerts - -Import-Module .\NetworkControllerWorkloadHelpers.psm1 -Force -. .\NetworkControllerRESTWrappers.ps1 -ComputerName $NCIP -Username $null -Password $null -Credential $Script:NCCredential - -$ncVMCredentials = [System.Management.Automation.PSCredential]::Empty - -$InputData = @() - -$LNs = @{} -$LNs.Name = "Logical Networks" -$LNs.Value = @() -$LNs.Value += {GenerateArrayForm -HandlerFunc "Get-NCLogicalNetwork" -RemoveFunc "Remove-NCLogicalNetwork" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$InputData += $LNs - -$VNs = @{} -$VNs.Name = "VirtualNetworks" -$VNs.Value = @() -$VNs.Value += {GenerateArrayForm -HandlerFunc "Get-NCVirtualNetwork" -RemoveFunc "Remove-NCVirtualNetwork" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$InputData += $VNs - - -$VSs = @{} -$VSs.Name = "Virtual Servers" -$VSs.Value = @() -$VSs.Value += {GenerateArrayForm -HandlerFunc "Get-NCVirtualServer" -RemoveFunc "Remove-NCVirtualServer" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$InputData += $VSs - -$NIs = @{} -$NIs.Name = "Network Interfaces" -$NIs.Value = @() -$NIs.Value += {GenerateArrayForm -HandlerFunc "Get-NCNetworkInterface" -RemoveFunc "Remove-NCNetworkInterface" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$NIs.Value += {PutNetworkInterface -HandlerFunc "New-NCNetworkInterface" -NCIP $NCIP -NCCredential $Script:NCCredential} -$NIs.Value += {RemoveObjForm -HandlerFunc "Remove-NCNetworkInterface" -GetFunc "Get-NCNetworkInterface" -NCIP $NCIP -NCCredential $Script:NCCredential} -$InputData += $NIs - - -$NS = @{} -$NS.Name = "Servers" -$NS.Value = @() -$NS.Value = {GenerateArrayForm -HandlerFunc "Get-NCServer" -RemoveFunc "Remove-NCServer" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$InputData += $NS - -$LB = @{} -$LB.Name = "Load Balancer" -$LB.Value = @() -$LB.Value += {GenerateArrayForm -HandlerFunc "Get-NCLoadBalancer" -RemoveFunc "Remove-NCLoadBalancer" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$LB.Value += {PutLoadBalancer -HandlerFunc "New-LoadBalancerVIP" -NCIP $NCIP -NCCredential $Script:NCCredential} -$InputData += $LB - -$Acls = @{} -$Acls.Name = "Access Control List" -$Acls.Value = @() -$Acls.Value += {GenerateArrayForm -HandlerFunc "Get-NCAccessControlList" -RemoveFunc "Remove-NCAccessControlList" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$InputData += $Acls - -$Credentials = @{} -$Credentials.Name = "NC Credentials" -$Credentials.Value = @() -$Credentials.Value += {GenerateArrayForm -HandlerFunc "Get-NCCredential" -RemoveFunc "Remove-NCCredential" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$InputData += $Credentials - -$LBM = @{} -$LBM.Name = "Load Balancer Manager" -$LBM.Value = @() -$LBM.Value += {GenerateArrayForm -HandlerFunc "Get-NCLoadbalancerManager" -RemoveFunc "null" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$InputData += $LBM - -$LBMUX = @{} -$LBMUX.Name = "Load Balancer Mux" -$LBMUX.Value = @() -$LBMUX.Value += {GenerateArrayForm -HandlerFunc "Get-NCLoadbalancerMux" -RemoveFunc "Remove-NCLoadBalancerMux" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$InputData += $LBMUX - -$CR = @{} -$CR.Name = "Diagnostics Panel" -$CR.Value = @() -$CR.Value += {GenerateArrayForm -HandlerFunc "Get-NCConnectivityCheckResult" -RemoveFunc "null" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$InputData += $CR - -$Publicip = @{} -$Publicip.Name = "Public IP Addresses" -$Publicip.Value = @() -$Publicip.Value += {GenerateArrayForm -HandlerFunc "Get-NCPublicIPAddress" -RemoveFunc "null" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$InputData += $Publicip - -$RT = @{} -$RT.Name = "Route Tables" -$RT.Value = @() -$RT.Value += {GenerateArrayForm -HandlerFunc "Get-NCRouteTable" -RemoveFunc "null" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$InputData += $RT - -$Gapteways = @{} -$Gapteways.Name = "Gateways" -$Gapteways.Value = @() -$Gapteways.Value += {GenerateArrayForm -HandlerFunc "Get-NCGateway" -RemoveFunc "null" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$InputData += $Gapteways - -$GatewayPools = @{} -$GatewayPools.Name = "Gateway Pools" -$GatewayPools.Value = @() -$GatewayPools.Value += {GenerateArrayForm -HandlerFunc "Get-NCGatewayPool" -RemoveFunc "Remove-NCGatewayPool" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$InputData += $GatewayPools - -$VirtualGateway = @{} -$VirtualGateway.Name = "Virtual Gateway" -$VirtualGateway.Value = @() -$VirtualGateway.Value += {GenerateArrayForm -HandlerFunc "Get-NCVirtualGateway" -RemoveFunc "Remove-NCVirtualGateway" -NCIP $NCIP -NCCredential $Script:NCCredential -EnableMultiWindow $Script:EnableMultiWindow} -$InputData += $VirtualGateway - -if(-not $script:IsModule) -{ - #Call the Function - GenerateMainForm -DataArr $InputData -} \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/SDN/SDNExpress.ps1 b/azure_jumpstart_hcibox/artifacts/SDN/SDNExpress.ps1 deleted file mode 100644 index 67b2e10b28..0000000000 --- a/azure_jumpstart_hcibox/artifacts/SDN/SDNExpress.ps1 +++ /dev/null @@ -1,304 +0,0 @@ -# -------------------------------------------------------------- -# Copyright © Microsoft Corporation. All Rights Reserved. -# Microsoft Corporation (or based on where you live, one of its affiliates) licenses this sample code for your internal testing purposes only. -# Microsoft provides the following sample code AS IS without warranty of any kind. The sample code arenot supported under any Microsoft standard support program or services. -# Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. -# The entire risk arising out of the use or performance of the sample code remains with you. -# In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the code be liable for any damages whatsoever -# (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) -# arising out of the use of or inability to use the sample code, even if Microsoft has been advised of the possibility of such damages. -# --------------------------------------------------------------- -<# -.SYNOPSIS - Deploys and configures the Microsoft SDN infrastructure, - including creation of the network controller, Software Load Balancer MUX - and gateway VMs. Then the VMs and Hyper-V hosts are configured to be - used by the Network Controller. When this script completes the SDN - infrastructure is ready to be fully used for workload deployments. -.EXAMPLE - .\SDNExpress.ps1 -ConfigurationDataFile .\MyConfig.psd1 - Reads in the configuration from a PSD1 file that contains a hash table - of settings data. -.EXAMPLE - .\SDNExpress -ConfigurationData $MyConfigurationData - Uses the hash table that is passed in as the configuration data. This - parameter set is useful when programatically generating the - configuration data. -.EXAMPLE - .\SDNExpress - Displays a user interface for interactively defining the configuraiton - data. At the end you have the option to save as a configuration file - before deploying. -.NOTES - Prerequisites: - * All Hyper-V hosts must have Hyper-V enabled and the Virtual Switch - already created. - * All Hyper-V hosts must be joined to Active Directory. - * The physical network must be preconfigured for the necessary subnets and - VLANs as defined in the configuration data. - * The VHD specified in the configuration data must be reachable from the - computer where this script is run. -#> - -[CmdletBinding(DefaultParameterSetName="NoParameters")] -param( - [Parameter(Mandatory=$true,ParameterSetName="ConfigurationFile")] - [String] $ConfigurationDataFile=$null, - [Parameter(Mandatory=$true,ParameterSetName="ConfigurationData")] - [object] $ConfigurationData=$null, - [Switch] $SkipValidation, - [Switch] $SkipDeployment, - [PSCredential] $DomainJoinCredential = $null, - [PSCredential] $NCCredential = $null, - [PSCredential] $LocalAdminCredential = $null - ) - - -# Script version, should be matched with the config files -$ScriptVersion = "2.0" - - -import-module networkcontroller -import-module .\SDNExpressModule.psm1 -force - -write-SDNExpressLog "*** Begin SDN Express Deployment ***" -write-SDNExpressLog "ParameterSet: $($psCmdlet.ParameterSetName)" -write-SDNExpressLog " -ConfigurationDataFile: $ConfigurationDataFile" -write-SDNExpressLog " -ConfigurationData: $ConfigurationData" -write-SDNExpressLog " -SkipValidation: $SkipValidation" -write-SDNExpressLog " -SkipDeployment: $SkipValidation" - -if ($psCmdlet.ParameterSetName -eq "NoParameters") { - write-sdnexpresslog "Begin interactive mode." - - import-module .\SDNExpressUI.psm1 -force - $configData = SDNExpressUI - if ($configData -eq $null) - { - # user cancelled - exit - } - -} elseif ($psCmdlet.ParameterSetName -eq "ConfigurationFile") { - write-sdnexpresslog "Using configuration file passed in by parameter." - $configdata = [hashtable] (iex (gc $ConfigurationDataFile | out-string)) -} elseif ($psCmdlet.ParameterSetName -eq "ConfigurationData") { - write-sdnexpresslog "Using configuration data object passed in by parameter." - $configdata = $configurationData -} - -if ($Configdata.ScriptVersion -ne $scriptversion) { - write-error "Configuration file version $($ConfigData.ScriptVersion) is not compatible with this version of SDN express. Please update your config file to match the version $scriptversion example." - return -} - -function GetPassword -{ - param( - [String] $SecurePasswordText, - [PSCredential] $Credential, - [String] $Message, - [String] $UserName - ) - if ([String]::IsNullOrEmpty($SecurePasswordText) -and ($Credential -eq $null)) { - write-sdnexpresslog "No credentials found on command line or in config file. Prompting." - $Credential = get-Credential -Message $Message -UserName $UserName - } - - if ($Credential -ne $null) { - write-sdnexpresslog "Using credentials from the command line." - return $Credential.GetNetworkCredential().Password - } - - try { - write-sdnexpresslog "Using credentials from config file." - $securepassword = $SecurePasswordText | convertto-securestring -erroraction Ignore - $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword) - return [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) - } catch { - write-sdnexpresslog "Unable to decrpypt credentials in config file. Could be from a different user or generated on different computer. Prompting instead." - $Credential = get-Credential -Message $Message -UserName $UserName - if ($credential -eq $null) { - write-sdnexpresslog "User cancelled credential input. Exiting." - exit - } - return $Credential.GetNetworkCredential().Password - } - -} - -$DomainJoinPassword = GetPassword $ConfigData.DomainJoinSecurePassword $DomainJoinCredential "Enter credentials for joining VMs to the AD domain." $configdata.DomainJoinUserName -$NCPassword = GetPassword $ConfigData.NCSecurePassword $NCCredential "Enter credentials for the Network Controller to use." $configdata.NCUserName -$LocalAdminPassword = GetPassword $ConfigData.LocalAdminSecurePassword $LocalAdminCredential "Enter the password for the local administrator of newly created VMs. Username is ignored." "Administrator" - -$NCSecurePassword = $NCPassword | convertto-securestring -AsPlainText -Force - -$credential = New-Object System.Management.Automation.PsCredential($ConfigData.NCUsername, $NCSecurePassword) - -$ManagementSubnetBits = $ConfigData.ManagementSubnet.Split("/")[1] -$PASubnetBits = $ConfigData.PASubnet.Split("/")[1] -$DomainJoinUserNameDomain = $ConfigData.DomainJoinUserName.Split("\")[0] -$DomainJoinUserNameName = $ConfigData.DomainJoinUserName.Split("\")[1] -$LocalAdminDomainUserDomain = $ConfigData.LocalAdminDomainUser.Split("\")[0] -$LocalAdminDomainUserName = $ConfigData.LocalAdminDomainUser.Split("\")[1] - -if ($ConfigData.VMProcessorCount -eq $null) {$ConfigData.VMProcessorCount = 4} -if ($ConfigData.VMMemory -eq $null) {$ConfigData.VMMemory = 3GB} - -write-SDNExpressLog "STAGE 1: Create VMs" - -$params = @{ - 'ComputerName'=''; - 'VMLocation'=$ConfigData.VMLocation; - 'VMName'=''; - 'VHDSrcPath'=$ConfigData.VHDPath; - 'VHDName'=$ConfigData.VHDFile; - 'VMMemory'=$ConfigData.VMMemory; - 'VMProcessorCount'=$ConfigData.VMProcessorCount; - 'Nics'=@(); - 'CredentialDomain'=$DomainJoinUserNameDomain; - 'CredentialUserName'=$DomainJoinUserNameName; - 'CredentialPassword'=$DomainJoinPassword; - 'JoinDomain'=$ConfigData.JoinDomain; - 'LocalAdminPassword'=$LocalAdminPassword; - 'DomainAdminDomain'=$LocalAdminDomainUserDomain; - 'DomainAdminUserName'=$LocalAdminDomainUserName; - 'SwitchName'=$ConfigData.SwitchName -} - -if (![String]::IsNullOrEmpty($ConfigData.ProductKey)) { - $params.ProductKey = $ConfigData.ProductKey -} -if (![String]::IsNullOrEmpty($ConfigData.Locale)) { - $params.Locale = $ConfigData.Locale -} -if (![String]::IsNullOrEmpty($ConfigData.TimeZone)) { - $params.TimeZone = $ConfigData.TimeZone -} - -write-SDNExpressLog "STAGE 1.1: Create NC VMs" -foreach ($NC in $ConfigData.NCs) { - $params.ComputerName=$NC.HostName; - $params.VMName=$NC.ComputerName; - $params.Nics=@( - @{Name="Management"; MacAddress=$NC.MacAddress; IPAddress="$($NC.ManagementIP)/$ManagementSubnetBits"; Gateway=$ConfigData.ManagementGateway; DNS=$ConfigData.ManagementDNS; VLANID=$ConfigData.ManagementVLANID} - ); - - New-SDNExpressVM @params -} - -write-SDNExpressLog "STAGE 1.2: Create Mux VMs" - -foreach ($Mux in $ConfigData.Muxes) { - $params.ComputerName=$mux.HostName; - $params.VMName=$mux.ComputerName; - $params.Nics=@( - @{Name="Management"; MacAddress=$Mux.MacAddress; IPAddress="$($Mux.ManagementIP)/$ManagementSubnetBits"; Gateway=$ConfigData.ManagementGateway; DNS=$ConfigData.ManagementDNS; VLANID=$ConfigData.ManagementVLANID}, - @{Name="HNVPA"; MacAddress=$Mux.PAMacAddress; IPAddress="$($Mux.PAIPAddress)/$PASubnetBits"; VLANID=$ConfigData.PAVLANID; IsMuxPA=$true} - ); - - New-SDNExpressVM @params -} - -write-SDNExpressLog "STAGE 1.3: Create Gateway VMs" - -foreach ($Gateway in $ConfigData.Gateways) { - $params.ComputerName=$Gateway.HostName; - $params.InstallRasRoutingProtocols = $true; - $params.VMName=$Gateway.ComputerName; - $params.Nics=@( - @{Name="Management"; MacAddress=$Gateway.MacAddress; IPAddress="$($Gateway.ManagementIP)/$ManagementSubnetBits"; Gateway=$ConfigData.ManagementGateway; DNS=$ConfigData.ManagementDNS; VLANID=$ConfigData.ManagementVLANID} - @{Name="FrontEnd"; MacAddress=$Gateway.FrontEndMac; IPAddress="$($Gateway.FrontEndIp)/$PASubnetBits"; VLANID=$ConfigData.PAVLANID}, - @{Name="BackEnd"; MacAddress=$Gateway.BackEndMac; VLANID=$ConfigData.PAVLANID} - ); - - New-SDNExpressVM @params -} - -write-SDNExpressLog "STAGE 2: Network Controller Configuration" - -$NCNodes = @() -foreach ($NC in $ConfigData.NCs) { - $NCNodes += $NC.ComputerName -} - -WaitforComputertobeready $NCNodes $false - - -New-SDNExpressNetworkController -ComputerNames $NCNodes -RESTName $ConfigData.RestName -Credential $Credential - -write-SDNExpressLog "STAGE 2.1: Getting REST cert thumbprint in order to find it in local root store." -$NCHostCertThumb = invoke-command -ComputerName $NCNodes[0] { - param( - $RESTName - ) - return (get-childitem "cert:\localmachine\my" | where {$_.Subject -eq "CN=$RestName"}).Thumbprint -} -ArgumentList $ConfigData.RestName - -$NCHostCert = get-childitem "cert:\localmachine\root\$NCHostCertThumb" - -$params = @{ - 'RestName' = $ConfigData.RestName; - 'MacAddressPoolStart' = $ConfigData.SDNMacPoolStart; - 'MacAddressPoolEnd' = $ConfigData.SDNMacPoolEnd; - 'NCHostCert' = $NCHostCert - 'NCUsername' = $ConfigData.NCUsername; - 'NCPassword' = $NCPassword -} -New-SDNExpressVirtualNetworkManagerConfiguration @Params - -$params = @{ - 'RestName' = $ConfigData.RestName; - 'PrivateVIPPrefix' = $ConfigData.PrivateVIPSubnet; - 'PublicVIPPrefix' = $ConfigData.PublicVIPSubnet -} - -New-SDNExpressLoadBalancerManagerConfiguration @Params - -$params = @{ - 'RestName' = $ConfigData.RestName; - 'AddressPrefix' = $ConfigData.PASubnet; - 'VLANID' = $ConfigData.PAVLANID; - 'DefaultGateways' = $ConfigData.PAGateway; - 'IPPoolStart' = $ConfigData.PAPoolStart; - 'IPPoolEnd' = $ConfigData.PAPoolEnd -} -Add-SDNExpressVirtualNetworkPASubnet @params - -write-SDNExpressLog "STAGE 3: Host Configuration" - -foreach ($h in $ConfigData.hypervhosts) { - Add-SDNExpressHost -ComputerName $h -RestName $ConfigData.RestName -HostPASubnetPrefix $ConfigData.PASubnet -NCHostCert $NCHostCert -Credential $Credential -VirtualSwitchName $ConfigData.SwitchName -} - -write-SDNExpressLog "STAGE 4: Mux Configuration" - -foreach ($Mux in $ConfigData.muxes) { - Add-SDNExpressMux -ComputerName $Mux.ComputerName -PAMacAddress $Mux.PAMacAddress -LocalPeerIP $Mux.PAIPAddress -MuxASN $ConfigData.SDNASN -Routers $ConfigData.Routers -RestName $ConfigData.RestName -NCHostCert $NCHostCert -Credential $Credential -} - -write-SDNExpressLog "STAGE 5: Gateway Configuration" - -New-SDNExpressGatewayPool -IsTypeAll -PoolName $ConfigData.PoolName -Capacity $ConfigData.Capacity -GreSubnetAddressPrefix $ConfigData.GreSubnet -RestName $ConfigData.RestName -Credential $Credential - -foreach ($G in $ConfigData.Gateways) { - $params = @{ - 'RestName'=$ConfigData.RestName - 'ComputerName'=$g.computername - 'HostName'=$g.Hostname - 'NCHostCert'= $NCHostCert - 'PoolName'=$ConfigData.PoolName - 'FrontEndIp'=$G.FrontEndIP - 'FrontEndLogicalNetworkName'='HNVPA' - 'FrontEndAddressPrefix'=$ConfigData.PASubnet - 'FrontEndMac'=$G.FrontEndMac - 'BackEndMac'=$G.BackEndMac - 'RouterASN'=$ConfigData.Routers[0].RouterASN - 'RouterIP'=$ConfigData.Routers[0].RouterIPAddress - 'LocalASN'=$ConfigData.SDNASN - } - New-SDNExpressGateway @params -} - - -write-SDNExpressLog "SDN Express deployment complete." diff --git a/azure_jumpstart_hcibox/artifacts/SDN/SDNExpressModule.psm1 b/azure_jumpstart_hcibox/artifacts/SDN/SDNExpressModule.psm1 deleted file mode 100644 index 2bd66bfb92..0000000000 --- a/azure_jumpstart_hcibox/artifacts/SDN/SDNExpressModule.psm1 +++ /dev/null @@ -1,1975 +0,0 @@ -# -------------------------------------------------------------- -# Copyright © Microsoft Corporation. All Rights Reserved. -# Microsoft Corporation (or based on where you live, one of its affiliates) licenses this sample code for your internal testing purposes only. -# Microsoft provides the following sample code AS IS without warranty of any kind. The sample code arenot supported under any Microsoft standard support program or services. -# Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. -# The entire risk arising out of the use or performance of the sample code remains with you. -# In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the code be liable for any damages whatsoever -# (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) -# arising out of the use of or inability to use the sample code, even if Microsoft has been advised of the possibility of such damages. -# --------------------------------------------------------------- - -$VerbosePreference = 'Continue' - -<# - -ooooo ooo .oooooo. .oooooo. . -`888b. `8' d8P' `Y8b d8P' `Y8b .o8 - 8 `88b. 8 888 888 oooo d8b .ooooo. .oooo. .o888oo .ooooo. - 8 `88b. 8 888 888 `888""8P d88' `88b `P )88b 888 d88' `88b - 8 `88b.8 888 888 888 888ooo888 .oP"888 888 888ooo888 - 8 `888 `88b ooo `88b ooo 888 888 .o d8( 888 888 . 888 .o -o8o `8 `Y8bood8P' `Y8bood8P' d888b `Y8bod8P' `Y888""8o "888" `Y8bod8P' - - - -#> -function New-SDNExpressNetworkController -{ - param( - [String[]] $ComputerNames, - [String] $RESTName, - [String] $ManagementSecurityGroupName = "", - [String] $ClientSecurityGroupName = "", - [PSCredential] $Credential = $null, - [Switch] $Force - ) - write-sdnexpresslog "New-SDNExpressNetworkController" - write-sdnexpresslog " -ComputerNames: $ComputerNames" - write-sdnexpresslog " -RestName: $RestName" - write-sdnexpresslog " -ManagementSecurityGroup: $ManagementSecurityGroup" - write-sdnexpresslog " -ClientSecurityGroup: $ClientSecurityGroup" - write-sdnexpresslog " -Force: $Force" - - $RESTName = $RESTNAme.ToUpper() - - write-sdnexpresslog ("Checking if Controller already deployed by looking for REST response.") - try { - get-networkcontrollerCredential -ConnectionURI "https://$RestName" -Credential $Credential | out-null - if (!$force) { - write-sdnexpresslog "Network Controller at $RESTNAME already exists, exiting New-SDNExpressNetworkController." - return - } - } - catch { - write-sdnexpresslog "Network Controller does not exist, will continue." - } - - write-sdnexpresslog "Setting properties and adding NetworkController role on all computers in parallel." - invoke-command -ComputerName $ComputerNames { - reg add hklm\system\currentcontrolset\services\tcpip6\parameters /v DisabledComponents /t REG_DWORD /d 255 /f | out-null - Set-Item WSMan:\localhost\Shell\MaxConcurrentUsers -Value 100 | out-null - Set-Item WSMan:\localhost\MaxEnvelopeSizekb -Value 7000 | out-null - - add-windowsfeature NetworkController -IncludeAllSubFeature -IncludeManagementTools -Restart | out-null - } - - write-sdnexpresslog "Creating local temp directory." - - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force - $TempDir = $TempFile.FullName - New-Item -ItemType Directory -Force -Path $TempDir | out-null - - write-sdnexpresslog "Temp directory is: $($TempFile.FullName)" - write-sdnexpresslog "Creating REST cert on: $($computernames[0])" - - $RestCertPfxData = invoke-command -computername $ComputerNames[0] { - param( - [String] $RestName - ) - $verbosepreference=$using:verbosepreference - - $Cert = get-childitem "Cert:\localmachine\my" | where {$_.Subject.ToUpper().StartsWith("CN=$RestName".ToUpper())} - - if ($Cert -eq $Null) { - write-verbose "Creating new REST certificate." - $Cert = New-SelfSignedCertificate -Type Custom -KeySpec KeyExchange -Subject "CN=$RESTName" -KeyExportPolicy Exportable -HashAlgorithm sha256 -KeyLength 2048 -CertStoreLocation "Cert:\LocalMachine\My" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.2") - } else { - write-verbose "Found existing REST certficate." - $HasServerEku = ($cert.EnhancedKeyUsageList | where {$_.ObjectId -eq "1.3.6.1.5.5.7.3.1"}) -ne $null - $HasClientEku = ($cert.EnhancedKeyUsageList | where {$_.ObjectId -eq "1.3.6.1.5.5.7.3.2"}) -ne $null - - if (!$HasServerEku) { - throw "Rest cert exists on $(hostname) but is missing the EnhancedKeyUsage for Server Authentication." - } - if (!$HasClientEku) { - throw "Rest cert exists but $(hostname) is missing the EnhancedKeyUsage for Client Authentication." - } - write-verbose "Existing certificate meets criteria. Exporting." - } - - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force | out-null - [System.io.file]::WriteAllBytes($TempFile.FullName, $cert.Export("PFX", "secret")) - $CertData = Get-Content $TempFile.FullName -Encoding Byte - Remove-Item $TempFile.FullName -Force | out-null - - return $CertData - - } -ArgumentList $RestName - - write-sdnexpresslog "Temporarily exporting Cert to My store." - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force - $RestCertPfxData | set-content $TempFile.FullName -Encoding Byte - $pwd = ConvertTo-SecureString "secret" -AsPlainText -Force - $cert = import-pfxcertificate -filepath $TempFile.FullName -certstorelocation "cert:\localmachine\my" -password $pwd -exportable - Remove-Item $TempFile.FullName -Force - - $RESTCertThumbprint = $cert.Thumbprint - write-sdnexpresslog "REST cert thumbprint: $RESTCertThumbprint" - write-sdnexpresslog "Exporting REST cert to PFX and CER in temp directory." - - [System.io.file]::WriteAllBytes("$TempDir\$RESTName.pfx", $cert.Export("PFX", "secret")) - Export-Certificate -Type CERT -FilePath "$TempDir\$RESTName" -cert $cert | out-null - - write-sdnexpresslog "Importing REST cert (public key only) into Root store." - import-certificate -filepath "$TempDir\$RESTName" -certstorelocation "cert:\localmachine\root" | out-null - - write-sdnexpresslog "Deleting REST cert from My store." - remove-item -path cert:\localmachine\my\$RESTCertThumbprint - - write-sdnexpresslog "Installing REST cert to my and root store of each NC node." - - foreach ($ncnode in $ComputerNames) { - write-sdnexpresslog "Installing REST cert to my and root store of: $ncnode" - invoke-command -computername $ncnode { - param( - [String] $RESTName, - [byte[]] $RESTCertPFXData, - [String] $RESTCertThumbprint - ) - - $pwd = ConvertTo-SecureString "secret" -AsPlainText -Force - - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force - $RESTCertPFXData | set-content $TempFile.FullName -Encoding Byte - - $Cert = get-childitem "Cert:\localmachine\my" | where {$_.Subject.ToUpper().StartsWith("CN=$RestName".ToUpper())} - - if ($Cert -eq $null) { - $cert = import-pfxcertificate -filepath $TempFile.FullName -certstorelocation "cert:\localmachine\my" -password $pwd -Exportable - } else { - if ($cert.Thumbprint -ne $RestCertThumbprint) { - Remove-Item $TempFile.FullName -Force - throw "REST cert already exists in My store on $(hostname), but thumbprint does not match cert on other nodes." - } - } - - $targetCertPrivKey = $Cert.PrivateKey - $privKeyCertFile = Get-Item -path "$ENV:ProgramData\Microsoft\Crypto\RSA\MachineKeys\*" | where {$_.Name -eq $targetCertPrivKey.CspKeyContainerInfo.UniqueKeyContainerName} - $privKeyAcl = Get-Acl $privKeyCertFile - $permission = "NT AUTHORITY\NETWORK SERVICE","Read","Allow" - $accessRule = new-object System.Security.AccessControl.FileSystemAccessRule $permission - $privKeyAcl.AddAccessRule($accessRule) - Set-Acl $privKeyCertFile.FullName $privKeyAcl - - $Cert = get-childitem "Cert:\localmachine\root\$RestCertThumbprint" -erroraction Ignore - if ($cert -eq $Null) { - $cert = import-pfxcertificate -filepath $TempFile.FullName -certstorelocation "cert:\localmachine\root" -password $pwd - } - - Remove-Item $TempFile.FullName -Force - } -Argumentlist $RESTName, $RESTCertPFXData, $RESTCertThumbprint - - } - - # Create Node cert for each NC - - foreach ($ncnode in $ComputerNames) { - write-sdnexpresslog "Creating node cert for: $ncnode" - - [byte[]] $CertData = invoke-command -computername $ncnode { - - # Set Trusted Hosts - Set-Item WSMan:\localhost\Client\TrustedHosts * -Confirm:$false -Force - - $NodeFQDN = (Get-WmiObject win32_computersystem).DNSHostName+"."+(Get-WmiObject win32_computersystem).Domain - $Cert = get-childitem "Cert:\localmachine\my" | where {$_.Subject.ToUpper().StartsWith("CN=$NodeFQDN".ToUpper())} - - if ($Cert -eq $null) { - $cert = New-SelfSignedCertificate -Type Custom -KeySpec KeyExchange -Subject "CN=$NodeFQDN" -KeyExportPolicy Exportable -HashAlgorithm sha256 -KeyLength 2048 -CertStoreLocation "Cert:\LocalMachine\My" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.2") - } else { - $HasServerEku = ($cert.EnhancedKeyUsageList | where {$_.ObjectId -eq "1.3.6.1.5.5.7.3.1"}) -ne $null - $HasClientEku = ($cert.EnhancedKeyUsageList | where {$_.ObjectId -eq "1.3.6.1.5.5.7.3.2"}) -ne $null - - if (!$HasServerEku) { - throw "Node cert exists on $(hostname) but is missing the EnhancedKeyUsage for Server Authentication." - } - if (!$HasClientEku) { - throw "Node cert exists but $(hostname) is missing the EnhancedKeyUsage for Client Authentication." - } - } - - $targetCertPrivKey = $Cert.PrivateKey - $privKeyCertFile = Get-Item -path "$ENV:ProgramData\Microsoft\Crypto\RSA\MachineKeys\*" | where {$_.Name -eq $targetCertPrivKey.CspKeyContainerInfo.UniqueKeyContainerName} - $privKeyAcl = Get-Acl $privKeyCertFile - $permission = "NT AUTHORITY\NETWORK SERVICE","Read","Allow" - $accessRule = new-object System.Security.AccessControl.FileSystemAccessRule $permission - $privKeyAcl.AddAccessRule($accessRule) - Set-Acl $privKeyCertFile.FullName $privKeyAcl - - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force | out-null - [System.io.file]::WriteAllBytes($TempFile.FullName, $cert.Export("PFX", "secret")) - $CertData = Get-Content $TempFile.FullName -Encoding Byte - Remove-Item $TempFile.FullName -Force | out-null - - return $CertData - } - - foreach ($othernode in $ComputerNames) { - write-sdnexpresslog "Installing node cert for $ncnode into root store of $othernode." - - invoke-command -computername $othernode { - param( - [Byte[]] $CertData - ) - - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force - - $CertData | set-content $TempFile.FullName -Encoding Byte - $pwd = ConvertTo-SecureString "secret" -AsPlainText -Force - $cert = import-pfxcertificate -filepath $TempFile.FullName -certstorelocation "cert:\localmachine\root" -password $pwd - Remove-Item $TempFile.FullName -Force - } -ArgumentList (,$CertData) - } - } - - write-sdnexpresslog "Configuring Network Controller role using node: $($ComputerNames[0])" - invoke-command -computername $ComputerNames[0] { - param( - [String] $RestName, - [String] $ManagementSecurityGroup, - [String] $ClientSecurityGroup, - [String[]] $ComputerNames, - [PSCredential] $Credential - ) - $SelfFQDN = (Get-WmiObject win32_computersystem).DNSHostName+"."+(Get-WmiObject win32_computersystem).Domain - - try { $controller = get-networkcontroller -erroraction Ignore } catch {} - if ($controller -ne $null) { - if ($force) { - uninstall-networkcontroller -force - uninstall-networkcontrollercluster -force - } else { - return - } - } - - $Nodes = @() - - foreach ($server in $ComputerNames) { - $NodeFQDN = "$server."+(Get-WmiObject win32_computersystem).Domain - - $cert = get-childitem "Cert:\localmachine\root" | where {$_.Subject.ToUpper().StartsWith("CN=$nodefqdn".ToUpper())} - - $nic = get-netadapter - if ($nic.count -gt 1) { - write-verbose ("WARNING: Invalid number of network adapters found in network Controller node.") - write-verbose ("WARNING: Using first adapter returned: $($nic[0].name)") - $nic = $nic[0] - } elseif ($nic.count -eq 0) { - write-verbose ("ERROR: No network adapters found in network Controller node.") - throw "Network controller node requires at least one network adapter." - } - - $nodes += New-NetworkControllerNodeObject -Name $server -Server $NodeFQDN -FaultDomain ("fd:/"+$server) -RestInterface $nic.Name -NodeCertificate $cert -verbose - } - - $RESTCert = get-childitem "Cert:\localmachine\root" | where {$_.Subject.ToUpper().StartsWith("CN=$RESTName".ToUpper())} - - $params = @{ - 'Node'=$nodes; - 'CredentialEncryptionCertificate'=$RESTCert; - 'Credential'=$Credential; - } - - if ([string]::isnullorempty($ManagementSecurityGroupName)) { - $params.add('ClusterAuthentication', 'X509'); - } else { - $params.add('ClusterAuthentication', 'Kerberos'); - $params.add('ManagementSecurityGroup', $ManagementSecurityGroup) - } - - Install-NetworkControllerCluster @Params -Force | out-null - - $params = @{ - 'Node'=$nodes; - 'ServerCertificate'=$RESTCert; - 'Credential'=$Credential; - } - - if ([string]::isnullorempty($ClientSecurityGroupName)) { - $params.add('ClientAuthentication', 'None'); - } else { - $params.add('ClusterAuthentication', 'Kerberos'); - $params.add('ClientSecurityGroup', $ClientSecurityGroup) - } - - if (![string]::isnullorempty($RestIpAddress)) { - $params.add('RestIPAddress', 'addr/bits'); - } else { - $params.add('RestName', $RESTName); - } - - Install-NetworkController @params -force | out-null - - } -ArgumentList $RestName, $ManagementSecurityGroup, $ClientSecurityGroup, $ComputerNames, $Credential - - Write-SDNExpressLog "Network Controller cluster creation complete." - #Verify that SDN REST endpoint is working before returning - - $dnsServers = (Get-DnsClientServerAddress -AddressFamily ipv4).ServerAddresses | select -uniq - $dnsWorking = $true - - foreach ($dns in $dnsServers) - { - $dnsResponse = $null - $count = 0 - - while (($dnsResponse -eq $null) -or ($count -eq 30)) { - $dnsResponse = Resolve-DnsName -name $RESTName -Server $dns -ErrorAction Ignore - if ($dnsREsponse -eq $null) { - sleep 10 - } - $count++ - } - - if ($count -eq 30) { - write-sdnexpresslog "REST name not resolving from $dns after 5 minutes." - $dnsWorking = $false - } else { - write-sdnexpresslog "REST name resolved from $dns after $count tries." - } - } - - if (!$dnsWorking) { - return - } - - write-sdnexpresslog ("Checking for REST response.") - $NotResponding = $true - while ($NotResponding) { - try { - $NotResponding = $false - get-networkcontrollerCredential -ConnectionURI "https://$RestName" -Credential $Credential | out-null - } - catch { - write-sdnexpresslog "Network Controller is not responding. Will try again in 10 seconds." - sleep 10 - $NotResponding = $true - } - } - - write-sdnexpresslog ("Network controller setup is complete and ready to use.") - write-sdnexpresslog "New-SDNExpressNetworkController Exit" -} - - - - - -<# - -ooooo ooo .oooooo. .oooooo. .o88o. o8o -`888b. `8' d8P' `Y8b d8P' `Y8b 888 `" `"' - 8 `88b. 8 888 888 .ooooo. ooo. .oo. o888oo oooo .oooooooo - 8 `88b. 8 888 888 d88' `88b `888P"Y88b 888 `888 888' `88b - 8 `88b.8 888 888 888 888 888 888 888 888 888 888 - 8 `888 `88b ooo `88b ooo 888 888 888 888 888 888 `88bod8P' -o8o `8 `Y8bood8P' `Y8bood8P' `Y8bod8P' o888o o888o o888o o888o `8oooooo. - d" YD - "Y88888P' - -#> -function New-SDNExpressVirtualNetworkManagerConfiguration -{ - param( - [String] $RestName, - [String] $MacAddressPoolStart, - [String] $MacAddressPoolEnd, - [Object] $NCHostCert, - [String] $NCUsername, - [String] $NCPassword, - [PSCredential] $Credential = $null - ) - - write-sdnexpresslog "New-SDNExpressVirtualNetworkManagerConfiguration" - write-sdnexpresslog " -RestName: $RestName" - write-sdnexpresslog " -MacAddressPoolEnd: $MacAddressPoolStart" - write-sdnexpresslog " -NCHostCert: $($NCHostCert.Thumbprint)" - write-sdnexpresslog " -NCUsername: $NCUsername" - write-sdnexpresslog " -NCPassword: ********" - write-sdnexpresslog " -Credential: $($Credential.UserName)" - - $uri = "https://$RestName" - - $MacAddressPoolStart = [regex]::matches($MacAddressPoolStart.ToUpper().Replace(":", "").Replace("-", ""), '..').groups.value -join "-" - $MacAddressPoolEnd = [regex]::matches($MacAddressPoolEnd.ToUpper().Replace(":", "").Replace("-", ""), '..').groups.value -join "-" - - $MacPoolProperties = new-object Microsoft.Windows.NetworkController.MacPoolProperties - $MacPoolProperties.StartMacAddress = $MacAddressPoolStart - $MacPoolProperties.EndMacAddress = $MacAddressPoolEnd - $MacPoolObject = New-NetworkControllerMacPool -connectionuri $uri -ResourceId "DefaultMacPool" -properties $MacPoolProperties -Credential $Credential -Force - - $CredentialProperties = new-object Microsoft.Windows.NetworkController.CredentialProperties - $CredentialProperties.Type = "X509Certificate" - $CredentialProperties.Value = $NCHostCert.thumbprint - $HostCertObject = New-NetworkControllerCredential -ConnectionURI $uri -ResourceId "NCHostCert" -properties $CredentialProperties -Credential $Credential -force - - $CredentialProperties = new-object Microsoft.Windows.NetworkController.CredentialProperties - $CredentialProperties.Type = "UsernamePassword" - $CredentialProperties.UserName = $NCUsername - $CredentialProperties.Value = $NCPassword - $HostUserObject = New-NetworkControllerCredential -ConnectionURI $uri -ResourceId "NCHostUser" -properties $CredentialProperties -Credential $Credential -force - - try { - $LogicalNetworkObject = get-NetworkControllerLogicalNetwork -ConnectionURI $uri -ResourceID "HNVPA" -Credential $Credential - } - catch - { - $LogicalNetworkProperties = new-object Microsoft.Windows.NetworkController.LogicalNetworkProperties - $LogicalNetworkProperties.NetworkVirtualizationEnabled = $true - $LogicalNetworkObject = New-NetworkControllerLogicalNetwork -ConnectionURI $uri -ResourceID "HNVPA" -properties $LogicalNetworkProperties -Credential $Credential -Force - } - write-sdnexpresslog "New-SDNExpressVirtualNetworkManagerConfiguration Exit" -} - - - - - -function Add-SDNExpressVirtualNetworkPASubnet -{ - param( - [String] $RestName, - [String] $AddressPrefix, - [String] $VLANID, - [String[]] $DefaultGateways, - [Object] $IPPoolStart, - [String] $IPPoolEnd, - [PSCredential] $Credential = $null - ) - - write-sdnexpresslog "New-SDNExpressVirtualNetworkPASubnet" - write-sdnexpresslog " -RestName: $RestName" - write-sdnexpresslog " -AddressPrefix: $AddressPrefix" - write-sdnexpresslog " -VLANID: $VLANID" - write-sdnexpresslog " -DefaultGateways: $DefaultGateways" - write-sdnexpresslog " -IPPoolStart: $IPPoolStart" - write-sdnexpresslog " -IPPoolStart: $IPPoolEnd" - write-sdnexpresslog " -Credential: $($Credential.UserName)" - - $uri = "https://$RestName" - - $PALogicalSubnets = get-networkcontrollerLogicalSubnet -Connectionuri $URI -LogicalNetworkId "HNVPA" -Credential $Credential - $PALogicalSubnet = $PALogicalSubnets | where {$_.properties.AddressPrefix -eq $AddressPrefix} - - if ($PALogicalSubnet -eq $null) { - $LogicalSubnetProperties = new-object Microsoft.Windows.NetworkController.LogicalSubnetProperties - $logicalSubnetProperties.VLANId = $VLANID - $LogicalSubnetProperties.AddressPrefix = $AddressPrefix - $LogicalSubnetProperties.DefaultGateways = $DefaultGateways - - $LogicalSubnetObject = New-NetworkControllerLogicalSubnet -ConnectionURI $uri -LogicalNetworkId "HNVPA" -ResourceId $AddressPrefix.Replace("/", "_") -properties $LogicalSubnetProperties -Credential $Credential -Force - } - - $IPpoolProperties = new-object Microsoft.Windows.NetworkController.IPPoolproperties - $ippoolproperties.startipaddress = $IPPoolStart - $ippoolproperties.endipaddress = $IPPoolEnd - - $IPPoolObject = New-networkcontrollerIPPool -ConnectionURI $uri -NetworkId "HNVPA" -SubnetId $AddressPrefix.Replace("/", "_") -ResourceID $AddressPrefix.Replace("/", "_") -Properties $IPPoolProperties -force -Credential $Credential - - write-sdnexpresslog "New-SDNExpressVirtualNetworkPASubnet Exit" -} - - - - - - - -function New-SDNExpressLoadBalancerManagerConfiguration -{ - param( - [String] $RestName, - [String] $PrivateVIPPrefix, - [String] $PublicVIPPrefix, - [String] $SLBMVip = (Get-IPv4AddressInSubnet -subnet $PrivateVIPPrefix -offset 1), - [String] $PrivateVIPPoolStart = (Get-IPv4AddressInSubnet -subnet $PrivateVIPPrefix -offset 1), - [String] $PrivateVIPPoolEnd = (Get-IPv4LastAddressInSubnet -subnet $PrivateVIPPrefix), - [String] $PublicVIPPoolStart = (Get-IPv4AddressInSubnet -subnet $PublicVIPPrefix -offset 1), - [String] $PublicVIPPoolEnd = (Get-IPv4LastAddressInSubnet -subnet $PublicVIPPrefix), - [PSCredential] $Credential = $null - ) - - write-sdnexpresslog "New-SDNExpressLoadBalancerManagerConfiguration" - write-sdnexpresslog " -RestName: $RestName" - write-sdnexpresslog " -PrivateVIPPrefix: $PrivateVipPrefix" - write-sdnexpresslog " -PublicVIPPrefix: $PublicVIPPrefix" - write-sdnexpresslog " -SLBMVip: $SLBMVip" - write-sdnexpresslog " -PrivateVIPPoolStart: $PrivateVIPPoolStart" - write-sdnexpresslog " -PrivateVIPPoolEnd: $PrivateVIPPoolEnd" - write-sdnexpresslog " -PublicVIPPoolStart: $PublicVIPPoolStart" - write-sdnexpresslog " -PublicVIPPoolEnd: $PrivateVIPPoolEnd" - write-sdnexpresslog " -Credential: $($Credential.UserName)" - - $uri = "https://$RestName" - - #PrivateVIP LN - try - { - $PrivateVIPLNObject = Get-NetworkControllerLogicalNetwork -ConnectionURI $uri -ResourceID "PrivateVIP" -Credential $Credential - } - catch - { - $LogicalNetworkProperties = new-object Microsoft.Windows.NetworkController.LogicalNetworkProperties - $LogicalNetworkProperties.NetworkVirtualizationEnabled = $false - $LogicalNetworkProperties.Subnets = @() - $LogicalNetworkProperties.Subnets += new-object Microsoft.Windows.NetworkController.LogicalSubnet - $logicalNetworkProperties.Subnets[0].ResourceId = $PrivateVIPPrefix.Replace("/", "_") - $logicalNetworkProperties.Subnets[0].Properties = new-object Microsoft.Windows.NetworkController.LogicalSubnetProperties - $logicalNetworkProperties.Subnets[0].Properties.AddressPrefix = $PrivateVIPPrefix - $logicalNetworkProperties.Subnets[0].Properties.DefaultGateways = @(Get-IPv4AddressInSubnet -subnet $PrivateVIPPrefix) - - $PrivateVIPLNObject = New-NetworkControllerLogicalNetwork -ConnectionURI $uri -ResourceID "PrivateVIP" -properties $LogicalNetworkProperties -Credential $Credential -Force - } - - $IPpoolProperties = new-object Microsoft.Windows.NetworkController.IPPoolproperties - $ippoolproperties.startipaddress = $PrivateVIPPoolStart - $ippoolproperties.endipaddress = $PrivateVIPPoolEnd - - $PrivatePoolObject = new-networkcontrollerIPPool -ConnectionURI $uri -NetworkId "PrivateVIP" -SubnetId $PrivateVIPPrefix.Replace("/", "_") -ResourceID $PrivateVIPPrefix.Replace("/", "_") -Properties $IPPoolProperties -force - - #PublicVIP LN - try - { - $PublicVIPLNObject = get-NetworkControllerLogicalNetwork -ConnectionURI $uri -ResourceID "PublicVIP" -Credential $Credential - } - catch - { - $LogicalNetworkProperties = new-object Microsoft.Windows.NetworkController.LogicalNetworkProperties - $LogicalNetworkProperties.NetworkVirtualizationEnabled = $false - $LogicalNetworkProperties.Subnets = @() - $LogicalNetworkProperties.Subnets += new-object Microsoft.Windows.NetworkController.LogicalSubnet - $logicalNetworkProperties.Subnets[0].ResourceId = $PublicVIPPrefix.Replace("/", "_") - $logicalNetworkProperties.Subnets[0].Properties = new-object Microsoft.Windows.NetworkController.LogicalSubnetProperties - $logicalNetworkProperties.Subnets[0].Properties.AddressPrefix = $PublicVIPPrefix - $logicalNetworkProperties.Subnets[0].Properties.DefaultGateways = @(Get-IPv4AddressInSubnet -subnet $PublicVIPPrefix) - $logicalnetworkproperties.subnets[0].properties.IsPublic = $true - - $PublicVIPLNObject = New-NetworkControllerLogicalNetwork -ConnectionURI $uri -ResourceID "PublicVIP" -properties $LogicalNetworkProperties -Credential $Credential -Force - } - - $IPpoolProperties = new-object Microsoft.Windows.NetworkController.IPPoolproperties - $ippoolproperties.startipaddress = $PublicVIPPoolStart - $ippoolproperties.endipaddress = $PublicVIPPoolEnd - - $PublicPoolObject = new-networkcontrollerIPPool -ConnectionURI $uri -NetworkId "PublicVIP" -SubnetId $PublicVIPPrefix.Replace("/", "_") -ResourceID $PublicVIPPrefix.Replace("/", "_") -Properties $IPPoolProperties -force - - #SLBManager Config - - $managerproperties = new-object Microsoft.Windows.NetworkController.LoadBalancerManagerProperties - $managerproperties.LoadBalancerManagerIPAddress = $SLBMVip - $managerproperties.OutboundNatIPExemptions = @("$SLBMVIP/32") - $managerproperties.VipIPPools = @($PrivatePoolObject, $PublicPoolObject) - - $SLBMObject = new-networkcontrollerloadbalancerconfiguration -connectionuri $uri -properties $managerproperties -resourceid "config" -Force - write-sdnexpresslog "New-SDNExpressLoadBalancerManagerConfiguration Exit" -} - - - - -function New-SDNExpressiDNSConfiguration -{ - param( - [String] $RestName, - [String] $Username, - [String] $Password, - [String] $IPAddress, - [String] $ZoneName, - [PSCredential] $Credential = $null - ) - - write-sdnexpresslog "New-SDNExpressiDNSConfiguration" - write-sdnexpresslog " -RestName: $RestName" - write-sdnexpresslog " -UserName: $UserName" - write-sdnexpresslog " -Password: ********" - write-sdnexpresslog " -IPAddress: $IPAddress" - write-sdnexpresslog " -ZoneName: $ZoneName" - write-sdnexpresslog " -Credential: $($Credential.UserName)" - - $uri = "https://$RestName" - - $CredentialProperties = new-object Microsoft.Windows.NetworkController.CredentialProperties - $CredentialProperties.Type = "UsernamePassword" - $CredentialProperties.UserName = $Username - $CredentialProperties.Value = $Password - $iDNSUserObject = New-NetworkControllerCredential -ConnectionURI $uri -ResourceId "iDNSUser" -properties $CredentialProperties -Credential $Credential -force - - $iDNSProperties = new-object microsoft.windows.networkcontroller.InternalDNSServerProperties - $iDNSProperties.Connections += new-object Microsoft.Windows.NetworkController.Connection - $iDNSProperties.Connections[0].Credential = $iDNSUserObject - $iDNSProperties.Connections[0].CredentialType = $iDNSUserObject.properties.Type - $iDNSProperties.Connections[0].ManagementAddresses = $IPAddress - - $iDNSProperties.Zone = $ZoneName - - New-NetworkControllerIDnsServerConfiguration -connectionuri $RestName -ResourceId "configuration" -properties $iDNSProperties -force -credential $Credential -} - - - - - -function Enable-SDNExpressVMPort { - param( - [String] $ComputerName, - [String] $VMName, - [String] $VMNetworkAdapterName - ) - - invoke-command -ComputerName $ComputerName -ScriptBlock { - param( - [String] $VMName, - [String] $VMNetworkAdapterName - ) - $PortProfileFeatureId = "9940cd46-8b06-43bb-b9d5-93d50381fd56" - $NcVendorId = "{1FA41B39-B444-4E43-B35A-E1F7985FD548}" - - $vnic = Get-VMNetworkAdapter -VMName $VMName -Name $VMNetworkAdapterName - - $currentProfile = Get-VMSwitchExtensionPortFeature -FeatureId $PortProfileFeatureId -VMNetworkAdapter $vNic - - if ( $currentProfile -eq $null) - { - $portProfileDefaultSetting = Get-VMSystemSwitchExtensionPortFeature -FeatureId $PortProfileFeatureId - - $portProfileDefaultSetting.SettingData.ProfileId = "{$([Guid]::Empty)}" - $portProfileDefaultSetting.SettingData.NetCfgInstanceId = "{56785678-a0e5-4a26-bc9b-c0cba27311a3}" - $portProfileDefaultSetting.SettingData.CdnLabelString = "TestCdn" - $portProfileDefaultSetting.SettingData.CdnLabelId = 1111 - $portProfileDefaultSetting.SettingData.ProfileName = "Testprofile" - $portProfileDefaultSetting.SettingData.VendorId = $NcVendorId - $portProfileDefaultSetting.SettingData.VendorName = "NetworkController" - $portProfileDefaultSetting.SettingData.ProfileData = 1 - - Add-VMSwitchExtensionPortFeature -VMSwitchExtensionFeature $portProfileDefaultSetting -VMNetworkAdapter $vNic | out-null - } - else - { - $currentProfile.SettingData.ProfileId = "{$([Guid]::Empty)}" - $currentProfile.SettingData.ProfileData = 1 - Set-VMSwitchExtensionPortFeature -VMSwitchExtensionFeature $currentProfile -VMNetworkAdapter $vNic | out-null - } - } -ArgumentList $VMName, $VMNetworkAdapterName -} - - - - - - - -<# - -ooooo ooooo . -`888' `888' .o8 - 888 888 .ooooo. .oooo.o .o888oo - 888ooooo888 d88' `88b d88( "8 888 - 888 888 888 888 `"Y88b. 888 - 888 888 888 888 o. )88b 888 . -o888o o888o `Y8bod8P' 8""888P' "888" - -#> - -Function Add-SDNExpressHost { - param( - [String] $RestName, - [string] $ComputerName, - [String] $HostPASubnetPrefix, - [String] $VirtualSwitchName = "", - [Object] $NCHostCert, - [String] $iDNSIPAddress = "", - [String] $iDNSMacAddress = "", - [PSCredential] $Credential = $null - ) - - write-sdnexpresslog "New-SDNExpressHost" - write-sdnexpresslog " -RestName: $RestName" - write-sdnexpresslog " -ComputerName: $ComputerName" - write-sdnexpresslog " -HostPASubnetPrefix: $HostPASubnetPrefix" - write-sdnexpresslog " -VirtualSwitchName: $VirtualSwitchName" - write-sdnexpresslog " -NCHostCert: $($NCHostCert.Thumbprint)" - write-sdnexpresslog " -iDNSIPAddress: $iDNSIPAddress" - write-sdnexpresslog " -iDNSMacAddress: $iDNSMacAddress" - write-sdnexpresslog " -Credential: $($Credential.UserName)" - - $uri = "https://$RestName" - - write-sdnexpresslog "Get the SLBM VIP" - - $SLBMConfig = get-networkcontrollerloadbalancerconfiguration -connectionuri $uri -credential $Credential - - $slbmvip = $slbmconfig.properties.loadbalancermanageripaddress - - write-sdnexpresslog "SLBM VIP is $slbmvip" - - if ([String]::IsNullOrEmpty($VirtualSwitchName)) { - $VirtualSwitchName = invoke-command -ComputerName $ComputerName { - $vmswitch = get-vmswitch - if (($vmswitch -eq $null) -or ($vmswitch.count -eq 0)) { - throw "No virtual switch found on this host. Please create the virtual switch before adding this host." - } - if ($vmswitch.count -gt 1) { - throw "More than one virtual switch exists on the specified host. Use the VirtualSwitchName parameter to specify which switch you want configured for use with SDN." - } - - return $vmswitch.Name - } - } - - add-windowsfeature -computername $ComputerName NetworkVirtualization -IncludeAllSubFeature -IncludeManagementTools -Restart -ErrorAction Ignore | out-null - - $NodeFQDN = invoke-command -ComputerName $ComputerName { - param( - [String] $RestName, - [String] $iDNSIPAddress, - [String] $iDNSMacAddress - ) - $NodeFQDN = (Get-WmiObject win32_computersystem).DNSHostName+"."+(Get-WmiObject win32_computersystem).Domain - - $connections = "ssl:$($RestName):6640","pssl:6640" - $peerCertCName = $RestName.ToUpper() - $hostAgentCertCName = $NodeFQDN.ToUpper() - - Set-Item WSMan:\localhost\MaxEnvelopeSizekb -Value 7000 | out-null - - new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Services\NcHostAgent\Parameters" -Name "Connections" -Value $connections -PropertyType "MultiString" -Force | out-null - new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Services\NcHostAgent\Parameters" -Name "PeerCertificateCName" -Value $peerCertCName -PropertyType "String" -Force | out-null - new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Services\NcHostAgent\Parameters" -Name "HostAgentCertificateCName" -Value $hostAgentCertCName -PropertyType "String" -Force | out-null - - if (![String]::IsNullOrEmpty($iDNSIPAddress) -and ![String]::IsNullOrEmpty($iDNSMacAddress)) { - new-item -path "HKLM:\SYSTEM\CurrentControlSet\Services\NcHostAgent\Parameters\Plugins\Vnet" -name "InfraServices" -force | out-null - new-item -path "HKLM:\SYSTEM\CurrentControlSet\Services\NcHostAgent\Parameters\Plugins\Vnet\InfraServices" -name "DnsProxyService" -force | out-null - new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Services\NcHostAgent\Parameters\Plugins\Vnet\InfraServices\DnsProxyService" -Name "Port" -Value 53 -PropertyType "Dword" -Force | out-null - new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Services\NcHostAgent\Parameters\Plugins\Vnet\InfraServices\DnsProxyService" -Name "ProxyPort" -Value 53 -PropertyType "Dword" -Force | out-null - new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Services\NcHostAgent\Parameters\Plugins\Vnet\InfraServices\DnsProxyService" -Name "IP" -Value "169.254.169.254" -PropertyType "String" -Force | out-null - new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Services\NcHostAgent\Parameters\Plugins\Vnet\InfraServices\DnsProxyService" -Name "MAC" -Value $iDNSMacAddress -PropertyType "String" -Force | out-null - - new-item -path "HKLM:\SYSTEM\CurrentControlSet\Services" -name "DnsProxy" -force | out-null - new-item -path "HKLM:\SYSTEM\CurrentControlSet\Services\DnsProxy" -name "Parameters" -force | out-null - new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Services\DNSProxy\Parameters" -Name "Forwarders" -Value $iDNSIPAddress -PropertyType "String" -Force | out-null - - Enable-NetFirewallRule -DisplayGroup 'DNS Proxy Service' -ErrorAction Ignore | out-null - } - - - $fwrule = Get-NetFirewallRule -Name "Firewall-REST" -ErrorAction SilentlyContinue - if ($fwrule -eq $null) { - New-NetFirewallRule -Name "Firewall-REST" -DisplayName "Network Controller Host Agent REST" -Group "NcHostAgent" -Action Allow -Protocol TCP -LocalPort 80 -Direction Inbound -Enabled True | Out-Null - } - - $fwrule = Get-NetFirewallRule -Name "Firewall-OVSDB" -ErrorAction SilentlyContinue - if ($fwrule -eq $null) { - New-NetFirewallRule -Name "Firewall-OVSDB" -DisplayName "Network Controller Host Agent OVSDB" -Group "NcHostAgent" -Action Allow -Protocol TCP -LocalPort 6640 -Direction Inbound -Enabled True | Out-Null - } - - $fwrule = Get-NetFirewallRule -Name "Firewall-HostAgent-TCP-IN" -ErrorAction SilentlyContinue - if ($fwrule -eq $null) { - New-NetFirewallRule -Name "Firewall-HostAgent-TCP-IN" -DisplayName "Network Controller Host Agent (TCP-In)" -Group "Network Controller Host Agent Firewall Group" -Action Allow -Protocol TCP -LocalPort Any -Direction Inbound -Enabled True | Out-Null - } - - $fwrule = Get-NetFirewallRule -Name "Firewall-HostAgent-WCF-TCP-IN" -ErrorAction SilentlyContinue - if ($fwrule -eq $null) { - New-NetFirewallRule -Name "Firewall-HostAgent-WCF-TCP-IN" -DisplayName "Network Controller Host Agent WCF(TCP-In)" -Group "Network Controller Host Agent Firewall Group" -Action Allow -Protocol TCP -LocalPort 80 -Direction Inbound -Enabled True | Out-Null - } - - $fwrule = Get-NetFirewallRule -Name "Firewall-HostAgent-TLS-TCP-IN" -ErrorAction SilentlyContinue - if ($fwrule -eq $null) { - New-NetFirewallRule -Name "Firewall-HostAgent-TLS-TCP-IN" -DisplayName "Network Controller Host Agent WCF over TLS (TCP-In)" -Group "Network Controller Host Agent Firewall Group" -Action Allow -Protocol TCP -LocalPort 443 -Direction Inbound -Enabled True | Out-Null - } - - return $NodeFQDN - } -ArgumentList $RestName, $iDNSIPAddress, $iDNSMacAddress - - write-sdnexpresslog "Create and return host certificate." - - $CertData = invoke-command -ComputerName $ComputerName { - $NodeFQDN = (Get-WmiObject win32_computersystem).DNSHostName+"."+(Get-WmiObject win32_computersystem).Domain - - $cert = get-childitem "cert:\localmachine\my" | where {$_.Subject.ToUpper() -eq "CN=$NodeFQDN".ToUpper()} - if ($Cert -eq $Null) { - write-verbose "Creating new host certificate." - $Cert = New-SelfSignedCertificate -Type Custom -KeySpec KeyExchange -Subject "CN=$NodeFQDN" -KeyExportPolicy Exportable -HashAlgorithm sha256 -KeyLength 2048 -CertStoreLocation "Cert:\LocalMachine\My" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.2") - } else { - write-verbose "Found existing host certficate." - $HasServerEku = ($cert.EnhancedKeyUsageList | where {$_.ObjectId -eq "1.3.6.1.5.5.7.3.1"}) -ne $null - $HasClientEku = ($cert.EnhancedKeyUsageList | where {$_.ObjectId -eq "1.3.6.1.5.5.7.3.2"}) -ne $null - - if (!$HasServerEku) { - throw "Host cert exists on $(hostname) but is missing the EnhancedKeyUsage for Server Authentication." - } - if (!$HasClientEku) { - throw "Host cert exists but $(hostname) is missing the EnhancedKeyUsage for Client Authentication." - } - write-verbose "Existing certificate meets criteria. Exporting." - } - - $targetCertPrivKey = $Cert.PrivateKey - $privKeyCertFile = Get-Item -path "$ENV:ProgramData\Microsoft\Crypto\RSA\MachineKeys\*" | where {$_.Name -eq $targetCertPrivKey.CspKeyContainerInfo.UniqueKeyContainerName} - $privKeyAcl = Get-Acl $privKeyCertFile - $permission = "NT AUTHORITY\NETWORK SERVICE","Read","Allow" - $accessRule = new-object System.Security.AccessControl.FileSystemAccessRule $permission - $privKeyAcl.AddAccessRule($accessRule) - Set-Acl $privKeyCertFile.FullName $privKeyAcl - - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force | out-null - Export-Certificate -Type CERT -FilePath $TempFile.FullName -cert $cert | out-null - - $CertData = Get-Content $TempFile.FullName -Encoding Byte - Remove-Item $TempFile.FullName -Force | out-null - - return $CertData - } - #Hold on to CertData, we will need it later when adding the host to the NC. - - write-sdnexpresslog "Install NC host cert into Root store on host." - - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force | out-null - Export-Certificate -Type CERT -FilePath $TempFile.FullName -cert $NCHostCert | out-null - $NCHostCertData = Get-Content $TempFile.FullName -Encoding Byte - Remove-Item $TempFile.FullName -Force | out-null - - invoke-command -ComputerName $ComputerName { - param( - [byte[]] $CertData - ) - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force - - $CertData | set-content $TempFile.FullName -Encoding Byte - import-certificate -filepath $TempFile.FullName -certstorelocation "cert:\localmachine\root" | out-null - Remove-Item $TempFile.FullName -Force - } -ArgumentList (,$NCHostCertData) - - write-sdnexpresslog "Restart NC Host Agent and enable VFP." - - $VirtualSwitchId = invoke-command -ComputerName $ComputerName { - param( - [String] $VirtualSwitchName - ) - Stop-Service -Name NCHostAgent -Force | out-null - Set-Service -Name NCHostAgent -StartupType Automatic | out-null - Start-Service -Name NCHostAgent | out-null - - Disable-VmSwitchExtension -VMSwitchName $VirtualSwitchName -Name "Microsoft Windows Filtering Platform" | out-null - Enable-VmSwitchExtension -VMSwitchName $VirtualSwitchName -Name "Microsoft Azure VFP Switch Extension" | out-null - - return (get-vmswitch -Name $VirtualSwitchName).Id - } -ArgumentList $VirtualSwitchName - - write-sdnexpresslog "Configure and start SLB Host Agent." - - invoke-command -computername $ComputerNAme { - param( - [String] $SLBMVip, - [String] $RestName - ) - $NodeFQDN = (Get-WmiObject win32_computersystem).DNSHostName+"."+(Get-WmiObject win32_computersystem).Domain - - $slbhpconfigtemplate = @" -<?xml version=`"1.0`" encoding=`"utf-8`"?> -<SlbHostPluginConfiguration xmlns:xsd=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`"> - <SlbManager> - <HomeSlbmVipEndpoints> - <HomeSlbmVipEndpoint>$($SLBMVIP):8570</HomeSlbmVipEndpoint> - </HomeSlbmVipEndpoints> - <SlbmVipEndpoints> - <SlbmVipEndpoint>$($SLBMVIP):8570</SlbmVipEndpoint> - </SlbmVipEndpoints> - <SlbManagerCertSubjectName>$RESTName</SlbManagerCertSubjectName> - </SlbManager> - <SlbHostPlugin> - <SlbHostPluginCertSubjectName>$NodeFQDN</SlbHostPluginCertSubjectName> - </SlbHostPlugin> - <NetworkConfig> - <MtuSize>0</MtuSize> - <JumboFrameSize>4088</JumboFrameSize> - <VfpFlowStatesLimit>500000</VfpFlowStatesLimit> - </NetworkConfig> -</SlbHostPluginConfiguration> -"@ - - set-content -value $slbhpconfigtemplate -path 'c:\windows\system32\slbhpconfig.xml' -encoding UTF8 - - Stop-Service -Name SLBHostAgent -Force - Set-Service -Name SLBHostAgent -StartupType Automatic - Start-Service -Name SLBHostAgent - } -ArgumentList $SLBMVIP, $RESTName - - $nchostcertObject = get-networkcontrollerCredential -Connectionuri $URI -ResourceId "NCHostCert" -credential $Credential - - $PALogicalNetwork = get-networkcontrollerLogicalNetwork -Connectionuri $URI -ResourceId "HNVPA" -credential $Credential - $PALogicalSubnet = $PALogicalNetwork.Properties.Subnets | where {$_.properties.AddressPrefix -eq $HostPASubnetPrefix} - - $ServerProperties = new-object Microsoft.Windows.NetworkController.ServerProperties - - $ServerProperties.Connections = @() - $ServerProperties.Connections += new-object Microsoft.Windows.NetworkController.Connection - $ServerProperties.Connections[0].Credential = $nchostcertObject - $ServerProperties.Connections[0].CredentialType = $nchostcertObject.properties.Type - $ServerProperties.Connections[0].ManagementAddresses = @($NodeFQDN) - - $ServerProperties.NetworkInterfaces = @() - $serverProperties.NetworkInterfaces += new-object Microsoft.Windows.NetworkController.NwInterface - $serverProperties.NetworkInterfaces[0].ResourceId = $VirtualSwitchName - $serverProperties.NetworkInterfaces[0].Properties = new-object Microsoft.Windows.NetworkController.NwInterfaceProperties - $ServerProperties.NetworkInterfaces[0].Properties.LogicalSubnets = @($PALogicalSubnet) - - $ServerProperties.Certificate = [System.Convert]::ToBase64String($CertData) - - $Server = New-NetworkControllerServer -ConnectionURI $uri -ResourceId $VirtualSwitchId -Properties $ServerProperties -Credential $Credential -Force - - invoke-command -computername $ComputerName { - param( - [String] $InstanceId - ) - new-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Services\NcHostAgent\Parameters" -Name "HostId" -Value $InstanceId -PropertyType "String" -Force | out-null - - $dnsproxy = get-service DNSProxy -ErrorAction Ignore - if ($dnsproxy -ne $null) { - $dnsproxy | Stop-Service -Force - } - - Stop-Service SlbHostAgent -Force - Stop-Service NcHostAgent -Force - - Start-Service NcHostAgent - Start-Service SlbHostAgent - - if ($dnsproxy -ne $null) { - Set-Service -Name "DnsProxy" -StartupType Automatic - $dnsproxy | Start-Service - } - - } -ArgumentList $Server.InstanceId - - write-sdnexpresslog "New-SDNExpressHost Exit" -} - - - - -<# -ooooo ooo . o8o oooo o8o . -`888' `8' .o8 `"' `888 `"' .o8 - 888 8 .o888oo oooo 888 oooo .o888oo oooo ooo - 888 8 888 `888 888 `888 888 `88. .8' - 888 8 888 888 888 888 888 `88..8' - `88. .8' 888 . 888 888 888 888 . `888' - `YbodP' "888" o888o o888o o888o "888" .8' - .o..P' - `Y8P' -#> -function Write-SDNExpressLog -{ - Param([String] $Message) - - $FormattedDate = date -Format "yyyyMMdd-HH:mm:ss" - $FormattedMessage = "[$FormattedDate] $Message" - write-verbose $FormattedMessage - - $formattedMessage | out-file ".\SDNExpressLog.txt" -Append -} -function Get-IPv4AddressInSubnet -{ - param([string] $subnet, [int] $offset) - - $prefix = ($subnet.split("/"))[0] - $bits = ($subnet.split("/"))[1] - - $sp = $prefix.Split(".", 4) - $val = [System.Convert]::ToInt64($sp[0]) - $val = $val -shl 8 - $val += [System.Convert]::ToInt64($sp[1]) - $val = $val -shl 8 - $val += [System.Convert]::ToInt64($sp[2]) - $val = $val -shl 8 - $val += [System.Convert]::ToInt64($sp[3]) - - $val = $val -shr (32 - $bits) - $val = $val -shl (32 - $bits) - $val += $offset - - "{0}.{1}.{2}.{3}" -f (($val -shr 24) -band 0xff), (($val -shr 16) -band 0xff), (($val -shr 8) -band 0xff), ($val -band 0xff ) -} -function Get-IPv4LastAddressInSubnet -{ - param([string] $subnet, [Int32]$offset = 0) - - $bits = ($subnet.split("/"))[1] - $Count = [math]::pow(2, 32-$bits) - return get-ipv4addressinsubnet $subnet (($count-1)+$offset) -} - - -function WaitForComputerToBeReady -{ - param( - [string[]] $ComputerName, - [Switch]$CheckPendingReboot - ) - - - foreach ($computer in $computername) { - write-sdnexpresslog "Waiting for $Computer to become active." - Start-Sleep -Seconds 120 - - $continue = $true - while ($continue) { - try { - $ps = $null - $result = "" - - klist purge | out-null #clear kerberos ticket cache - Clear-DnsClientCache #clear DNS cache in case IP address is stale - - write-sdnexpresslog "Attempting to contact $Computer." - $ps = new-pssession -computername $Computer -erroraction ignore - if ($ps -ne $null) { - if ($CheckPendingReboot) { - $result = Invoke-Command -Session $ps -ScriptBlock { - if (Test-Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") { - "Reboot pending" - } - else { - hostname - } - } - } - else { - try { - $result = Invoke-Command -Session $ps -ScriptBlock { hostname } - } catch { } - } - remove-pssession $ps - } - if ($result -eq $Computer) { - $continue = $false - break - } - if ($result -eq "Reboot pending") { - write-sdnexpresslog "Reboot pending on $Computer. Waiting for restart." - } - } - catch - { - } - write-sdnexpresslog "$Computer is not active, sleeping for 10 seconds." - sleep 10 - } - write-sdnexpresslog "$Computer IS ACTIVE. Continuing with deployment." - } -} - - - - -<# - -ooo ooooo -`88. .888' - 888b d'888 oooo oooo oooo ooo - 8 Y88. .P 888 `888 `888 `88b..8P' - 8 `888' 888 888 888 Y888' - 8 Y 888 888 888 .o8"'88b -o8o o888o `V88V"V8P' o88' 888o - - - -#> -Function Add-SDNExpressMux { - param( - [String] $RestName, - [string] $ComputerName, - [Object] $NCHostCert, - [String] $PAMacAddress, - [String] $LocalPeerIP, - [String] $MuxASN, - [Object] $Routers, - [PSCredential] $Credential = $null - ) - - write-sdnexpresslog "New-SDNExpressMux" - write-sdnexpresslog " -RestName: $RestName" - write-sdnexpresslog " -ComputerName: $ComputerName" - write-sdnexpresslog " -NCHostCert: $($NCHostCert.Thumbprint)" - write-sdnexpresslog " -PAMacAddress: $PAMacAddress" - write-sdnexpresslog " -LocalPeerIP: $LocalPeerIP" - write-sdnexpresslog " -MuxASN: $MuxASN" - write-sdnexpresslog " -Routers: $Routers" - write-sdnexpresslog " -Credential: $($Credential.UserName)" - - $uri = "https://$RestName" - - #TODO: Add PA Routes - - invoke-command -computername $ComputerName { - param( - [String] $PAMacAddress - ) - reg add hklm\system\currentcontrolset\services\tcpip6\parameters /v DisabledComponents /t REG_DWORD /d 255 /f | out-null - - $PAMacAddress = [regex]::matches($PAMacAddress.ToUpper().Replace(":", "").Replace("-", ""), '..').groups.value -join "-" - $nic = Get-NetAdapter -ErrorAction Ignore | where {$_.MacAddress -eq $PAMacAddress} - - if ($nic -eq $null) - { - throw "No adapter with the HNVPA MAC $PAMacAddress was found" - } - - $nicProperty = Get-NetAdapterAdvancedProperty -Name $nic.Name -AllProperties -RegistryKeyword *EncapOverhead -ErrorAction Ignore - if($nicProperty -eq $null) - { - New-NetAdapterAdvancedProperty -Name $nic.Name -RegistryKeyword *EncapOverhead -RegistryValue 160 | out-null - } - else - { - Set-NetAdapterAdvancedProperty -Name $nic.Name -AllProperties -RegistryKeyword *EncapOverhead -RegistryValue 160 - } - - add-windowsfeature SoftwareLoadBalancer -Restart | out-null - } -argumentlist $PAMacAddress - - WaitforComputerToBeReady $ComputerName $true - - $MuxFQDN = invoke-command -computername $ComputerName { - Return (Get-WmiObject win32_computersystem).DNSHostName+"."+(Get-WmiObject win32_computersystem).Domain - } - - #wait for comptuer to restart. - - $CertData = invoke-command -computername $ComputerName { - write-verbose "Creating self signed certificate..."; - - $NodeFQDN = (Get-WmiObject win32_computersystem).DNSHostName+"."+(Get-WmiObject win32_computersystem).Domain - - $cert = get-childitem "cert:\localmachine\my" | where {$_.Subject.ToUpper() -eq "CN=$NodeFQDN".ToUpper()} - if ($cert -eq $null) { - $cert = New-SelfSignedCertificate -Type Custom -KeySpec KeyExchange -Subject "CN=$NodeFQDN" -KeyExportPolicy Exportable -HashAlgorithm sha256 -KeyLength 2048 -CertStoreLocation "Cert:\LocalMachine\My" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.2") - } - - $targetCertPrivKey = $Cert.PrivateKey - $privKeyCertFile = Get-Item -path "$ENV:ProgramData\Microsoft\Crypto\RSA\MachineKeys\*" | where {$_.Name -eq $targetCertPrivKey.CspKeyContainerInfo.UniqueKeyContainerName} - $privKeyAcl = Get-Acl $privKeyCertFile - $permission = "NT AUTHORITY\NETWORK SERVICE","Read","Allow" - $accessRule = new-object System.Security.AccessControl.FileSystemAccessRule $permission - $privKeyAcl.AddAccessRule($accessRule) - Set-Acl $privKeyCertFile.FullName $privKeyAcl - - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force | out-null - Export-Certificate -Type CERT -FilePath $TempFile.FullName -cert $cert | out-null - - $CertData = Get-Content $TempFile.FullName -Encoding Byte - Remove-Item $TempFile.FullName -Force | out-null - - return $CertData - } - - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force | out-null - Export-Certificate -Type CERT -FilePath $TempFile.FullName -cert $NCHostCert | out-null - $NCHostCertData = Get-Content $TempFile.FullName -Encoding Byte - Remove-Item $TempFile.FullName -Force | out-null - - invoke-command -ComputerName $ComputerName { - param( - [byte[]] $CertData - ) - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force - - $CertData | set-content $TempFile.FullName -Encoding Byte - import-certificate -filepath $TempFile.FullName -certstorelocation "cert:\localmachine\root" | out-null - Remove-Item $TempFile.FullName -Force - } -ArgumentList (,$NCHostCertData) - - - $vmguid = invoke-command -computername $ComputerName { - param( - [String] $RestName - ) - - $NodeFQDN = (Get-WmiObject win32_computersystem).DNSHostName+"."+(Get-WmiObject win32_computersystem).Domain - $cert = get-childitem "cert:\localmachine\my" | where {$_.Subject.ToUpper() -eq "CN=$NodeFQDN".ToUpper()} - - New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\SlbMux" -Force -Name SlbmThumb -PropertyType String -Value $RestName | out-null - New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\SlbMux" -Force -Name MuxCert -PropertyType String -Value $NodeFQDN | out-null - - Get-ChildItem -Path WSMan:\localhost\Listener | Where {$_.Keys.Contains("Transport=HTTPS") } | Remove-Item -Recurse -Force | out-null - New-Item -Path WSMan:\localhost\Listener -Address * -HostName $NodeFQDN -Transport HTTPS -CertificateThumbPrint $cert.Thumbprint -Force | out-null - - Get-Netfirewallrule -Group "@%SystemRoot%\system32\firewallapi.dll,-36902" | Enable-NetFirewallRule - - start-service slbmux - - return (get-childitem -Path "HKLM:\software\microsoft\virtual machine\guest" | get-itemproperty).virtualmachineid - } -ArgumentList $RestName - - write-sdnexpresslog "Add VirtualServerToNC"; - $nchostcertObject = get-networkcontrollerCredential -Connectionuri $URI -ResourceId "NCHostCert" -credential $Credential - - $VirtualServerProperties = new-object Microsoft.Windows.NetworkController.VirtualServerProperties - $VirtualServerProperties.Connections = @() - $VirtualServerProperties.Connections += new-object Microsoft.Windows.NetworkController.Connection - $VirtualServerProperties.Connections[0].Credential = $nchostcertObject - $VirtualServerProperties.Connections[0].CredentialType = $nchostcertObject.properties.Type - $VirtualServerProperties.Connections[0].ManagementAddresses = @($MuxFQDN) - $VirtualServerProperties.Certificate = [System.Convert]::ToBase64String($CertData) - $VirtualServerProperties.vmguid = $vmGuid - - $VirtualServer = new-networkcontrollervirtualserver -connectionuri $uri -credential $Credential -MarkServerReadOnly $false -ResourceId $MuxFQDN -Properties $VirtualServerProperties -force - - $MuxProperties = new-object Microsoft.Windows.NetworkController.LoadBalancerMuxProperties - $muxProperties.RouterConfiguration = new-object Microsoft.Windows.NetworkController.RouterConfiguration - $muxProperties.RouterConfiguration.LocalASN = $MuxASN - $muxProperties.RouterConfiguration.PeerRouterConfigurations = @() - foreach ($router in $routers) { - $peerRouter = new-object Microsoft.Windows.NetworkController.PeerRouterConfiguration - $peerRouter.LocalIPAddress = $LocalPeerIP - $peerRouter.PeerASN = $Router.RouterASN - $peerRouter.RouterIPAddress = $Router.RouterIPAddress - $peerRouter.RouterName = $Router.RouterIPAddress.Replace(".", "_") - $muxProperties.RouterConfiguration.PeerRouterConfigurations += $PeerRouter - } - $muxProperties.VirtualServer = $VirtualServer - - $Mux = new-networkcontrollerloadbalancermux -connectionuri $uri -credential $Credential -ResourceId $MuxFQDN -Properties $MuxProperties -force - write-sdnexpresslog "New-SDNExpressMux Exit" -} - -<# - - .oooooo. . - d8P' `Y8b .o8 -888 .oooo. .o888oo .ooooo. oooo oooo ooo .oooo. oooo ooo .oooo.o -888 `P )88b 888 d88' `88b `88. `88. .8' `P )88b `88. .8' d88( "8 -888 ooooo .oP"888 888 888ooo888 `88..]88..8' .oP"888 `88..8' `"Y88b. -`88. .88' d8( 888 888 . 888 .o `888'`888' d8( 888 `888' o. )88b - `Y8bood8P' `Y888""8o "888" `Y8bod8P' `8' `8' `Y888""8o .8' 8""888P' - .o..P' - `Y8P' - -#> -function New-SDNExpressGatewayPool -{ - param( - [String] $RestName, - [PSCredential] $Credential, - [String] $PoolName, - [Parameter(Mandatory=$true,ParameterSetName="TypeAll")] - [Switch] $IsTypeAll, - [Parameter(Mandatory=$true,ParameterSetName="TypeIPSec")] - [Switch] $IsTypeIPSec, - [Parameter(Mandatory=$true,ParameterSetName="TypeGre")] - [Switch] $IsTypeGre, - [Parameter(Mandatory=$true,ParameterSetName="TypeForwarding")] - [Switch] $IsTypeForwarding, - [Parameter(Mandatory=$false,ParameterSetName="TypeAll")] - [Parameter(Mandatory=$false,ParameterSetName="TypeGre")] - [String] $PublicIPAddress, - [Parameter(Mandatory=$false,ParameterSetName="TypeAll")] - [Parameter(Mandatory=$true,ParameterSetName="TypeGre")] - [String] $GreSubnetAddressPrefix, - [Parameter(Mandatory=$false,ParameterSetName="TypeGre")] - [String] $GrePoolStart = (Get-IPv4AddressInSubnet -subnet $GreSubnetAddressPrefix -offset 1), - [Parameter(Mandatory=$false,ParameterSetName="TypeGre")] - [String] $GrePoolEnd = (Get-IPv4LastAddressInSubnet -subnet $GreSubnetAddressPrefix), - [String] $Capacity, - [String] $RedundantCount=1 - ) - - write-sdnexpresslog "New-SDNExpressGatewayPool" - write-sdnexpresslog " -RestName: $RestName" - write-sdnexpresslog " -Credential: $($Credential.UserName)" - write-sdnexpresslog " -PoolName: $PoolName" - write-sdnexpresslog " -IsTypeAll: $IsTypeAll" - write-sdnexpresslog " -IsTypeIPSec: $IsTypeIPSec" - write-sdnexpresslog " -IsTypeGre: $IsTypeGre" - write-sdnexpresslog " -IsTypeForwarding: $IsTypeForwarding" - write-sdnexpresslog " -PublicIPAddress: $PublicIPAddress" - write-sdnexpresslog " -GRESubnetAddressPrefix: $GRESubnetAddressPrefix" - write-sdnexpresslog " -GrePoolStart: $GrePoolStart" - write-sdnexpresslog " -GrePoolEnd: $GrePoolEnd" - write-sdnexpresslog " -Capacity: $Capacity" - write-sdnexpresslog " -RedundantCount: $RedundantCount" - - $uri = "https://$RestName" - - $gresubnet = $null - - if ($IsTypeAll -or $IsTypeIPSec) { - $PublicIPProperties = new-object Microsoft.Windows.NetworkController.PublicIPAddressProperties - $publicIPProperties.IdleTimeoutInMinutes = 4 - - if ([String]::IsNullOrEmpty($PublicIPAddress)) { - $PublicIPProperties.PublicIPAllocationMethod = "Dynamic" - } else { - $PublicIPProperties.PublicIPAllocationMethod = "Static" - $PublicIPProperites.IPAddress = $PublicIPAddress - } - $PublicIPAddressObject = New-NetworkControllerPublicIPAddress -connectionURI $uri -ResourceId $PoolName -Properties $PublicIPProperties -Force -Credential $Credential - } - - if ($IsTypeGre -or $IsTypeAll) { - $logicalNetwork = try { get-networkcontrollerlogicalnetwork -ResourceId "GreVIP" -connectionuri $uri -credential $Credential } catch {} - - if ($logicalNetwork -eq $null) { - $LogicalNetworkProperties = new-object Microsoft.Windows.NetworkController.LogicalNetworkProperties - $LogicalNetworkProperties.NetworkVirtualizationEnabled = $false - $LogicalNetwork = New-NetworkControllerLogicalNetwork -ConnectionURI $uri -ResourceID "GreVIP" -properties $LogicalNetworkProperties -Credential $Credential -Force - } - - foreach ($subnet in $logicalnetwork.properties.subnets) { - if ($Subnet.properties.AddressPrefix -eq $GreSubnetAddressPrefix) { - $GreSubnet = $subnet - } - } - - if ($GreSubnet -eq $Null) { - $LogicalSubnetProperties = new-object Microsoft.Windows.NetworkController.LogicalSubnetProperties - $LogicalSubnetProperties.AddressPrefix = $GreSubnetAddressPrefix - $logicalSubnetProperties.DefaultGateways = @(Get-IPv4AddressInSubnet -subnet $GreSubnetAddressPrefix) - - $greSubnet = New-NetworkControllerLogicalSubnet -ConnectionURI $uri -LogicalNetworkId "GreVIP" -ResourceId $GreSubnetAddressPrefix.Replace("/", "_") -properties $LogicalSubnetProperties -Credential $Credential -Force - - $IPpoolProperties = new-object Microsoft.Windows.NetworkController.IPPoolproperties - $ippoolproperties.startipaddress = $GrePoolStart - $ippoolproperties.endipaddress = $GrePoolEnd - - $IPPoolObject = New-networkcontrollerIPPool -ConnectionURI $uri -NetworkId "GreVIP" -SubnetId $GreSubnetAddressPrefix.Replace("/", "_") -ResourceID $GreSubnetAddressPrefix.Replace("/", "_") -Properties $IPPoolProperties -Credential $Credential -force - } - } - - $GatewayPoolProperties = new-object Microsoft.Windows.NetworkController.GatewayPoolProperties - $GatewayPoolProperties.RedundantGatewayCount = $RedundantCount - $GatewayPoolProperties.GatewayCapacityKiloBitsPerSecond = $Capacity - - if ($IsTypeAll) { - $GatewayPoolProperties.Type = "All" - - $GatewayPoolProperties.IPConfiguration = new-object Microsoft.Windows.NetworkController.IPConfig - $GatewayPoolProperties.IPConfiguration.PublicIPAddresses = @() - $GatewayPoolProperties.IPConfiguration.PublicIPAddresses += $PublicIPAddressObject - - $GatewayPoolProperties.IpConfiguration.GreVipSubnets = @() - $GatewayPoolProperties.IPConfiguration.GreVipSubnets += $GreSubnet - } elseif ($IsTypeIPSec) { - $GatewayPoolProperties.Type = "S2sIpSec" - - $GatewayPoolProperties.IPConfiguration = new-object Microsoft.Windows.NetworkController.IPConfig - $GatewayPoolProperties.IPConfiguration.PublicIPAddresses = @() - $GatewayPoolProperties.IPConfiguration.PublicIPAddresses += $PublicIPAddressObject - } elseif ($IsTypeGre) { - $GatewayPoolProperties.Type = "S2sGre" - - $GatewayPoolProperties.IPConfiguration = new-object Microsoft.Windows.NetworkController.IPConfig - $GatewayPoolProperties.IpConfiguration.GreVipSubnets = @() - $GatewayPoolProperties.IPConfiguration.GreVipSubnets += $GreSubnet - } elseif ($IsForwarding) { - $GatewayPoolProperties.Type = "Forwarding" - } - - $GWPoolObject = new-networkcontrollergatewaypool -connectionURI $URI -ResourceId $PoolName -Properties $GatewayPoolProperties -Force -Credential $Credential - write-sdnexpresslog "New-SDNExpressGatewayPool Exit" -} - - - - - -Function New-SDNExpressGateway { - param( - [String] $RestName, - [string] $ComputerName, - [String] $HostName, - [Object] $NCHostCert, - [String] $PoolName, - [String] $FrontEndLogicalNetworkName, - [String] $FrontEndAddressPrefix, - [String] $FrontEndIp, - [String] $FrontEndMac, - [String] $BackEndMac, - [String] $RouterASN = $null, - [String] $RouterIP = $null, - [String] $LocalASN = $null, - [PSCredential] $Credential = $null - ) - - write-sdnexpresslog "New-SDNExpressGateway" - write-sdnexpresslog " -RestName: $RestName" - write-sdnexpresslog " -ComputerName: $ComputerName" - write-sdnexpresslog " -HostName: $HostName" - write-sdnexpresslog " -NCHostCert: $($NCHostCert.thumbprint)" - write-sdnexpresslog " -PoolName: $PoolName" - write-sdnexpresslog " -FrontEndLogicalNetworkName: $FrontEndLogicalNetworkName" - write-sdnexpresslog " -FrontEndAddressPrefix: $FrontEndAddressPrefix" - write-sdnexpresslog " -FrontEndIp: $FrontEndIp" - write-sdnexpresslog " -FrontEndMac: $FrontEndMac" - write-sdnexpresslog " -BackEndMac: $BackEndMac" - write-sdnexpresslog " -RouterASN: $RouterASN" - write-sdnexpresslog " -RouterIP: $RouterIP" - write-sdnexpresslog " -LocalASN: $LocalASN" - write-sdnexpresslog " -Credential: $($Credential.UserName)" - - $uri = "https://$RestName" - - - invoke-command -computername $ComputerName { - param( - [String] $FrontEndMac, - [String] $BackEndMac - ) - - # Get-NetAdapter returns MacAddresses with hyphens '-' - $FrontEndMac = [regex]::matches($FrontEndMac.ToUpper().Replace(":", "").Replace("-", ""), '..').groups.value -join "-" - $BackEndMac = [regex]::matches($BackEndMac.ToUpper().Replace(":", "").Replace("-", ""), '..').groups.value -join "-" - - Set-Item WSMan:\localhost\MaxEnvelopeSizekb -Value 7000 - - $adapters = Get-NetAdapter - - $adapter = $adapters | where {$_.MacAddress -eq $BackEndMac} - $adapter | Rename-NetAdapter -NewName "Internal" -Confirm:$false -ErrorAction Ignore - - $adapter = $adapters | where {$_.MacAddress -eq $FrontEndMac} - $adapter | Rename-NetAdapter -NewName "External" -Confirm:$false -ErrorAction Ignore - - Add-WindowsFeature -Name RemoteAccess -IncludeAllSubFeature -IncludeManagementTools | out-null - - $RemoteAccess = get-RemoteAccess - if ($RemoteAccess -eq $null -or $RemoteAccess.VpnMultiTenancyStatus -ne "Installed") - { - Install-RemoteAccess -MultiTenancy | out-null - } - - Get-Netfirewallrule -Group "@%SystemRoot%\system32\firewallapi.dll,-36902" | Enable-NetFirewallRule - - $GatewayService = get-service GatewayService -erroraction Ignore - if ($gatewayservice -ne $null) { - Set-Service -Name GatewayService -StartupType Automatic | out-null - Start-Service -Name GatewayService | out-null - } - - } -ArgumentList $FrontEndMac, $BackEndMac - - $GatewayFQDN = invoke-command -computername $ComputerName { - Return (Get-WmiObject win32_computersystem).DNSHostName+"."+(Get-WmiObject win32_computersystem).Domain - } - - $vmGuid = invoke-command -computername $ComputerName { - return (get-childitem -Path "HKLM:\software\microsoft\virtual machine\guest" | get-itemproperty).virtualmachineid - } - - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force | out-null - Export-Certificate -Type CERT -FilePath $TempFile.FullName -cert $NCHostCert | out-null - $NCHostCertData = Get-Content $TempFile.FullName -Encoding Byte - Remove-Item $TempFile.FullName -Force | out-null - - invoke-command -ComputerName $ComputerName { - param( - [byte[]] $CertData - ) - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force - - $CertData | set-content $TempFile.FullName -Encoding Byte - import-certificate -filepath $TempFile.FullName -certstorelocation "cert:\localmachine\root" | out-null - Remove-Item $TempFile.FullName -Force - } -ArgumentList (,$NCHostCertData) - - # Get-VMNetworkAdapter returns MacAddresses without hyphens '-'. NetworkInterface prefers without hyphens also. - - $FrontEndMac = [regex]::matches($FrontEndMac.ToUpper().Replace(":", "").Replace("-", ""), '..').groups.value -join "" - $BackEndMac = [regex]::matches($BackEndMac.ToUpper().Replace(":", "").Replace("-", ""), '..').groups.value -join "" - - $LogicalSubnet = get-networkcontrollerlogicalSubnet -LogicalNetworkId $FrontEndLogicalNetworkName -ConnectionURI $uri -Credential $Credential - $LogicalSubnet = $LogicalSubnet | where {$_.properties.AddressPrefix -eq $FrontEndAddressPrefix } - - $NicProperties = new-object Microsoft.Windows.NetworkController.NetworkInterfaceProperties - $nicproperties.PrivateMacAddress = $BackEndMac - $NicProperties.privateMacAllocationMethod = "Static" - $BackEndNic = new-networkcontrollernetworkinterface -connectionuri $uri -credential $Credential -ResourceId "$($GatewayFQDN)_BackEnd" -Properties $NicProperties -force - - $NicProperties = new-object Microsoft.Windows.NetworkController.NetworkInterfaceProperties - $nicproperties.PrivateMacAddress = $FrontEndMac - $NicProperties.privateMacAllocationMethod = "Static" - $NicProperties.IPConfigurations = @() - $NicProperties.IPConfigurations += new-object Microsoft.Windows.NetworkController.NetworkInterfaceIpConfiguration - $NicProperties.IPConfigurations[0].ResourceId = "FrontEnd" - $NicProperties.IPConfigurations[0].Properties = new-object Microsoft.Windows.NetworkController.NetworkInterfaceIpConfigurationProperties - $NicProperties.IPConfigurations[0].Properties.Subnet = new-object Microsoft.Windows.NetworkController.Subnet - $nicProperties.IpConfigurations[0].Properties.Subnet.ResourceRef = $LogicalSubnet.ResourceRef - $NicProperties.IPConfigurations[0].Properties.PrivateIPAddress = $FrontEndIp - $NicProperties.IPConfigurations[0].Properties.PrivateIPAllocationMethod = "Static" - $FrontEndNic = new-networkcontrollernetworkinterface -connectionuri $uri -credential $Credential -ResourceId "$($GatewayFQDN)_FrontEnd" -Properties $NicProperties -force - - $SetPortProfileBlock = { - param( - [String] $VMName, - [String] $MacAddress, - [String] $InstanceId - ) - $PortProfileFeatureId = "9940cd46-8b06-43bb-b9d5-93d50381fd56" - $NcVendorId = "{1FA41B39-B444-4E43-B35A-E1F7985FD548}" - - $vnic = Get-VMNetworkAdapter -VMName $VMName | where {$_.MacAddress -eq $MacAddress} - - $currentProfile = Get-VMSwitchExtensionPortFeature -FeatureId $PortProfileFeatureId -VMNetworkAdapter $vNic - - if ( $currentProfile -eq $null) - { - $portProfileDefaultSetting = Get-VMSystemSwitchExtensionPortFeature -FeatureId $PortProfileFeatureId - $portProfileDefaultSetting.SettingData.NetCfgInstanceId = "{56785678-a0e5-4a26-bc9b-c0cba27311a3}" - $portProfileDefaultSetting.SettingData.CdnLabelString = "TestCdn" - $portProfileDefaultSetting.SettingData.CdnLabelId = 1111 - $portProfileDefaultSetting.SettingData.ProfileName = "Testprofile" - $portProfileDefaultSetting.SettingData.VendorId = $NcVendorId - $portProfileDefaultSetting.SettingData.VendorName = "NetworkController" - - $portProfileDefaultSetting.SettingData.ProfileId = "{$InstanceId}" - $portProfileDefaultSetting.SettingData.ProfileData = 1 - - Add-VMSwitchExtensionPortFeature -VMSwitchExtensionFeature $portProfileDefaultSetting -VMNetworkAdapter $vNic | out-null - } - else - { - $currentProfile.SettingData.ProfileId = "{$InstanceId}" - $currentProfile.SettingData.ProfileData = 1 - Set-VMSwitchExtensionPortFeature -VMSwitchExtensionFeature $currentProfile -VMNetworkAdapter $vNic | out-null - } - } - - invoke-command -ComputerName $HostName -ScriptBlock $SetPortProfileBlock -ArgumentList $ComputerName, $BackEndMac, $BackEndNic.InstanceId - invoke-command -ComputerName $HostName -ScriptBlock $SetPortProfileBlock -ArgumentList $ComputerName, $FrontEndMac, $FrontEndNic.InstanceId - - $nchostUserObject = get-networkcontrollerCredential -Connectionuri $URI -ResourceId "NCHostUser" -credential $Credential - $GatewayPoolObject = get-networkcontrollerGatewayPool -Connectionuri $URI -ResourceId $PoolName -credential $Credential - - $VirtualServerProperties = new-object Microsoft.Windows.NetworkController.VirtualServerProperties - $VirtualServerProperties.Connections = @() - $VirtualServerProperties.Connections += new-object Microsoft.Windows.NetworkController.Connection - $VirtualServerProperties.Connections[0].Credential = $nchostUserObject - $VirtualServerProperties.Connections[0].CredentialType = $nchostUserObject.properties.Type - $VirtualServerProperties.Connections[0].ManagementAddresses = @($GatewayFQDN) - $VirtualServerProperties.vmguid = $vmGuid - - $VirtualServerObject = new-networkcontrollervirtualserver -connectionuri $uri -credential $Credential -MarkServerReadOnly $false -ResourceId $GatewayFQDN -Properties $VirtualServerProperties -force - - $GatewayProperties = new-object Microsoft.Windows.NetworkController.GatewayProperties - $GatewayProperties.NetworkInterfaces = new-object Microsoft.Windows.NetworkController.NetworkInterfaces - $GatewayProperties.NetworkInterfaces.InternalNetworkInterface = $BackEndNic - $GatewayProperties.NetworkInterfaces.ExternalNetworkInterface = $FrontEndNic - $GatewayProperties.Pool = $GatewayPoolObject - $GatewayProperties.VirtualServer = $VirtualServerObject - - if (($GatewayPoolObject.Properties.Type -eq "All") -or ($GatewayPoolObject.Properties.Type -eq "S2sIpsec" )) { - $GatewayProperties.BGPConfig = new-object Microsoft.Windows.NetworkController.GatewayBgpConfig - - $GatewayProperties.BGPConfig.BgpPeer = @() - $GatewayProperties.BGPConfig.BgpPeer += new-object Microsoft.Windows.NetworkController.GatewayBgpPeer - $GatewayProperties.BGPConfig.BgpPeer[0].PeerExtAsNumber = "0.$RouterASN" - $GatewayProperties.BGPConfig.BgpPeer[0].PeerIP = $RouterIP - - $GatewayProperties.BgpConfig.ExtASNumber = "0.$LocalASN" - } - - $Gw = new-networkcontrollerGateway -connectionuri $uri -credential $Credential -ResourceId $GatewayFQDN -Properties $GatewayProperties -force - - write-sdnexpresslog "New-SDNExpressGateway Exit" -} - - - - - - - -function New-SDNExpressVM -{ - param( - [String] $ComputerName, - [String] $VMLocation, - [String] $VMName, - [String] $VHDSrcPath, - [String] $VHDName, - [Int64] $VMMemory=3GB, - [String] $SwitchName="", - [Object] $Nics, - [String] $CredentialDomain, - [String] $CredentialUserName, - [String] $CredentialPassword, - [String] $JoinDomain, - [String] $LocalAdminPassword, - [String] $DomainAdminDomain, - [String] $DomainAdminUserName, - [String] $ProductKey="", - [int] $VMProcessorCount = 4, - [String] $Locale = [System.Globalization.CultureInfo]::CurrentCulture.Name, - [String] $TimeZone = [TimeZoneInfo]::Local.Id, - [Bool] $InstallRasRoutingProtocols - ) - - write-sdnexpresslog "New-SDNExpressVM" - write-sdnexpresslog " -ComputerName: $ComputerName" - write-sdnexpresslog " -VMLocation: $VMLocation" - write-sdnexpresslog " -VMName: $VMName" - write-sdnexpresslog " -VHDSrcPath: $VHDSrcPath" - write-sdnexpresslog " -VHDName: $VHDName" - write-sdnexpresslog " -VMMemory: $VMMemory" - write-sdnexpresslog " -SwitchName: $SwitchName" - write-sdnexpresslog " -Nics: $Nics" - write-sdnexpresslog " -CredentialDomain: $CredentialDomain" - write-sdnexpresslog " -CredentialUserName: $CredentialUserName" - write-sdnexpresslog " -CredentialPassword: ********" - write-sdnexpresslog " -JoinDomain: $JoinDomain" - write-sdnexpresslog " -LocalAdminPassword: ********" - write-sdnexpresslog " -DomainAdminDomain: $DomainAdminDomain" - write-sdnexpresslog " -DomainAdminUserName: $DomainAdminUserName" - write-sdnexpresslog " -ProductKey: ********" - write-sdnexpresslog " -VMProcessorCount: $VMProcessorCount" - write-sdnexpresslog " -Locale: $Locale" - write-sdnexpresslog " -TimeZone: $TimeZone" - - $LocalVMPath = "$vmLocation\$VMName" - $LocalVHDPath = "$localVMPath\$VHDName" - $VHDFullPath = "$VHDSrcPath\$VHDName" - - if ($VMLocation.startswith("\\")) { - $VMPath = "$VMLocation\$VMName" - } else { - $VMPath = "\\$ComputerName\VMShare\$VMName" - } - - $VHDVMPath = "$VMPath\$VHDName" - - write-sdnexpresslog "Checking for previously mounted image." - - $mounted = get-WindowsImage -Mounted - foreach ($mount in $mounted) - { - if ($mount.ImagePath -eq $VHDVMPath) { - DisMount-WindowsImage -Discard -path $mount.Path | out-null - } - } - - $vm = $null - try { - $VM = get-vm -computername $ComputerName -Name $VMName -erroraction Ignore - if ($VM -ne $Null) { - write-sdnexpresslog "VM already exists, exiting VM creation." - return - } - } catch - { - #Continue - } - - if ([String]::IsNullOrEmpty($SwitchName)) { - write-sdnexpresslog "Finding virtual switch." - $SwitchName = invoke-command -computername $computername { - $VMSwitches = Get-VMSwitch - if ($VMSwitches -eq $Null) { - throw "No Virtual Switches found on the host. Can't create VM. Please create a virtual switch before continuing." - } - if ($VMSwitches.count -gt 1) { - throw "More than one virtual switch found on host. Please specify virtual switch name using SwitchName parameter." - } - - return $VMSwitches.Name - } - } - write-sdnexpresslog "Will attach VM to virtual switch: $SwitchName" - - write-sdnexpresslog "Creating VM root directory and share on host." - - invoke-command -computername $computername { - param( - [String] $VMLocation, - [String] $UserName - ) - New-Item -ItemType Directory -Force -Path $VMLocation | out-null - if (!$VMLocation.startswith("\\")) { - get-SmbShare -Name VMShare -ErrorAction Ignore | remove-SMBShare -Force - New-SmbShare -Name VMShare -Path $VMLocation -FullAccess $UserName -Temporary | out-null - } - } -ArgumentList $VMLocation, ([System.Security.Principal.WindowsIdentity]::GetCurrent()).Name - - write-sdnexpresslog "Creating VM directory and copying VHD. This may take a few minutes." - - New-Item -ItemType Directory -Force -Path $VMPath | out-null - copy-item -Path $VHDFullPath -Destination $VMPath | out-null - - write-sdnexpresslog "Creating mount directory and mounting VHD." - - $TempFile = New-TemporaryFile - Remove-Item $TempFile.FullName -Force - $MountPath = $TempFile.FullName - - New-Item -ItemType Directory -Force -Path $MountPath | out-null - - Mount-WindowsImage -ImagePath $VHDVMPath -Index 1 -path $MountPath | out-null - - If ($InstallRasRoutingProtocols) { - write-sdnexpresslog "Installing RasRoutingProtocols Offline" - Enable-WindowsOptionalFeature -Path $MountPath -FeatureName RasRoutingProtocols -All -LimitAccess | Out-Null - } - - write-sdnexpresslog "Generating unattend.xml" - - $count = 1 - $TCPIPInterfaces = "" - $dnsinterfaces = "" - - foreach ($nic in $Nics) { - - $MacAddress = [regex]::matches($nic.MacAddress.ToUpper().Replace(":", "").Replace("-", ""), '..').groups.value -join "-" - - - if (![String]::IsNullOrEmpty($Nic.IPAddress)) { - $sp = $NIC.IPAddress.Split("/") - $IPAddress = $sp[0] - $SubnetMask = $sp[1] - - $Gateway = $Nic.Gateway - $gatewaysnippet = "" - - if (![String]::IsNullOrEmpty($gateway)) { - $gatewaysnippet = @" - <routes> - <Route wcm:action="add"> - <Identifier>0</Identifier> - <Prefix>0.0.0.0/0</Prefix> - <Metric>20</Metric> - <NextHopAddress>$Gateway</NextHopAddress> - </Route> - </routes> -"@ - } - - $TCPIPInterfaces += @" - <Interface wcm:action="add"> - <Ipv4Settings> - <DhcpEnabled>false</DhcpEnabled> - </Ipv4Settings> - <Identifier>$MacAddress</Identifier> - <UnicastIpAddresses> - <IpAddress wcm:action="add" wcm:keyValue="1">$IPAddress/$SubnetMask</IpAddress> - </UnicastIpAddresses> - $gatewaysnippet - </Interface> -"@ - } else { - $TCPIPInterfaces += @" - <Interface wcm:action="add"> - <Ipv4Settings> - <DhcpEnabled>true</DhcpEnabled> - </Ipv4Settings> - <Identifier>$MacAddress</Identifier> - </Interface> -"@ - - } - $alldns = "" - foreach ($dns in $Nic.DNS) { - $alldns += '<IpAddress wcm:action="add" wcm:keyValue="{1}">{0}</IpAddress>' -f $dns, $count++ - } - - if ($Nic.DNS -eq $null -or $Nic.DNS.count -eq 0) { - $dnsregistration = "false" - } else { - $dnsregistration = "true" - } - - $dnsinterfaces += @" - <Interface wcm:action="add"> - <DNSServerSearchOrder> - $alldns - </DNSServerSearchOrder> - <Identifier>$MacAddress</Identifier> - <EnableAdapterDomainNameRegistration>$DNSRegistration</EnableAdapterDomainNameRegistration> - </Interface> -"@ - } - - $ProductKeyField = "" - if (![String]::IsNullOrEmpty($ProductKey)) { - $ProductKeyField = "<ProductKey>$ProductKey</ProductKey>" - } - - $unattendfile = @" - <?xml version="1.0" encoding="utf-8"?> - <unattend xmlns="urn:schemas-microsoft-com:unattend"> - <settings pass="specialize"> - <component name="Microsoft-Windows-TCPIP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <Interfaces> - $TCPIPInterfaces - </Interfaces> - </component> - <component name="Microsoft-Windows-DNS-Client" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <Interfaces> - $DNSInterfaces - </Interfaces> - </component> - <component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <Identification> - <Credentials> - <Domain>$CredentialDomain</Domain> - <Password>$CredentialPassword</Password> - <Username>$CredentialUsername</Username> - </Credentials> - <JoinDomain>$JoinDomain</JoinDomain> - </Identification> - </component> - <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <ComputerName>$VMName</ComputerName> - $ProductKeyField - </component> - </settings> - <settings pass="oobeSystem"> - <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <UserAccounts> - <AdministratorPassword> - <Value>$LocalAdminPassword</Value> - <PlainText>true</PlainText> - </AdministratorPassword> - <DomainAccounts> - <DomainAccountList wcm:action="add"> - <DomainAccount wcm:action="add"> - <Name>$DomainAdminUserName</Name> - <Group>Administrators</Group> - </DomainAccount> - <Domain>$DomainAdminDomain</Domain> - </DomainAccountList> - </DomainAccounts> - </UserAccounts> - <TimeZone>$TimeZone</TimeZone> - <OOBE> - <HideEULAPage>true</HideEULAPage> - <SkipUserOOBE>true</SkipUserOOBE> - <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> - <HideOnlineAccountScreens>true</HideOnlineAccountScreens> - <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> - <NetworkLocation>Work</NetworkLocation> - <ProtectYourPC>1</ProtectYourPC> - <HideLocalAccountScreen>true</HideLocalAccountScreen> - </OOBE> - </component> - <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <UserLocale>$Locale</UserLocale> - <SystemLocale>$Locale</SystemLocale> - <InputLocale>$Locale</InputLocale> - <UILanguage>$Locale</UILanguage> - </component> - </settings> - <cpi:offlineImage cpi:source="" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> - </unattend> -"@ - - write-sdnexpresslog "Writing unattend.xml to $MountPath\unattend.xml" - Set-Content -value $UnattendFile -path "$MountPath\unattend.xml" | out-null - - write-sdnexpresslog "Cleaning up" - - DisMount-WindowsImage -Save -path $MountPath | out-null - Remove-Item $MountPath -Force - Invoke-Command -computername $computername { - Get-SmbShare -Name VMShare -ErrorAction Ignore | remove-SMBShare -Force | out-null - } - - write-sdnexpresslog "Creating VM: $computername" - $NewVM = New-VM -ComputerName $computername -Generation 2 -Name $VMName -Path $LocalVMPath -MemoryStartupBytes $VMMemory -VHDPath $LocalVHDPath -SwitchName $SwitchName - $NewVM | Set-VM -processorcount $VMProcessorCount | out-null - - $first = $true - foreach ($nic in $Nics) { - write-sdnexpresslog "Configuring NIC" - $FormattedMac = [regex]::matches($nic.MacAddress.ToUpper().Replace(":", "").Replace("-", ""), '..').groups.value -join "-" - write-sdnexpresslog "Configuring NIC with MAC $FormattedMac" - if ($first) { - $vnic = $NewVM | get-vmnetworkadapter - $vnic | rename-vmnetworkadapter -newname $Nic.Name - $vnic | Set-vmnetworkadapter -StaticMacAddress $FormattedMac - $first = $false - } else { - #Note: add-vmnetworkadapter doesn't actually return the vnic object for some reason which is why this does a get immediately after. - $vnic = $NewVM | Add-VMNetworkAdapter -SwitchName $SwitchName -Name $Nic.Name -StaticMacAddress $FormattedMac - $vnic = $NewVM | get-vmnetworkadapter -Name $Nic.Name - } - - if ($nic.vlanid) { - write-sdnexpresslog "Setting VLANID to $($nic.vlanid)" - $vnic | Set-VMNetworkAdapterIsolation -AllowUntaggedTraffic $true -IsolationMode VLAN -defaultisolationid $nic.vlanid | out-null - } - - if ($nic.IsMuxPA) { - write-sdnexpresslog "This is a mux PA nic, so ProfileData set to 2." - $ProfileData = 2 - } else { - $ProfileData = 1 - } - - write-sdnexpresslog "Applying Null Guid to ensure initial ability to communicate with VFP enabled." - - invoke-command -ComputerName $ComputerName -ScriptBlock { - param( - [String] $VMName, - [String] $VMNetworkAdapterName, - [Int] $ProfileData - ) - $PortProfileFeatureId = "9940cd46-8b06-43bb-b9d5-93d50381fd56" - $NcVendorId = "{1FA41B39-B444-4E43-B35A-E1F7985FD548}" - - $vnic = Get-VMNetworkAdapter -VMName $VMName -Name $VMNetworkAdapterName - - $currentProfile = Get-VMSwitchExtensionPortFeature -FeatureId $PortProfileFeatureId -VMNetworkAdapter $vNic - - if ( $currentProfile -eq $null) - { - $portProfileDefaultSetting = Get-VMSystemSwitchExtensionPortFeature -FeatureId $PortProfileFeatureId - - $portProfileDefaultSetting.SettingData.ProfileId = "{$([Guid]::Empty)}" - $portProfileDefaultSetting.SettingData.NetCfgInstanceId = "{56785678-a0e5-4a26-bc9b-c0cba27311a3}" - $portProfileDefaultSetting.SettingData.CdnLabelString = "TestCdn" - $portProfileDefaultSetting.SettingData.CdnLabelId = 1111 - $portProfileDefaultSetting.SettingData.ProfileName = "Testprofile" - $portProfileDefaultSetting.SettingData.VendorId = $NcVendorId - $portProfileDefaultSetting.SettingData.VendorName = "NetworkController" - $portProfileDefaultSetting.SettingData.ProfileData = $ProfileData - - Add-VMSwitchExtensionPortFeature -VMSwitchExtensionFeature $portProfileDefaultSetting -VMNetworkAdapter $vNic | out-null - } - else - { - $currentProfile.SettingData.ProfileId = "{$([Guid]::Empty)}" - $currentProfile.SettingData.ProfileData = $ProfileData - Set-VMSwitchExtensionPortFeature -VMSwitchExtensionFeature $currentProfile -VMNetworkAdapter $vNic | out-null - } - } -ArgumentList $VMName, $nic.Name, $ProfileData - } - - write-sdnexpresslog "Starting VM." - - $NewVM | Start-VM | out-null - write-sdnexpresslog "New-SDNExpressVM is complete." -} diff --git a/azure_jumpstart_hcibox/artifacts/SDN/SDNExpressUI.psm1 b/azure_jumpstart_hcibox/artifacts/SDN/SDNExpressUI.psm1 deleted file mode 100644 index 849f094176..0000000000 --- a/azure_jumpstart_hcibox/artifacts/SDN/SDNExpressUI.psm1 +++ /dev/null @@ -1,1206 +0,0 @@ - -function SDNExpressUI { - - [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') - [xml]$XAML = @' - <Window - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - Title="SDN Express" ResizeMode="NoResize" Height="600" Width="800" WindowStartupLocation="CenterScreen" > - <Window.Resources> - <ControlTemplate x:Key="ErrorTemplate" TargetType="{x:Type Control}"> - <DockPanel> - <TextBlock Foreground="Red" TextAlignment="Center" Width="16" FontSize="18" DockPanel.Dock="Right">!</TextBlock> - <Border BorderThickness="1" BorderBrush="Red"> - <ScrollViewer x:Name="PART_ContentHost"/> - </Border> - </DockPanel> - </ControlTemplate> - <ControlTemplate x:Key="NormalTemplate" TargetType="{x:Type Control}"> - <DockPanel> - <TextBlock Foreground="Red" TextAlignment="Center" Width="16" FontSize="18" DockPanel.Dock="Right"></TextBlock> - <Border BorderThickness="1" BorderBrush="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}"> - <ScrollViewer x:Name="PART_ContentHost" /> - </Border> - </DockPanel> - </ControlTemplate> - <Style TargetType="{x:Type TextBox}"> - <Setter Property="OverridesDefaultStyle" Value="True" /> - <Setter Property="Template" Value="{DynamicResource ErrorTemplate}" /> - </Style> - <Style TargetType="{x:Type PasswordBox}"> - <Setter Property="OverridesDefaultStyle" Value="True" /> - <Setter Property="Template" Value="{DynamicResource ErrorTemplate}" /> - </Style> - </Window.Resources> - <Grid> - <StackPanel Name="panel0" HorizontalAlignment="Left" Width="169.149" Background="{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}"> - <Rectangle Height="10" Margin="0,0,159,0" /> - <Grid> - <Rectangle Name="mark1" Fill="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" Height="27.976" Margin="0,0,159,0" /> - <Label Content="Introduction" Margin="10,0,0,0"/> - </Grid> - <Grid> - <Rectangle Name="mark2" Fill="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" Height="27.976" Margin="0,0,159,0" Visibility="Hidden"/> - <Label Content="VM Creation" Margin="10,0,0,0"/> - </Grid> - <Grid> - <Rectangle Name="mark3" Fill="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" Height="27.976" Margin="0,0,159,0" Visibility="Hidden"/> - <Label Content="Management Network" Margin="10,0,0,0"/> - </Grid> - <Grid> - <Rectangle Name="mark4" Fill="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" Height="27.976" Margin="0,0,159,0" Visibility="Hidden"/> - <Label Content="Provider Network" Margin="10,0,0,0"/> - </Grid> - <Grid> - <Rectangle Name="mark5" Fill="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" Height="27.976" Margin="0,0,159,0" Visibility="Hidden"/> - <Label Content="Network Controller" Margin="10,0,0,0"/> - </Grid> - <Grid> - <Rectangle Name="mark6" Fill="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" Height="27.976" Margin="0,0,159,0" Visibility="Hidden"/> - <Label Content="Software Load Balancer" Margin="10,0,0,0"/> - </Grid> - <Grid> - <Rectangle Name="mark7" Fill="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" Height="27.976" Margin="0,0,159,0" Visibility="Hidden"/> - <Label Content="Gateways" Margin="10,0,0,0"/> - </Grid> - <Grid> - <Rectangle Name="mark8" Fill="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" Height="27.976" Margin="0,0,159,0" Visibility="Hidden"/> - <Label Content="BGP" Margin="10,0,0,0"/> - </Grid> - <Grid> - <Rectangle Name="mark9" Fill="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" Height="27.976" Margin="0,0,159,0" Visibility="Hidden"/> - <Label Content="Review" Margin="10,0,0,0"/> - </Grid> - </StackPanel> - <StackPanel Name="panel1" HorizontalAlignment="Left" Height="522.101" VerticalAlignment="Top" Margin="169.149,0,0,0" Width="615.137"> - <TextBlock FontSize="20" Margin="10,0,0,0"><Run Text="Welcome to the SDN Express deployment wizard"/></TextBlock> - <TextBlock Margin="10,0,10,0" TextWrapping="WrapWithOverflow"> - <LineBreak/> - <Run Text="For additional information on any of these steps, click on the Docs link below. Before you can complete this wizard you must perform some prerequisite configuration steps in your network:"/><LineBreak/> - </TextBlock> - <BulletDecorator Margin="10,0,0,0"> - <BulletDecorator.Bullet><Ellipse Height="5" Width="5" Fill="Black"/></BulletDecorator.Bullet> - <TextBlock TextWrapping="Wrap" HorizontalAlignment="Left" Margin="19,0,0,0"> - Allocate a block of static IP addresses from your management subnet for each Network Controller, Mux and Gateway VM to be created. - </TextBlock> - </BulletDecorator> - <BulletDecorator Margin="10,0,0,0"> - <BulletDecorator.Bullet><Ellipse Height="5" Width="5" Fill="Black"/></BulletDecorator.Bullet> - <TextBlock TextWrapping="Wrap" HorizontalAlignment="Left" Margin="19,0,0,0"> - Allocate a subnet and vlan for Hyper-V Network Virtualization Provider Addresses (HNV PA). - </TextBlock> - </BulletDecorator> - <BulletDecorator Margin="10,0,0,0"> - <BulletDecorator.Bullet><Ellipse Height="5" Width="5" Fill="Black"/></BulletDecorator.Bullet> - <TextBlock TextWrapping="Wrap" HorizontalAlignment="Left" Margin="19,0,0,0"> - Allocate a set of subnets for Private VIPs, Public VIPs and GRE VIPs. Do not configure these on a VLAN, instead enable them to be advertized by SDN through BGP. - </TextBlock> - </BulletDecorator> - <BulletDecorator Margin="10,0,0,0"> - <BulletDecorator.Bullet><Ellipse Height="5" Width="5" Fill="Black"/></BulletDecorator.Bullet> - <TextBlock TextWrapping="Wrap" HorizontalAlignment="Left" Margin="19,0,0,0"> - Configure HNV PA network's routers for BGP, with a 16-bit ASN for the router and one for SDN. SDN should peer with the loopback address of each router. - </TextBlock> - </BulletDecorator> - <TextBlock Margin="10,0,0,0" TextWrapping="WrapWithOverflow"> - <LineBreak/> - <Run Text="Physical switch configuration examples are "/> - <Hyperlink Name="uri4" NavigateUri="https://github.com/Microsoft/SDN/tree/master/SwitchConfigExamples">available on Github.</Hyperlink><LineBreak/> - <LineBreak/> - <Run Text="In addition you will need to have the following ready:"/><LineBreak/> - </TextBlock> - <BulletDecorator Margin="10,0,0,0"> - <BulletDecorator.Bullet><Ellipse Height="5" Width="5" Fill="Black"/></BulletDecorator.Bullet> - <TextBlock TextWrapping="Wrap" HorizontalAlignment="Left" Margin="19,0,0,0"> - A set of Hyper-V hosts configured with a virtual switch. - </TextBlock> - </BulletDecorator> - <BulletDecorator Margin="10,0,0,0"> - <BulletDecorator.Bullet><Ellipse Height="5" Width="5" Fill="Black"/></BulletDecorator.Bullet> - <TextBlock TextWrapping="Wrap" HorizontalAlignment="Left" Margin="19,0,0,0"> - A virtual hard disk containing Windows Server 2016 or 2019, Datacenter Edition. - </TextBlock> - </BulletDecorator> - <BulletDecorator Margin="10,0,0,0"> - <BulletDecorator.Bullet><Ellipse Height="5" Width="5" Fill="Black"/></BulletDecorator.Bullet> - <TextBlock TextWrapping="Wrap" HorizontalAlignment="Left" Margin="19,0,0,0"> - An Active Directory domain to join and credentials with Domain Join permission. - </TextBlock> - </BulletDecorator> - <BulletDecorator Margin="10,0,0,0"> - <BulletDecorator.Bullet><Ellipse Height="5" Width="5" Fill="Black"/></BulletDecorator.Bullet> - <TextBlock TextWrapping="Wrap" HorizontalAlignment="Left" Margin="19,0,0,0"> - Domain credentials with DNS update and host administrator priviliges. - </TextBlock> - </BulletDecorator> - <TextBlock Margin="10,0,0,0" TextWrapping="WrapWithOverflow"> - <LineBreak/> - <Run Text="When you have completed the above you can proceed by clicking Next."/> - <LineBreak/> - <LineBreak/> - <Run Text="Help make SDN Express better by "/> - <Hyperlink Name="uri2" NavigateUri="mail:sdnfeedback@microsoft.com">providing feedback.</Hyperlink><LineBreak/> - </TextBlock> - </StackPanel> - <StackPanel Name="panel2" HorizontalAlignment="Left" Height="522.101" VerticalAlignment="Top" Margin="169.149,0,0,0" Width="615.137"> - <Label Content="VM Creation" FontSize="18" Margin="10,0"/> - <Label Content="SDN Express is going to create a number of VMs. This information is used for customizing those VMs." Margin="10,0"/> - <Grid Margin="0,10"/> - <Grid Margin="0,2"> - <Label Content="VHD Location" Margin="10,0,0,0" HorizontalAlignment="Left" Width="122.089"/> - <TextBox Name="txtVHDLocation" Text="" Margin="192.739,0,119.71,0" > - - </TextBox> - <Button Name="btnBrowse" Content="Browse..." Margin="0,0,10,0" HorizontalAlignment="Right" Width="104.71" Height="25.426" VerticalAlignment="Bottom"/> - </Grid> - <Grid Margin="0,2"> - <Label Content="VM Path (on host)" Margin="10,0,0,0" HorizontalAlignment="Left" Width="122.089"/> - <TextBox Name="txtVMPath" Text="" Margin="192.739,0,119.71,0"/> - </Grid> - <Grid Margin="0,2"> - <Label Content="VM Name Prefix" Margin="10,0,0,0" HorizontalAlignment="Left" Width="122.089"/> - <TextBox Name="txtVMNamePrefix" Text="" Margin="192.739,0,281.032,0"/> - </Grid> - <Grid Margin="0,10"/> - <Grid Margin="0,2"> - <Label Content="VM Domain" Margin="10,0,0,0" HorizontalAlignment="Left" Width="178.852"/> - <TextBox Name="txtVMDomain" Text="" Margin="192.739,0,119.71,0"/> - </Grid> - <Grid Margin="0,2"> - <Label Content="Domain Join Username" Margin="10,0,0,0" HorizontalAlignment="Left" Width="178.852"/> - <TextBox Name="txtDomainJoinUsername" Text="" Margin="192.739,0,281.032,0"/> - </Grid> - <Grid Margin="0,2"> - <Label Content="Domain Join Password" Margin="10,0,0,0" HorizontalAlignment="Left" Width="178.852"/> - <PasswordBox Name="txtDomainJoinPassword" Margin="192.739,0,281.032,0"/> - </Grid> - <Grid Margin="0,10"/> - <Grid Margin="0,2"> - <Label Content="Local Admin Password" Margin="10,0,0,0" HorizontalAlignment="Left" Width="177.739"/> - <PasswordBox Name="txtLocalAdminPassword" Margin="192.739,0,282.145,0"/> - </Grid> - </StackPanel> - <StackPanel Name="panel3" HorizontalAlignment="Left" Height="522.101" VerticalAlignment="Top" Margin="169.149,0,0,0" Width="615.137"> - <Label Content="Management Network" FontSize="18" Margin="10,0"/> - <TextBlock Margin="14,0" TextWrapping="WrapWithOverflow"> - <Run Text="Provide information about the management network the SDN infrastructure will use to communicate. This information is used to provide each VM with a network adapter configured for this network."/> - </TextBlock> - <StackPanel Margin="0,10"/> - <Label Content="Subnet Information" Margin="10,0"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="VLAN ID" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtManagementVLANID" Text="" Width="75"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2" > - <Label Content="Subnet Prefix" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtManagementSubnetPrefix" Text="" Width="150"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2" > - <Label Content="Gateway" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtManagementGateway" Text="" Width="150"/> - </StackPanel> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="IP Address Pool" Margin="10,0"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="First Address" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtManagementIPPoolStart" Text="" Width="150"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Last Address" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtManagementIPPoolEnd" Text="" Width="150"/> - </StackPanel> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="DNS Servers" Margin="10,0"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2" > - <Label Content="DNS Server 1" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtManagementDNS1" Width="150"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2" > - <Label Content="DNS Server 2" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtManagementDNS2" Width="150" Template="{DynamicResource NormalTemplate}"/> - <Label Content="Optional" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130" FontStyle="Italic"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2" > - <Label Content="DNS Server 3" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtManagementDNS3" Width="150" Template="{DynamicResource NormalTemplate}"/> - <Label Content="Optional" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130" FontStyle="Italic"/> - </StackPanel> - </StackPanel> - <StackPanel Name="panel4" HorizontalAlignment="Left" Height="522.101" VerticalAlignment="Top" Margin="169.149,0,0,0" Width="615.137"> - <Label Content="Provider Network" FontSize="18" Margin="10,0"/> - <TextBlock Margin="14,0" TextWrapping="WrapWithOverflow"><Run Text="Provide information about the provider network which is used for all workload VM communication."/></TextBlock> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Subnet Information" Margin="10,0"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="VLAN ID" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtPAVLANID" Text="" Width="75"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Subnet Prefix" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtPASubnetPrefix" Text="" Width="150"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Default Gateway" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtPAGateway" Text="" Width="150"/> - </StackPanel> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="IP Address Pool" Margin="10,0"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="First IP Address" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtPAIPPoolStart" Text="" Width="150"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Last IP Address" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtPAIPPoolEnd" Text="" Width="150"/> - </StackPanel> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="MAC Address Pool" Margin="10,0"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="First MAC Address" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtMACPoolStart" Text="" Width="150"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Last MAC Address" Margin="10,0,0,0" HorizontalAlignment="Left" Width="130"/> - <TextBox Name="txtMACPoolEnd" Text="" Width="150"/> - </StackPanel> - </StackPanel> - <StackPanel Name="panel5" HorizontalAlignment="Left" Height="522.101" VerticalAlignment="Top" Margin="169.149,0,0,0" Width="615.137"> - <Label Content="Network Controller" FontSize="18" Margin="10,0"/> - <TextBlock Margin="14,0" TextWrapping="WrapWithOverflow"> - <Run Text="Provide information to be used for the creation of the Network Controller and the Hyper-V hosts to be added to the controller."/> - </TextBlock> - <Grid Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Network Controller" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <RadioButton Name="rdoMultiNode" Content="Multi-node" HorizontalAlignment="Left" VerticalAlignment="Center" Width="91.96" IsChecked="True"/> - <RadioButton Name="rdoSingleode" Content="Single-node" HorizontalAlignment="Left" VerticalAlignment="Center" Width="91.96"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="REST Name (FQDN)" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <TextBox Name="txtRESTName" Text="" Width="240"/> - </StackPanel> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Hyper-V Hosts" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <TextBox Name="txtHyperVHosts" Width="300" Height="160" TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Visible" VerticalContentAlignment="Top"/> - </StackPanel> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Host Credentials" Margin="10,0"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Username" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <TextBox Name="txtHostUsername" Text="" Width="150"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Password" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <PasswordBox Name="txtHostPassword" Width="150"/> - </StackPanel> - </StackPanel> - <StackPanel Name="panel6" HorizontalAlignment="Left" Height="522.101" VerticalAlignment="Top" Margin="169.149,0,0,0" Width="615.137"> - <Label Content="Software Load Balancer" FontSize="18" Margin="10,0"/> - <TextBlock Margin="14,0" TextWrapping="WrapWithOverflow"> - <Run Text="The Software Load Balancer is an SDN integrated L3 and L4 load balancer that is also used for network address translation (NAT). Muxes are the routers for the virtual IP (VIP) endpoints. Use this panel to define how many muxes you want to deploy. All Muxes are active and traffic is spread across them automatically."/> - </TextBlock> - <Grid Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Load Balancer Muxes" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <TextBlock Name="txtMuxCount" VerticalAlignment="Center" Margin="10,0" Text="{Binding ElementName=sliMuxCount, Path=Value, UpdateSourceTrigger=PropertyChanged}" /> - <Slider Name="sliMuxCount" Width="280" Minimum="1" Maximum="8" Value="2" TickFrequency="1" VerticalAlignment="Center" TickPlacement="BottomRight" SmallChange="1" IsSnapToTickEnabled="True"/> - </StackPanel> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Private VIP Subnet" FontSize="14" Margin="10,0"/> - </StackPanel> - <TextBlock Margin="14,0" TextWrapping="WrapWithOverflow"> - <Run Text="Private VIPs are used internally by the SDN infrastructure. This subnet must not be configured on a VLAN in the physical switch as it will be advertized by the Muxes through BGP."/> - </TextBlock> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Subnet Prefix" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <TextBox Name="txtPrivateVIPs" Text="" Width="150"/> - </StackPanel> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Public VIP Subnet" Margin="10,0" FontSize="14" /> - </StackPanel> - <TextBlock Margin="14,0" TextWrapping="WrapWithOverflow"> - <Run Text="Public VIPs are used to directly access workloads as load balanced VIPs or for NAT. If these need to be reached directly from the internet, then you must obtain an internet routable subnet from your Internet Service Provider (ISP). This subnet must not be configured on a VLAN in the physical switch as it will be advertized by the Muxes through BGP."/> - </TextBlock> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Subnet Prefix" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <TextBox Name="txtPublicVIPs" Text="" Width="150"/> - </StackPanel> - </StackPanel> - <StackPanel Name="panel7" HorizontalAlignment="Left" Height="522.101" VerticalAlignment="Top" Margin="169.149,0,0,0" Width="615.137"> - <Label Content="Gateways" FontSize="18" Margin="10,0"/> - <TextBlock Margin="14,0" TextWrapping="WrapWithOverflow"><Run Text="Gateways are used for routing between a virtual network and another network (local or remote). SDN Express creates a default gateway pool that supports all connection types. Within this pool you can select how many gateways are reserved on standby in case an active gateway fails."/></TextBlock> - <Grid Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Gateways" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <TextBlock Name="txtGatewayCount" VerticalAlignment="Center" Margin="10,0" Text="{Binding ElementName=sliGatewayCount, Path=Value, UpdateSourceTrigger=PropertyChanged}" /> - <Slider Name="sliGatewayCount" Margin="35,0" Width="240" Minimum="2" Maximum="8" Value="2" TickFrequency="1" VerticalAlignment="Center" TickPlacement="BottomRight" SmallChange="1" IsSnapToTickEnabled="True"/> - </StackPanel> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Gateways on standby" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <TextBlock Name="txtRedundantCount" VerticalAlignment="Center" Margin="10,0" Text="{Binding ElementName=sliRedundantCount, Path=Value, UpdateSourceTrigger=PropertyChanged}" /> - <Slider Name="sliRedundantCount" Width="0" Minimum="1" Maximum="7" Value="1" VerticalAlignment="Center" TickPlacement="BottomRight" SmallChange="1" IsSnapToTickEnabled="True" /> - </StackPanel> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="GRE Endpoints" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - </StackPanel> - <TextBlock Margin="14,0" TextWrapping="WrapWithOverflow"><Run Text="GRE connections require an endpoint IP address that will be allocated from subnet specified below. This subnet must not be configured on a VLAN in the physical switch as the endpoints will be advertised to the physical network through BGP."/></TextBlock> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Subnet Prefix" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <TextBox Name="txtGREVIPs" Text="" Width="150"/> - </StackPanel> - </StackPanel> - <StackPanel Name="panel8" HorizontalAlignment="Left" Height="522.101" VerticalAlignment="Top" Margin="169.149,0,0,0" Width="615.137"> - <Label Content="Border Gateway Protocol (BGP)" FontSize="18" Margin="10,0"/> - <TextBlock Margin="14,0" TextWrapping="WrapWithOverflow"><Run Text="BGP is used by the Software Load Balancer to advertise VIPs to the physical network. It is also used by the gateways for advertising GRE endpoints."/></TextBlock> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="SDN ASN" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <TextBox Name="txtSDNASN" Text="" Width="150"/> - </StackPanel> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Router 1" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Router IP Address" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <TextBox Name="txtRouterIP1" Text="" Width="150"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Router ASN" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <TextBox Name="txtRouterASN1" Text="" Width="150"/> - </StackPanel> - <StackPanel Margin="0,10"/> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Router 2" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Router IP Address" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <TextBox Name="txtRouterIP2" Text="" Width="150" Template="{DynamicResource NormalTemplate}"/> - <Label Content="Optional" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150" FontStyle="Italic"/> - </StackPanel> - <StackPanel Orientation="Horizontal" Margin="0,2"> - <Label Content="Router ASN" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150"/> - <TextBox Name="txtRouterASN2" Text="" Width="150" Template="{DynamicResource NormalTemplate}"/> - <Label Content="Optional" Margin="10,0,0,0" HorizontalAlignment="Left" Width="150" FontStyle="Italic"/> - </StackPanel> - </StackPanel> - <StackPanel Name="panel9" HorizontalAlignment="Left" Height="522.101" VerticalAlignment="Top" Margin="169.149,0,0,0" Width="615.137"> - <Label Content="Review" FontSize="18" Margin="10,0"/> - <TextBlock Margin="14,0" TextWrapping="WrapWithOverflow"><Run Text="You have entered everything required for SDN Express to configure SDN on this system. If you would like to save this configuration, select Export. You can re-run SDN Express later with this file using the ConfigurationDataFile parameter."/></TextBlock> - <Grid Margin="0,10"/> - <TextBox Name="txtReview" Text="" Margin="14,0,0,0" Height="300" Template="{DynamicResource NormalTemplate}"/> - <Grid Margin="0,5"/> - <Button Name="btnExport" Content="Export..." Margin="0,0,14,0" HorizontalAlignment="Right" Width="153.868" Height="34.328" /> - <TextBlock Margin="10,0,0,0" TextWrapping="WrapWithOverflow"> - <Run Text="Help make SDN Express better by "/> - <Hyperlink Name="uri3" NavigateUri="mail:sdnfeedback@microsoft.com">providing feedback.</Hyperlink><LineBreak/> - </TextBlock> - - </StackPanel> - <TextBlock Margin="179.149,0,0,74.328" HorizontalAlignment="Left" VerticalAlignment="Bottom"> - <Run Text="For additional help and guidance, refer to the "/> - <Hyperlink Name="uri1" NavigateUri="https://learn.microsoft.com/windows-server/networking/sdn/plan/plan-software-defined-networking">Plan SDN topic on docs.microsoft.com.</Hyperlink> - </TextBlock> - <Button Name="btnBack1" Content="Back" Margin="0,0,168.868,10" HorizontalAlignment="Right" Width="153.868" Height="34.328" VerticalAlignment="Bottom"/> - <Button Name="btnNext1" Content="Next" Margin="0,0,10,10" HorizontalAlignment="Right" Width="153.868" Height="34.328" VerticalAlignment="Bottom"/> - </Grid> - </Window> -'@ - - -function ConfigDataToString { - param ( - [object] $InputData, - [string] $indent = "" - ) - if ($InputData.GetType().Name.EndsWith("String")) { - return "$indent $InputData" - } - - foreach ($i in $InputData.GetEnumerator()) { - if ($i.Value.GetType().Name.EndsWith("[]")) { - $result += ("`r`n$indent {0}:`r`n" -f $i.key) - foreach ($v in ($i.value)) { - $result += "$(ConfigDataToString $v "$indent ")`r`n" - } - $result += "`r`n" - } else { - if ($i.Value.GetType().Name.EndsWith("Object")) { - $result += ("$indent {0,-20}: {1}`r`n" -f $i.key, (convertto-psd1 $i.Value "$indent ")) - } - else { - if (!$i.key.Contains("Password")) { - $result += ("$indent {0,-20}: {1}`r`n" -f $i.key, $i.Value) - } - } - } - } - return $result -} - -function convertto-psd1 { -param( - [object] $InputData, - [string] $Indent = "" -) - if ($InputData.GetType().Name.EndsWith("String")) { - return "'$InputData'" - } - - $result = "$indent@{`r`n" - - foreach ($i in $InputData.GetEnumerator()) { - if ($i.Value.GetType().Name.EndsWith("Object[]")) { - $result += "$indent $($i.key) = @(" - $first = $true - foreach ($v in ($i.value)) { - if (!$first) { $result += ","} - $result += "`r`n" - $result += convertto-psd1 $v "$indent " - $first = $false - } - $result += "`r`n$indent )`r`n" - } elseif ($i.Value.GetType().Name.EndsWith("String[]")) { - $result += "$indent $($i.key) = @(" - $first = $true - foreach ($v in ($i.value)) { - if (!$first) { $result += ", "} - $result += "'$v'" - $first = $false - } - $result += " )`r`n" - } else { - if ($i.Value.GetType().Name.EndsWith("Object")) { - $result += "$indent $($i.key) = $(convertto-psd1 $i.Value "$indent ")`r`n" - } - else { - $result += "$indent $($i.key) = '$($i.Value)'`r`n" - } - } - } - $result += "$indent}" - return $result -} - - -#WARNING: this may be too slow -$ValidateFileExistsBlock = { - if(Test-path $this.Text) { - $this.Template=$global:defaulttxttemplate - } else { - $this.Template=$form.FindResource("ErrorTemplate") - } -} - - -function ValidateNotBlank { -param( - [Object] $ctl, - [String] $message = "This field is required." -) - if([String]::IsNullOrEmpty($ctl.text)) { - $ctl.Template = $form.FindResource("ErrorTemplate") - if ([String]::IsNullOrEmpty($ctl.Tooltip)) { - $ctl.tooltip = "Invalid value: this field is required.`r`nDetail: $message" - } - return $true - } else { - $ctl.tooltip = $message - $ctl.Template = $global:defaulttxttemplate - return $false - } -} - -function ValidatePassword { -param( - [Object] $ctl -) - if([String]::IsNullOrEmpty($ctl.password)) { - $ctl.Template=$form.FindResource("ErrorTemplate") - $ctl.tooltip = "Invalid value: This field is required." - return $true - } else { - $ctl.tooltip = "" - $ctl.Template=$global:defaulttxttemplate - return $false - } -} - - -function ValidateVLAN { -param( - [Object] $ctl -) - if ([Regex]::Match($ctl.text, "^\d{1,4}$").Success) { - $value = [Int32]::Parse($ctl.text) - if ($value -le 4096) { - $ctl.Template=$global:defaulttxttemplate - $ctl.tooltip = "" - return $false - } - $ctl.tooltip = "Invalid value: VLAN ID must be a value between 0 and 4096." - } else { - $ctl.tooltip = "Invalid value: VLAN ID can't contain non-numeric characters." - } - $ctl.Template=$form.FindResource("ErrorTemplate") - return $true -} - -function ValidateRegex { - param( - [Object] $ctl, - [string] $Regex, - [bool] $IsOptional = $false, - [string] $errormessage = "Field syntax is incorrect.", - [string] $message = "" - ) - if ($IsOptional -and [string]::IsNullOrEmpty($ctl.text)) { - $ctl.ToolTip = "Invalid value: $errormessage`r`nDetail: $message" - $ctl.Template=$global:defaulttxttemplate - return $FALSE - } - - if([Regex]::Match($ctl.text, $regex).Success) { - $ctl.Template=$global:defaulttxttemplate - $ctl.tooltip = $message - return $FALSE - } else { - $ctl.ToolTip = "Invalid value: $errormessage`r`nDetail: $message" - $ctl.Template=$form.FindResource("ErrorTemplate") - return $TRUE - } -} - -function ValidateIPAddress { - param( - [Object] $ctl, - [bool] $IsOptional = $false, - [string] $message = "" - ) - return ValidateRegex $ctl "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$" $IsOptional "IP address syntax is not correct." $message -} - -function ValidateSubnetPrefix { - param( - [Object] $ctl, - [bool] $IsOptional = $false, - [string] $message = "" - ) - return ValidateRegex $ctl "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$" $IsOptional "Subnet prefix does not match required format of <subnet>/<bits>." $message -} - -function ValidateASN { - param( - [Object] $ctl, - [bool] $IsOptional = $false - ) - if ($IsOptional -and [string]::IsNullOrEmpty($ctl.text)) { - $ctl.Template=$global:defaulttxttemplate - $ctl.ToolTip = "" - return $FALSE - } - - if ([Regex]::Match($ctl.text, "^\d{1,5}$").Success) { - $value = [Int32]::Parse($ctl.text) - if (($value -lt 65535) -and ($value -gt 0)) { - $ctl.ToolTip = "" - $ctl.Template=$global:defaulttxttemplate - return $false - } - $ctl.ToolTip = "ASN is outside the valid range of values. It must be an integer number between 1 and 65534." - } else { - $ctl.ToolTip = "ASN contains non-numeric characters. It must be an integer number between 1 and 65534." - } - $ctl.Template=$form.FindResource("ErrorTemplate") - return $true -} - -function ValidateMACAddress { - param( - [Object] $ctl, - [bool] $IsOptional = $false - ) - if ($IsOptional -and [string]::IsNullOrEmpty($ctl.text)) { - $ctl.Template=$global:defaulttxttemplate - return $false - } - - $phys = [regex]::matches($ctl.text.ToUpper().Replace(":", "").Replace("-", ""), '.{1,2}').groups.value -join "-" - - if((![bool]($phys -as [physicaladdress])) -or ($phys.length -gt 17)) { - $ctl.tooltip = "Invalid mac address. Must contain 12 hexadecimal digits, can be optionally separated by : or - every two digits. Example: 00:11:22:33:44:55" - $ctl.Template=$form.FindResource("ErrorTemplate") - return $true - } else { - $ctl.tooltip = "" - $ctl.Template=$global:defaulttxttemplate - return $false - } -} - -function IsIPAddressInSubnet { - param( - [String] $IP, - [String] $subnet - ) - - $parts = $subnet.split("/") - if ($parts.count -ne 2) { - return $false - } - - $prefix = $parts[0] - - if (!($ip -as [IPAddress]) -or !($prefix -as [IPAddress])) { - return $false - } - - $bits = [int] $parts[1] - - $ipbytes = ($IP -as [IPaddress]).getaddressbytes() - $prebytes = ($prefix -as [IPaddress]).getaddressbytes() - - $fullbytes = [int] ((32-$bits) / 8) - $partbits = [int] ((32-$bits) % 8) - - for ($i=3; $i -ge (4-$fullbytes); $i--) { - $ipbytes[$i] = 0 - $prebytes[$i] = 0 - } - - $bitmask = [byte] 0xff -shl $partbits - $ipbytes[$i] = $ipbytes[$i] -band $bitmask - $prebytes[$i] = $prebytes[$i] -band $bitmask - - for ($i = 0; $i -lt $ipbytes.count ; $i++) { - if ($ipbytes[$i] -ne $prebytes[$i]) { - return $false - } - } - return $true -} - -function ValidateIPAddressInSubnet { - param( - [Object] $ctl, - [string] $subnet - ) - - if(!(IsIPAddressInSubnet $ctl.text $subnet)) { - $ctl.tooltip = "IP Address must fall within the specified subnet prefix of $subnet." - $ctl.Template=$form.FindResource("ErrorTemplate") - return $true - } else { - $ctl.tooltip = "" - $ctl.Template=$global:defaulttxttemplate - return $false - } -} - -function ValidateIPAddressIsGreater { - param( - [Object] $ctl, - [string] $lower - ) - - $greater = $ctl.text - - if (!($lower -as [IPAddress]) -or !($greater -as [IPAddress])) { - $ctl.Template=$form.FindResource("ErrorTemplate") - return $true - } - - $lbytes = ($lower -as [IPaddress]).getaddressbytes() - $gbytes = ($greater -as [IPaddress]).getaddressbytes() - - for ($i = 0; $i -lt $lbytes.count ; $i++) { - if ($gbytes[$i] -lt $lbytes[$i]) { - $ctl.Template=$form.FindResource("ErrorTemplate") - $ctl.tooltip = "This IP address must be squentially higher than $lower." - return $true - } - if ($gbytes[$i] -gt $lbytes[$i]) { - $ctl.Template=$global:defaulttxttemplate - $ctl.tooltip = "" - return $false - } - } - -} - -$ValidatePanel2 = { - $results = @() - $results += ValidateNotBlank $txtVHDLocation "This field must contain the full path and filename of the VHD or VHDX to use for VM creation." - $results += ValidateNotBlank $txtVMPath "This field must contain the path on the Hyper-V host where VM files will be placed. This can be a UNC or CSV path as long as the host has the necessary access privileges for the share and file system." - $results += ValidateNotBlank $txtVMNamePrefix "This field must contain a prefix which is applied to the beginning of the VM and computer name of VMs created by SDN Express." - $results += ValidateNotBlank $txtVMDomain "This field must contain the name of the active directory domain to which the VMs will join." - #Needs domain user validation - $results += ValidateNotBlank $txtDomainJoinUsername "This field must contain the domain and username of a domain account that has permission to join machines to the above specified domain. Example: CONTOSO\alyoung" - $results += ValidatePassword $txtDomainJoinPassword - $results += ValidatePassword $txtLocalAdminPassword - - foreach ($result in $results) { - if ($result) { - $btnNext1.IsEnabled = $false - return - } - } - $btnNext1.IsEnabled = $true -} - -$ValidatePanel3 = { - $results = @() - $results += ValidateVLAN $txtManagementVLANID - $results += ValidateSubnetPrefix $txtManagementSubnetPrefix $false "Enter the subnet prefix of the management subnet. Example: 192.168.0.0/24" - $results += ValidateIPAddress $txtManagementGateway $false "Enter the IP address of managemetn subnet's gateway." - $results += ValidateIPAddress $txtManagementIPPoolStart $false "Enter the first IP address to assign to the management interface of the SDN infrastructure VMs created by SDN Express." - $results += ValidateIPAddress $txtManagementIPPoolEnd $false "Enter the last IP address to assign to the management interface of the SDN infrastructure VMs created by SDN Express. There must be enough addresses in this pool to assign one address to each VM created." - $results += ValidateIPAddress $txtManagementDNS1 $false "Enter a DNS server to assign to the SDN infrastructure VMs created by SDN express." - $results += ValidateIPAddress $txtManagementDNS2 $true "Optionally enter additional DNS servers to assign to the SDN infrastructure VMs created by SDN Express." - $results += ValidateIPAddress $txtManagementDNS3 $true "Optionally enter additional DNS servers to assign to the SDN infrastructure VMs created by SDN Express." - - $results += ValidateIPAddressInSubnet $txtManagementGateway $txtManagementSubnetPrefix.Text - $results += ValidateIPAddressInSubnet $txtManagementIPPoolStart $txtManagementSubnetPrefix.Text - $results += ValidateIPAddressInSubnet $txtManagementIPPoolEnd $txtManagementSubnetPrefix.Text - $results += ValidateIPAddressIsGreater $txtManagementIPPoolEnd $txtManagementIPPoolStart.Text - - foreach ($result in $results) { - if ($result) { - $btnNext1.IsEnabled = $false - return - } - } - $btnNext1.IsEnabled = $true -} - -$ValidatePanel4 = { - $results = @() - $results += ValidateVLAN $txtPAVLANID - $results += ValidateSubnetPrefix $txtPASubnetPrefix - $results += ValidateIPAddress $txtPAGateway - $results += ValidateIPAddress $txtPAIPPoolStart - $results += ValidateIPAddress $txtPAIPPoolEnd - $results += ValidateMACAddress $txtMacPoolStart - $results += ValidateMACAddress $txtMacPoolEnd - - foreach ($result in $results) { - if ($result) { - $btnNext1.IsEnabled = $false - return - } - } - $btnNext1.IsEnabled = $true -} - -$ValidatePanel5 = { - $results = @() - $results += ValidateNotBlank $txtRESTName "This field must contain the fully qualified domain name to be assigned to the REST interface of the network controller." - $results += ValidateNotBlank $txtHyperVHosts "This field must contain a list of Hyper-V hosts to be added to the network controller. They must be separated by newlines, commas or semicolons." - $results += ValidateNotBlank $txtHostUsername "This domain and username is used by the network controller to access the Hyper-V hosts and SDN gateways running on the hsots. Example: CONTOSO\AlYoung" - $results += ValidatePassword $txtHostPassword - - foreach ($result in $results) { - if ($result) { - $btnNext1.IsEnabled = $false - return - } - } - $btnNext1.IsEnabled = $true -} - -$ValidatePanel6 = { - $results = @() - $results += ValidateSubnetPrefix $txtPrivateVIPs - $results += ValidateSubnetPrefix $txtPublicVIPs - - foreach ($result in $results) { - if ($result) { - $btnNext1.IsEnabled = $false - return - } - } - $btnNext1.IsEnabled = $true -} -$ValidatePanel7 = { - $results = @() - $results += ValidateSubnetPrefix $txtGREVIPs - - foreach ($result in $results) { - if ($result) { - $btnNext1.IsEnabled = $false - return - } - } - $btnNext1.IsEnabled = $true -} - -$ValidatePanel8 = { - $results = @() - $results += ValidateASN $txtSDNASN - $results += ValidateIPAddress $txtRouterIP1 - $results += ValidateASN $txtRouterASN1 - $results += ValidateIPAddress $txtRouterIP2 $true - $results += ValidateASN $txtRouterASN2 $true - - foreach ($result in $results) { - if ($result) { - $btnNext1.IsEnabled = $false - return - } - } - $btnNext1.IsEnabled = $true -} - - function AddTxtValidation { - param( - $objtxt, - $block - ) - $objtxt.Add_TextChanged($block) - } - - -function GetNextIP { - param ( - $Ip - ) - if (!($IP -as [IPAddress])) - { - return "" - } - - $mb = ($IP -as [IPaddress]).getaddressbytes() - - for ($c = $mb.count; $c -gt 0; $c--) { - if ($mb[$c-1] -eq 0xff) { - $mb[$c-1] = 0 - } else { - $mb[$c-1]++ - return ($mb -as [ipaddress]).ToString() - } - } - } - -function GetNextMAC { -param ( - $Mac - ) - $mac = [regex]::matches($mac.ToUpper().Replace(":", "").Replace("-", ""), '..').groups.value -join "-" - - if (!($mac -as [physicaladdress])) { - return "" - } - - $mb = ($mac -as [physicaladdress]).getaddressbytes() - - for ($c = $mb.count; $c -gt 0; $c--) { - if ($mb[$c-1] -eq 0xff) { - $mb[$c-1] = 0 - } else { - $mb[$c-1]++ - $newmac = ($mb -as [physicaladdress]).ToString() - return [regex]::matches($newmac.ToUpper().Replace(":", "").Replace("-", ""), '..').groups.value -join "-" - } - } -} - - function GenerateConfigData { - $ConfigData = [ordered] @{} - - $Path = $txtVHDLocation.Text - if (![string]::IsNullOrEmpty($path)) { - $PathParts = $path.Split("\") - - $ConfigData.ScriptVersion = "2.0" - - $ConfigData.VHDPath = $Path.substring(0, $Path.length-$PathParts[$PathParts.Count-1].length-1) - $ConfigData.VHDFile = $PathParts[$PathParts.Count-1] - } - - $ConfigData.VMLocation = $txtVMPath.Text - $ConfigData.JoinDomain = $txtVMDomain.text - - $ConfigData.ManagementVLANID = $txtManagementVLANID.text - $ConfigData.ManagementSubnet = $txtManagementSubnetPrefix.text - $ConfigData.ManagementGateway = $txtManagementGateway.text - $ConfigData.ManagementDNS = @() - $ConfigData.ManagementDNS += $txtManagementDNS1.text - if (![String]::IsNullOrEmpty($txtManagementDNS2.text)) { $ConfigData.ManagementDNS += $txtManagementDNS2.text } - if (![String]::IsNullOrEmpty($txtManagementDNS3.text)) { $ConfigData.ManagementDNS += $txtManagementDNS3.text } - - $ConfigData.DomainJoinUsername = $txtDomainJoinUsername.text - $ConfigData.DomainJoinSecurePassword = $txtDomainJoinPassword.Password | ConvertTo-SecureString -AsPlainText -Force | convertfrom-securestring - - $ConfigData.LocalAdminSecurePassword = $txtLocalAdminPassword.Password | ConvertTo-SecureString -AsPlainText -Force | convertfrom-securestring - - $ConfigData.LocalAdminDomainUser = $txtHostUserName.text - - $ConfigData.RestName = $txtRESTName.Text - - $hosttxt = $txtHyperVHosts.text - $hosttxt = $hosttxt.Replace("`r", "").Replace(" ", "") - $hosts = $hosttxt.Split("`n,;") - $ConfigData.HyperVHosts = $hosts - - - $nexthost = 0 - $nextIP = $txtManagementIPPoolStart.Text - $nextPA = $txtPAIPPoolStart.Text - $nextMAC = $txtMacPoolStart.Text - - $ConfigData.NCs = @() - $ConfigData.NCs += [ordered] @{ComputerName="$($txtVMNamePrefix.Text)NC01"; HostName=$hosts[$nexthost]; ManagementIP=$nextIP; MACAddress=$nextMac} - - $nextip = GetNextIP $nextip - $nextmac = GetNextMac $nextmac - $nexthost = ($nexthost + 1) % $hosts.count - - if ($rdoMultiNode.IsChecked) { - - $ConfigData.NCs += [ordered] @{ComputerName="$($txtVMNamePrefix.Text)NC02"; HostName=$hosts[$nexthost]; ManagementIP=$nextIP; MACAddress=$nextMac} - $nextip = GetNextIP $nextip - $nextmac = GetNextMac $nextmac - $nexthost = ($nexthost + 1) % $hosts.count - - $ConfigData.NCs += [ordered] @{ComputerName="$($txtVMNamePrefix.Text)NC03"; HostName=$hosts[$nexthost]; ManagementIP=$nextIP; MACAddress=$nextMac} - $nextip = GetNextIP $nextip - $nextmac = GetNextMac $nextmac - $nexthost = ($nexthost + 1) % $hosts.count - } - - $ConfigData.Muxes = @() - for ($c = 1; $c -le $sliMuxCount.Value; $c++) { - - $mgmtmac = $nextmac - $nextmac = getnextmac $nextmac - $pamac = $nextmac - $nextmac = getnextmac $nextmac - - $ConfigData.Muxes += [ordered] @{ComputerName="$($txtVMNamePrefix.Text)Mux{0:00}" -f $c; HostName=$hosts[$nexthost]; ManagementIP=$nextip; MACAddress=$mgmtmac; PAIPAddress=$nextpa; PAMACAddress=$pamac} - - $nexthost = ($nexthost + 1) % $hosts.count - $nextip = getnextip $nextip - $nextpa = getnextip $nextpa - - } - - $papoolstart = $nextpa - - $ConfigData.Gateways = @() - for ($c = 1; $c -le $sliGatewayCount.Value; $c++) { - $mgmtmac = $nextmac - $nextmac = getnextmac $nextmac - $femac = $nextmac - $nextmac = getnextmac $nextmac - $bemac = $nextmac - $nextmac = getnextmac $nextmac - - $ConfigData.Gateways += [ordered] @{ComputerName="$($txtVMNamePrefix.Text)GW{0:00}" -f $c; HostName=$hosts[$nexthost]; ManagementIP=$nextip; MACAddress=$mgmtmac; FrontEndIp=$nextpa; FrontEndMac=$femac; BackEndMac=$bemac} - - $nexthost = ($nexthost + 1) % $hosts.count - $nextip = getnextip $nextip - $nextpa = getnextip $nextpa - - } - - $ConfigData.NCUsername = $txtHostUsername.Text - $ConfigData.NCSecurePassword = $txtHostPassword.Password | ConvertTo-SecureString -AsPlainText -Force | convertfrom-securestring - - $ConfigData.PAVLANID = $txtPAVLANID.text - $ConfigData.PASubnet = $txtPASubnetPrefix.text - $ConfigData.PAGateway = $txtPAGateway.text - $ConfigData.PAPoolStart = $papoolstart - $ConfigData.PAPoolEnd = $txtPAIPPoolEnd.Text - - $ConfigData.SDNMacPoolStart = $nextMAC - $ConfigData.SDNMacPoolEnd = $txtMacPoolEnd.Text - - $ConfigData.SDNASN = $txtSDNASN.text - $ConfigData.Routers = @( - [ordered] @{ RouterASN=$txtRouterASN1.text; RouterIPAddress=$txtRouterIP1.text} - ) - if (![String]::IsNullOrEmpty($txtRouterIP2.text)) { - $ConfigData.Routers += [ordered] @{ RouterASN=$txtRouterASN2.text; RouterIPAddress=$txtRouterIP2.text} - } - - $ConfigData.PrivateVIPSubnet = $txtPrivateVIPs.text - $ConfigData.PublicVIPSubnet = $txtPublicVIPs.text - - $ConfigData.PoolName = "DefaultAll" - $ConfigData.GRESubnet = $txtGREVIPs.text - - - $ConfigData.Capacity = 10000 - - - return $ConfigData - } - - - function SetPanel - { - param( - $PanelIndex - ) - if ($panelIndex -eq 1) { $mark1.Visibility = "Visible"; $panel1.Visibility = "Visible"; } else { $mark1.Visibility = "Hidden"; $panel1.Visibility = "Hidden" } - if ($panelIndex -eq 2) { $mark2.Visibility = "Visible"; $panel2.Visibility = "Visible"; invoke-command $ValidatePanel2 } else { $mark2.Visibility = "Hidden"; $panel2.Visibility = "Hidden" } - if ($panelIndex -eq 3) { $mark3.Visibility = "Visible"; $panel3.Visibility = "Visible"; invoke-command $ValidatePanel3 } else { $mark3.Visibility = "Hidden"; $panel3.Visibility = "Hidden" } - if ($panelIndex -eq 4) { $mark4.Visibility = "Visible"; $panel4.Visibility = "Visible"; invoke-command $ValidatePanel4 } else { $mark4.Visibility = "Hidden"; $panel4.Visibility = "Hidden" } - if ($panelIndex -eq 5) { $mark5.Visibility = "Visible"; $panel5.Visibility = "Visible"; invoke-command $ValidatePanel5 } else { $mark5.Visibility = "Hidden"; $panel5.Visibility = "Hidden" } - if ($panelIndex -eq 6) { $mark6.Visibility = "Visible"; $panel6.Visibility = "Visible"; invoke-command $ValidatePanel6 } else { $mark6.Visibility = "Hidden"; $panel6.Visibility = "Hidden" } - if ($panelIndex -eq 7) { $mark7.Visibility = "Visible"; $panel7.Visibility = "Visible"; invoke-command $ValidatePanel7 } else { $mark7.Visibility = "Hidden"; $panel7.Visibility = "Hidden" } - if ($panelIndex -eq 8) { $mark8.Visibility = "Visible"; $panel8.Visibility = "Visible" } else { $mark8.Visibility = "Hidden"; $panel8.Visibility = "Hidden" } - if ($panelIndex -eq 9) { - $mark9.Visibility = "Visible"; - $panel9.Visibility = "Visible"; - $btnNext1.Content = "Deploy" - $txtReview.Text = ConfigDataToString (GenerateConfigData) - } else { - $mark9.Visibility = "Hidden"; - $panel9.Visibility = "Hidden"; - $btnNext1.Content = "Next" - } - if ($panelIndex -eq 10) { $global:Deploy = $true; $form.Close() } - } - - - #Read XAML - $reader=(New-Object System.Xml.XmlNodeReader $xaml) - try{$Form=[Windows.Markup.XamlReader]::Load( $reader )} - catch{Write-Host "Unable to load Windows.Markup.XamlReader. Some possible causes for this problem include: .NET Framework is missing PowerShell must be launched with PowerShell -sta, invalid XAML code was encountered."; exit} - - $xaml.SelectNodes("//*[@Name]") | %{Set-Variable -Name ($_.Name) -Value $Form.FindName($_.Name)} - - $global:PanelIndex = 1 - $global:Deploy = $false - $global:defaulttxttemplate = $form.FindResource("NormalTemplate") - - $btnBack1.IsEnabled = $false - - $uri1.Add_Click({ Start-Process -FilePath $this.NavigateUri}) - $uri2.Add_Click({ Start-Process -FilePath $this.NavigateUri}) - $uri3.Add_Click({ Start-Process -FilePath $this.NavigateUri}) - $uri4.Add_Click({ Start-Process -FilePath $this.NavigateUri}) - - $btnBack1.Add_Click({$global:PanelIndex=$global:panelIndex - 1; SetPanel $global:panelIndex; if ($global:panelIndex -eq 1) { $btnBack1.IsEnabled = $false }}) - $btnNext1.Add_Click({$global:PanelIndex=$global:panelIndex + 1; SetPanel $global:panelIndex; if ($global:panelIndex -gt 1) { $btnBack1.IsEnabled = $true }}) - $btnBrowse.Add_Click({ - [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null - - $ofd = New-Object System.Windows.Forms.OpenFileDialog - $ofd.initialDirectory = $txtVHDLocation.text - $ofd.filter = "Virtual Hard Disks (*.vhdx; *.vhd)|*.vhdx;*.vhd" - $ofd.ShowDialog() | Out-Null - $txtVHDLocation.text = $ofd.Filename - }) - $btnExport.Add_Click({ - [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null - - $ofd = New-Object System.Windows.Forms.SaveFileDialog - $ofd.filter = "Powershell (*.psd1)|*.psd1" - $result = $ofd.ShowDialog() - - if ($result) - { - $fn = $ofd.filename - $ConfigData = GenerateConfigData - - "" | out-file $fn - Convertto-psd1 $ConfigData | out-file $fn -append - - $Result = $null - } - }) - - - # Panel 2 Validation - AddTxtValidation $txtVHDLocation $ValidatePanel2 - AddTxtValidation $txtVMPath $ValidatePanel2 - AddTxtValidation $txtVMNamePrefix $ValidatePanel2 - AddTxtValidation $txtVMDomain $ValidatePanel2 - AddTxtValidation $txtDomainJoinUsername $ValidatePanel2 #Needs domain user validation - $txtDomainJoinPassword.Add_PasswordChanged($ValidatePanel2) - $txtLocalAdminPassword.Add_PasswordChanged($ValidatePanel2) - - # Panel 3 Validation - AddTxtValidation $txtManagementVLANID $ValidatePanel3 - AddTxtValidation $txtManagementSubnetPrefix $ValidatePanel3 - AddTxtValidation $txtManagementGateway $ValidatePanel3 - AddTxtValidation $txtManagementIPPoolStart $ValidatePanel3 - AddTxtValidation $txtManagementIPPoolEnd $ValidatePanel3 - AddTxtValidation $txtManagementDNS1 $ValidatePanel3 - AddTxtValidation $txtManagementDNS2 $ValidatePanel3 - AddTxtValidation $txtManagementDNS3 $ValidatePanel3 - - # Panel 4 Validation - AddTxtValidation $txtPAVLANID $ValidatePanel4 - AddTxtValidation $txtPASubnetPrefix $ValidatePanel4 - AddTxtValidation $txtPAGateway $ValidatePanel4 - AddTxtValidation $txtPAIPPoolStart $ValidatePanel4 - AddTxtValidation $txtPAIPPoolEnd $ValidatePanel4 - - $txtMacPoolStart.Text = "00:1D:D8:B7:1C:00" - $txtMacPoolEnd.Text = "00:1D:D8:B7:1F:FF" - - AddTxtValidation $txtMacPoolStart $ValidatePanel4 - AddTxtValidation $txtMacPoolEnd $ValidatePanel4 - - # Panel 5 Validation - AddTxtValidation $txtRESTName $ValidatePanel5 - AddTxtValidation $txtHyperVHosts $ValidatePanel5 # Needs multiline host validation - AddTxtValidation $txtHostUsername $ValidatePanel5 # Needs domain user validation - $txtHostPassword.Add_PasswordChanged($ValidatePanel5) - - # Panel 6 Validation - AddTxtValidation $txtPrivateVIPs $ValidatePanel6 - AddTxtValidation $txtPublicVIPs $ValidatePanel6 - - # Panel 7 Validation - AddTxtValidation $txtGREVIPs $ValidatePanel7 - - # Panel 8 Validation - AddTxtValidation $txtSDNASN $ValidatePanel8 - AddTxtValidation $txtRouterIP1 $ValidatePanel8 - AddTxtValidation $txtRouterASN1 $ValidatePanel8 - AddTxtValidation $txtRouterIP2 $ValidatePanel8 - AddTxtValidation $txtRouterASN2 $ValidatePanel8 - - SetPanel $PanelIndex - - $sliGatewayCount.Add_ValueChanged({ - $newmax = $sliGatewayCount.Value-1 - if ($sliRedundantCount.Value -gt $newmax) { - $sliRedundantCount.Value = $newmax - } - $sliRedundantCount.Maximum = $newmax - $sliRedundantCount.Width = ($newmax-1) * 40 - - }) - - $Form.ShowDialog() | out-null - - if ($global:deploy) { - return GenerateConfigData - } -} \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/SDN/Single-NC.psd1 b/azure_jumpstart_hcibox/artifacts/SDN/Single-NC.psd1 deleted file mode 100644 index a00294138f..0000000000 Binary files a/azure_jumpstart_hcibox/artifacts/SDN/Single-NC.psd1 and /dev/null differ diff --git a/azure_jumpstart_hcibox/artifacts/Uninstall-AKS.ps1 b/azure_jumpstart_hcibox/artifacts/Uninstall-AKS.ps1 deleted file mode 100644 index f2ab781be7..0000000000 --- a/azure_jumpstart_hcibox/artifacts/Uninstall-AKS.ps1 +++ /dev/null @@ -1,47 +0,0 @@ -# Set paths -$Env:HCIBoxDir = "C:\HCIBox" -$Env:HCIBoxLogsDir = "C:\HCIBox\Logs" -$Env:HCIBoxVMDir = "C:\HCIBox\Virtual Machines" -$Env:HCIBoxKVDir = "C:\HCIBox\KeyVault" -$Env:HCIBoxGitOpsDir = "C:\HCIBox\GitOps" -$Env:HCIBoxIconDir = "C:\HCIBox\Icons" -$Env:HCIBoxVHDDir = "C:\HCIBox\VHD" -$Env:HCIBoxSDNDir = "C:\HCIBox\SDN" -$Env:HCIBoxWACDir = "C:\HCIBox\Windows Admin Center" -$Env:agentScript = "C:\HCIBox\agentScript" -$Env:ToolsDir = "C:\Tools" -$Env:tempDir = "C:\Temp" -$Env:VMPath = "C:\VMs" - -Start-Transcript -Path $Env:HCIBoxLogsDir\Uninstall-AKS.log - -# Import Configuration Module and create Azure login credentials -Write-Header 'Importing config' -$ConfigurationDataFile = 'C:\HCIBox\HCIBox-Config.psd1' -$SDNConfig = Import-PowerShellDataFile -Path $ConfigurationDataFile - -# Generate credential objects -Write-Header 'Creating credentials and connecting to Azure' -$user = "jumpstart.local\administrator" -$password = ConvertTo-SecureString -String $SDNConfig.SDNAdminPassword -AsPlainText -Force -$adcred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user, $password # Domain credential - -$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 - -# Uninstall AksHci - only need to perform the following on one of the nodes -$clusterName = $env:AKSClusterName -Write-Header "Removing AKS-HCI workload cluster" -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - Disable-AksHciArcConnection -name $using:clusterName - Remove-AksHciCluster -name $using:clusterName -Confirm:$false -} - -Write-Header "Uninstalling AKS-HCI management plane" -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - Uninstall-AksHci -Confirm:$false -} -# Set env variable deployAKSHCI to true (in case the script was run manually) -[System.Environment]::SetEnvironmentVariable('deployAKSHCI', 'false',[System.EnvironmentVariableTarget]::Machine) - -Stop-Transcript \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/Uninstall-ResourceBridge.ps1 b/azure_jumpstart_hcibox/artifacts/Uninstall-ResourceBridge.ps1 deleted file mode 100644 index f4a9ff61e0..0000000000 --- a/azure_jumpstart_hcibox/artifacts/Uninstall-ResourceBridge.ps1 +++ /dev/null @@ -1,46 +0,0 @@ -# Set paths -$Env:HCIBoxDir = "C:\HCIBox" -$Env:HCIBoxLogsDir = "C:\HCIBox\Logs" -$Env:HCIBoxVMDir = "C:\HCIBox\Virtual Machines" -$Env:HCIBoxKVDir = "C:\HCIBox\KeyVault" -$Env:HCIBoxGitOpsDir = "C:\HCIBox\GitOps" -$Env:HCIBoxIconDir = "C:\HCIBox\Icons" -$Env:HCIBoxVHDDir = "C:\HCIBox\VHD" -$Env:HCIBoxSDNDir = "C:\HCIBox\SDN" -$Env:HCIBoxWACDir = "C:\HCIBox\Windows Admin Center" -$Env:agentScript = "C:\HCIBox\agentScript" -$Env:ToolsDir = "C:\Tools" -$Env:tempDir = "C:\Temp" -$Env:VMPath = "C:\VMs" - -# Import Configuration Module -$ConfigurationDataFile = "$Env:HCIBoxDir\HCIBox-Config.psd1" -$SDNConfig = Import-PowerShellDataFile -Path $ConfigurationDataFile - -# Set AD Domain cred -$user = "jumpstart.local\administrator" -$password = ConvertTo-SecureString -String $SDNConfig.SDNAdminPassword -AsPlainText -Force -$adcred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user, $password - -$csv_path = "C:\ClusterStorage\S2D_vDISK1" -$subId = $env:subscriptionId -$rg = $env:resourceGroup -$spnClientId = $env:spnClientId -$spnSecret = $env:spnClientSecret -$spnTenantId = $env:spnTenantId -$resource_name = "HCIBox-ResourceBridge" -$custom_location_name = "hcibox-rb-cl" - -Invoke-Command -VMName $SDNConfig.HostList[0] -Credential $adcred -ScriptBlock { - Write-Host "Removing Arc Resource Bridge." - $WarningPreference = "SilentlyContinue" - az login --service-principal --username $using:spnClientID --password=$using:spnSecret --tenant $using:spnTenantId - - az azurestackhci virtualnetwork delete --subscription $using:subId --resource-group $using:rg --name "vlan200" --yes - az azurestackhci galleryimage delete --subscription $using:subId --resource-group $using:rg --name "ubuntu20" - az azurestackhci galleryimage delete --subscription $using:subId --resource-group $using:rg --name "win2k22" - az customlocation delete --resource-group $using:rg --name $using:custom_location_name --yes - az k8s-extension delete --cluster-type appliances --cluster-name $using:resource_name --resource-group $using:rg --name vmss-hci --yes - az arcappliance delete hci --config-file $using:csv_path\ResourceBridge\hci-appliance.yaml --yes - Remove-ArcHciConfigFiles -} \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/dataController.json b/azure_jumpstart_hcibox/artifacts/dataController.json index f7e2969098..6c3a6d1bac 100644 --- a/azure_jumpstart_hcibox/artifacts/dataController.json +++ b/azure_jumpstart_hcibox/artifacts/dataController.json @@ -49,7 +49,7 @@ "type": "string" }, "logAnalyticsPrimaryKey": { - "type": "string" + "type": "securestring" }, "resourceTags": { "type": "object" diff --git a/azure_jumpstart_hcibox/artifacts/hci.json b/azure_jumpstart_hcibox/artifacts/hci.json new file mode 100644 index 0000000000..42933f2ac0 --- /dev/null +++ b/azure_jumpstart_hcibox/artifacts/hci.json @@ -0,0 +1,613 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "deploymentMode": { + "defaultValue": "Validate", + "type": "string", + "allowedValues": [ + "Validate", + "Deploy" + ], + "metadata": { + "description": "First must pass Validate prior running Deploy" + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "The KeyVault name used to store the secrets." + }, + "defaultValue": "hcibox-kv-[substring(newGuid(),0,4)]" + }, + "softDeleteRetentionDays": { + "type": "int", + "defaultValue": 30 + }, + "diagnosticStorageAccountName": { + "type": "string", + "metadata": { + "description": "The name of the storage account used for KV audit logs" + } + }, + "logsRetentionInDays": { + "type": "int", + "defaultValue": 30, + "minValue": 0, + "maxValue": 365, + "metadata": { + "description": "Specifies the number of days that logs are gonna be kept. If you do not want to apply any retention policy and retain data forever, set value to 0." + } + }, + "storageAccountType": { + "type": "string", + "defaultValue": "Standard_LRS", + "allowedValues": [ + "Premium_LRS", + "Premium_ZRS", + "Standard_GRS", + "Standard_GZRS", + "Standard_LRS", + "Standard_RAGRS", + "Standard_RAGZRS", + "Standard_ZRS" + ], + "metadata": { + "description": "Storage Account type" + } + }, + "secretsLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The fqdn of your KeyVault" + } + }, + "ClusterWitnessStorageAccountName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The existing storage account name used for the cluster witness" + } + }, + "clusterName": { + "type": "string", + "minLength": 3, + "maxLength": 24 + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]" + }, + "localAdminSecretName": { + "type": "string", + "defaultValue": "LocalAdminCredential", + "allowedValues": [ + "LocalAdminCredential" + ], + "metadata": { + "description": "The name can not be changed" + } + }, + "localAdminSecretValue": { + "type": "securestring" + }, + "domainAdminSecretName": { + "type": "string", + "defaultValue": "AzureStackLCMUserCredential", + "allowedValues": [ + "AzureStackLCMUserCredential" + ], + "metadata": { + "description": "The name can not be changed" + } + }, + "domainAdminSecretValue": { + "type": "securestring" + }, + "arbDeploymentSpnName": { + "type": "string", + "defaultValue": "DefaultARBApplication", + "allowedValues": [ + "DefaultARBApplication" + ], + "metadata": { + "description": "The name can not be changed" + } + }, + "arbDeploymentSpnValue": { + "type": "securestring" + }, + "storageWitnessName": { + "type": "string", + "defaultValue": "WitnessStorageKey", + "allowedValues": [ + "WitnessStorageKey" + ], + "metadata": { + "description": "The name can not be changed" + } + }, + "storageWitnessValue": { + "type": "securestring" + }, + "apiVersion": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The api version for deploying a hci cluster" + } + }, + "arcNodeResourceIds": { + "defaultValue": [ + ], + "type": "array", + "metadata": { + "description": "The arc for server node Ids of the hci cluster" + } + }, + "domainFqdn": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The domain name of the Active Directory Domain Services" + } + }, + "namingPrefix": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The ADFS name prefix" + } + }, + "adouPath": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The ADDS OU path" + } + }, + "securityLevel": { + "defaultValue": "Recommended", + "type": "string", + "allowedValues": [ + "Recommended", + "Customized" + ], + "metadata": { + "description": "The security level data for deploying a hci cluster" + } + }, + "driftControlEnforced": { + "defaultValue": true, + "type": "bool", + "allowedValues": [ + true, + false + ], + "metadata": { + "description": "The security setting driftControlEnforced data for deploying a hci cluster" + } + }, + "credentialGuardEnforced": { + "defaultValue": true, + "type": "bool", + "allowedValues": [ + true, + false + ], + "metadata": { + "description": "The security setting credentialGuardEnforced data for deploying a hci cluster" + } + }, + "smbSigningEnforced": { + "defaultValue": true, + "type": "bool", + "allowedValues": [ + true, + false + ], + "metadata": { + "description": "The security setting smbSigningEnforced data for deploying a hci cluster" + } + }, + "smbClusterEncryption": { + "defaultValue": false, + "type": "bool", + "allowedValues": [ + true, + false + ], + "metadata": { + "description": "The security setting smbClusterEncryption data for deploying a hci cluster" + } + }, + "bitlockerBootVolume": { + "defaultValue": true, + "type": "bool", + "allowedValues": [ + true, + false + ], + "metadata": { + "description": "The security setting bitlockerBootVolume data for deploying a hci cluster" + } + }, + "bitlockerDataVolumes": { + "defaultValue": true, + "type": "bool", + "allowedValues": [ + true, + false + ], + "metadata": { + "description": "The security setting bitlockerDataVolumes data for deploying a hci cluster" + } + }, + "wdacEnforced": { + "defaultValue": true, + "type": "bool", + "allowedValues": [ + true, + false + ], + "metadata": { + "description": "The security setting wdacEnforced data for deploying a hci cluster" + } + }, + "streamingDataClient": { + "defaultValue": true, + "type": "bool", + "allowedValues": [ + true, + false + ], + "metadata": { + "description": "The metrics data for deploying a hci cluster" + } + }, + "euLocation": { + "defaultValue": false, + "type": "bool", + "allowedValues": [ + true, + false + ], + "metadata": { + "description": "The location data for deploying a hci cluster" + } + }, + "episodicDataUpload": { + "defaultValue": true, + "type": "bool", + "allowedValues": [ + true, + false + ], + "metadata": { + "description": "The diagnostic data for deploying a hci cluster" + } + }, + "configurationMode": { + "defaultValue": "Express", + "type": "string", + "allowedValues": [ + "Express", + "InfraOnly", + "KeepStorage" + ], + "metadata": { + "description": "The storage volume configuration mode" + } + }, + "subnetMask": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The subnet mask for deploying a hci cluster" + } + }, + "defaultGateway": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The default gateway for deploying a hci cluster" + } + }, + "startingIPAddress": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The starting ip address for deploying a hci cluster" + } + }, + "endingIPAddress": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The ending ip address for deploying a hci cluster" + } + }, + "dnsServers": { + "defaultValue": [ + "" + ], + "type": "array", + "metadata": { + "description": "The dns servers for deploying a hci cluster" + } + }, + "physicalNodesSettings": { + "type": "array", + "metadata": { + "description": "The physical nodes settings for deploying a hci cluster" + } + }, + "networkingType": { + "defaultValue": "", + "type": "string", + "allowedValues": [ + "switchedMultiServerDeployment", + "switchlessMultiServerDeployment", + "singleServerDeployment" + ], + "metadata": { + "description": "The networking type for deploying a hci cluster" + } + }, + "intentList": { + "defaultValue": [ + ], + "type": "array", + "metadata": { + "description": "The intent list for deploying a hci cluster" + } + }, + "storageNetworkList": { + "defaultValue": [ + ], + "type": "array", + "metadata": { + "description": "The storage network list for deploying a hci cluster" + } + }, + "storageConnectivitySwitchless": { + "defaultValue": false, + "type": "bool", + "metadata": { + "description": "The storage connectivity switchless value for deploying a hci cluster" + } + }, + "customLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The custom location for deploying a hci cluster" + } + } + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-01-01", + "name": "[parameters('diagnosticStorageAccountName')]", + "location": "[parameters('location')]", + "sku": { + "name": "[parameters('storageAccountType')]", + "tier": "Standard" + }, + "kind": "StorageV2", + "properties": { + "supportsHttpsTrafficOnly": true + } + }, + { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2021-06-01-preview", + "name": "[parameters('keyVaultName')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', parameters('diagnosticStorageAccountName'))]" + ], + "properties": { + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": "[parameters('softDeleteRetentionDays')]", + "enableRbacAuthorization": true, + "publicNetworkAccess": "Enabled", + "accessPolicies": [], + "tenantId": "[parameters('tenantId')]", + "sku": { + "name": "standard", + "family": "A" + } + } + }, + { + "type": "Microsoft.KeyVault/vaults/providers/diagnosticsettings", + "name": "[concat(parameters('keyVaultName'), '/Microsoft.Insights/service')]", + "apiVersion": "2016-09-01", + "Location": "[resourceGroup().location]", + "dependsOn": [ + "[concat('Microsoft.KeyVault/vaults/', parameters('keyVaultName'))]", + "[concat('Microsoft.Storage/storageAccounts/', parameters('diagnosticStorageAccountName'))]" + ], + "properties": { + "storageAccountId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('diagnosticStorageAccountName'))]", + "logs": [ + { + "category": "AuditEvent", + "enabled": true, + "retentionPolicy": { + "enabled": true, + "days": "[parameters('LogsRetentionInDays')]" + } + } + ] + } + }, + { + "condition": "[equals(parameters('deploymentMode'), 'Validate')]", + "type": "Microsoft.AzureStackHCI/clusters", + "apiVersion": "2023-08-01-preview", + "name": "[parameters('clusterName')]", + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" + ], + "identity": { + "type": "SystemAssigned" + }, + "location": "[parameters('location')]", + "properties": { + } + }, + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2021-06-01-preview", + "name": "[concat(parameters('keyVaultName'), '/', parameters('domainAdminSecretName'))]", + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" + ], + "location": "[parameters('location')]", + "scale": null, + "properties": { + "contentType": "Secret", + "value": "[parameters('domainAdminSecretValue')]", + "attributes": { + "enabled": true + } + } + }, + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2021-06-01-preview", + "name": "[concat(parameters('keyVaultName'), '/', parameters('localAdminSecretName'))]", + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" + ], + "location": "[parameters('location')]", + "scale": null, + "properties": { + "contentType": "Secret", + "value": "[parameters('localAdminSecretValue')]", + "attributes": { + "enabled": true + } + } + }, + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2021-06-01-preview", + "name": "[concat(parameters('keyVaultName'), '/', parameters('arbDeploymentSpnName'))]", + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" + ], + "location": "[parameters('location')]", + "scale": null, + "properties": { + "contentType": "Secret", + "value": "[parameters('arbDeploymentSpnValue')]", + "attributes": { + "enabled": true + } + } + }, + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2021-06-01-preview", + "name": "[concat(parameters('keyVaultName'), '/', parameters('storageWitnessName'))]", + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" + ], + "location": "[parameters('location')]", + "scale": null, + "properties": { + "contentType": "Secret", + "value": "[parameters('storageWitnessValue')]", + "attributes": { + "enabled": true + } + } + }, + { + "type": "microsoft.azurestackhci/clusters/deploymentSettings", + "apiVersion": "[parameters('apiVersion')]", + "name": "[format('{0}/default', parameters('clusterName'))]", + "dependsOn": [ + "[resourceId('Microsoft.AzureStackHCI/clusters', parameters('clusterName'))]" + ], + "properties": { + "arcNodeResourceIds": "[parameters('arcNodeResourceIds')]", + "deploymentMode": "[parameters('deploymentMode')]", + "deploymentConfiguration": { + "version": "10.0.0.0", + "scaleUnits": [ + { + "deploymentData": { + "securitySettings": { + "hvciProtection": true, + "drtmProtection": true, + "driftControlEnforced": "[parameters('driftControlEnforced')]", + "credentialGuardEnforced": "[parameters('credentialGuardEnforced')]", + "smbSigningEnforced": "[parameters('smbSigningEnforced')]", + "smbClusterEncryption": "[parameters('smbClusterEncryption')]", + "sideChannelMitigationEnforced": true, + "bitlockerBootVolume": "[parameters('bitlockerBootVolume')]", + "bitlockerDataVolumes": "[parameters('bitlockerDataVolumes')]", + "wdacEnforced": "[parameters('wdacEnforced')]" + }, + "observability": { + "streamingDataClient": "[parameters('streamingDataClient')]", + "euLocation": "[parameters('euLocation')]", + "episodicDataUpload": "[parameters('episodicDataUpload')]" + }, + "cluster": { + "name": "[parameters('clusterName')]", + "witnessType": "Cloud", + "witnessPath": "", + "cloudAccountName": "[parameters('ClusterWitnessStorageAccountName')]", + "azureServiceEndpoint": "core.windows.net" + }, + "storage": { + "configurationMode": "[parameters('configurationMode')]" + }, + "namingPrefix": "[parameters('namingPrefix')]", + "domainFqdn": "[parameters('domainFqdn')]", + "infrastructureNetwork": [ + { + "subnetMask": "[parameters('subnetMask')]", + "gateway": "[parameters('defaultGateway')]", + "ipPools": [ + { + "startingAddress": "[parameters('startingIPAddress')]", + "endingAddress": "[parameters('endingIPAddress')]" + } + ], + "dnsServers": "[parameters('dnsServers')]" + } + ], + "physicalNodes": "[parameters('physicalNodesSettings')]", + "hostNetwork": { + "intents": "[parameters('intentList')]", + "storageNetworks": "[parameters('storageNetworkList')]", + "storageConnectivitySwitchless": "[parameters('storageConnectivitySwitchless')]" + }, + "adouPath": "[parameters('adouPath')]", + "secretsLocation": "[parameters('secretsLocation')]", + "optionalServices": { + "customLocation": "[parameters('customLocation')]" + } + } + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/azure_jumpstart_hcibox/artifacts/hci.parameters.json b/azure_jumpstart_hcibox/artifacts/hci.parameters.json new file mode 100644 index 0000000000..0ee2983ffb --- /dev/null +++ b/azure_jumpstart_hcibox/artifacts/hci.parameters.json @@ -0,0 +1,150 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "apiVersion": { + "value": "2023-08-01-preview" + }, + "clusterName": { + "value": "clusterName-staging" + }, + "arcNodeResourceIds": { + "value": arcNodeResourceIds-staging + }, + "localAdminSecretValue": { + "value": "localAdminSecretValue-staging" + }, + "domainAdminSecretValue": { + "value": "domainAdminSecretValue-staging" + }, + "arbDeploymentSpnValue": { + "value": "arbDeploymentSpnValue-staging" + }, + "storageWitnessValue": { + "value": "storageWitnessValue-staging" + }, + "domainFqdn": { + "value": "domainFqdn-staging" + }, + "namingPrefix": { + "value": "namingPrefix-staging" + }, + "keyVaultName": { + "value": "keyVaultName-staging" + }, + "ClusterWitnessStorageAccountName": { + "value": "ClusterWitnessStorageAccountName-staging" + }, + "diagnosticStorageAccountName": { + "value": "diagnosticStorageAccountName-staging" + }, + "adouPath": { + "value": "adouPath-staging" + }, + "secretsLocation": { + "value": "secretsLocation-staging" + }, + "subnetMask": { + "value": "subnetMask-staging" + }, + "defaultGateway": { + "value": "defaultGateway-staging" + }, + "startingIPAddress": { + "value": "startingIp-staging" + }, + "endingIPAddress": { + "value": "endingIp-staging" + }, + "dnsServers": { + "value": dnsServers-staging + }, + "physicalNodesSettings": { + "value": physicalNodesSettings-staging + }, + "storageConnectivitySwitchless": { + "value": false + }, + "intentList": { + "value": [ + { + "name": "HCI", + "trafficType": [ + "Management", + "Compute" + ], + "adapter": [ + "FABRIC" + ], + "overrideVirtualSwitchConfiguration": false, + "virtualSwitchConfigurationOverrides": { + "enableIov": "", + "loadBalancingAlgorithm": "" + }, + "overrideQosPolicy": false, + "qosPolicyOverrides": { + "priorityValue8021Action_Cluster": "7", + "priorityValue8021Action_SMB": "3", + "bandwidthPercentage_SMB": "50" + }, + "overrideAdapterProperty": true, + "adapterPropertyOverrides": { + "jumboPacket": "9014", + "networkDirect": "Disabled", + "networkDirectTechnology": "RoCEv2" + } + }, + { + "name": "Storage", + "trafficType": [ + "Storage" + ], + "adapter": [ + "StorageA", + "StorageB" + ], + "overrideVirtualSwitchConfiguration": false, + "virtualSwitchConfigurationOverrides": { + "enableIov": "", + "loadBalancingAlgorithm": "" + }, + "overrideQosPolicy": false, + "qosPolicyOverrides": { + "priorityValue8021Action_Cluster": "7", + "priorityValue8021Action_SMB": "3", + "bandwidthPercentage_SMB": "50" + }, + "overrideAdapterProperty": true, + "adapterPropertyOverrides": { + "jumboPacket": "9014", + "networkDirect": "Disabled", + "networkDirectTechnology": "iWARP" + } + } + ] + }, + "storageNetworkList": { + "value": [ + { + "name": "StorageNetwork1", + "networkAdapterName": "StorageA", + "vlanId": "storageNicAVLAN-staging" + }, + { + "name": "StorageNetwork2", + "networkAdapterName": "StorageB", + "vlanId": "storageNicBVLAN-staging" + } + ] + }, + "networkingType": { + "value": "switchlessMultiServerDeployment" + }, + "customLocation": { + "value": "customLocation-staging" + }, + "deploymentMode": { + "value": "Validate" + } + } +} \ No newline at end of file diff --git a/azure_jumpstart_hcibox/azure.yaml b/azure_jumpstart_hcibox/azure.yaml index e913731306..ebbfeb3d94 100644 --- a/azure_jumpstart_hcibox/azure.yaml +++ b/azure_jumpstart_hcibox/azure.yaml @@ -9,12 +9,24 @@ infra: module: "main.azd" hooks: preprovision: - shell: pwsh - run: ./scripts/preprovision.ps1 - continueOnError: false - interactive: true + windows: + shell: pwsh + run: ./hooks/preprovision.ps1 + continueOnError: false + interactive: true + posix: + shell: sh + run: ./hooks/preprovision.sh + continueOnError: false + interactive: true postprovision: - shell: pwsh - run: ./scripts/postprovision.ps1 - continueOnError: false - interactive: true + windows: + shell: pwsh + run: ./hooks/postprovision.ps1 + continueOnError: false + interactive: true + posix: + shell: sh + run: ./hooks/postprovision.sh + continueOnError: false + interactive: true diff --git a/azure_jumpstart_hcibox/bicep/host/host.bicep b/azure_jumpstart_hcibox/bicep/host/host.bicep index 48d5f7bb90..506998d48c 100644 --- a/azure_jumpstart_hcibox/bicep/host/host.bicep +++ b/azure_jumpstart_hcibox/bicep/host/host.bicep @@ -33,6 +33,9 @@ param spnClientSecret string @description('Tenant id of the service principal') param spnTenantId string +@description('Azure AD object id for your Microsoft.AzureStackHCI resource provider') +param spnProviderId string + @description('Name for the staging storage account using to hold kubeconfig. This value is passed into the template as an output from mgmtStagingStorage.json') param stagingStorageAccountName string @@ -60,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' @@ -246,9 +255,9 @@ resource vmBootstrap 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = autoUpgradeMinorVersion: true protectedSettings: { fileUris: [ - uri(templateBaseUrl, 'artifacts/Bootstrap.ps1') + 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} -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 d52beccddd..99ec11b666 100644 --- a/azure_jumpstart_hcibox/bicep/main.azd.bicep +++ b/azure_jumpstart_hcibox/bicep/main.azd.bicep @@ -4,17 +4,20 @@ param envName string @description('Azure service principal client id') -param spnClientId string +param spnClientId string = 'null' @description('Azure service principal client secret') @secure() -param spnClientSecret string +param spnClientSecret string = 'null' @description('Azure AD tenant id for your service principal') -param spnTenantId string +param spnTenantId string = 'null' + +@description('Azure AD object id for your Microsoft.AzureStackHCI resource provider') +param spnProviderId string ='null' @description('Username for Windows account') -param windowsAdminUsername string +param windowsAdminUsername string = 'arcdemo' @description('Password for Windows account. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. The value must be between 12 and 123 characters long') @minLength(12) @@ -25,15 +28,6 @@ param windowsAdminPassword string @description('Name for your log analytics workspace') param logAnalyticsWorkspaceName string = 'HCIBox-Workspace' -@description('Option to disable automatic cluster registration. Setting this to false will also disable deploying AKS and Resource bridge') -param registerCluster bool = true - -@description('Option to deploy AKS-HCI with HCIBox') -param deployAKSHCI bool = true - -@description('Option to deploy Resource Bridge with HCIBox') -param deployResourceBridge bool = true - @description('Public DNS to use for the domain') param natDNS string = '8.8.8.8' @@ -47,11 +41,17 @@ param githubBranch string = 'main' param deployBastion bool = false @description('Location to deploy resources') -@allowed(['eastus', 'eastus2', 'westus2', 'northeurope']) +@allowed(['eastus', 'eastus2', 'westus2', 'westeurope', 'australiaeast']) param location string @description('Override default RDP port using this parameter. Default is 3389.') -param rdpPort string +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/' @@ -97,17 +97,17 @@ module hostDeployment 'host/host.bicep' = { spnClientId: spnClientId spnClientSecret: spnClientSecret spnTenantId: spnTenantId + spnProviderId: spnProviderId workspaceName: logAnalyticsWorkspaceName stagingStorageAccountName: storageAccountDeployment.outputs.storageAccountName templateBaseUrl: templateBaseUrl subnetId: networkDeployment.outputs.subnetId deployBastion: deployBastion - registerCluster: registerCluster - deployAKSHCI: deployAKSHCI - deployResourceBridge: deployResourceBridge 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 989eadab2e..0aefe617b5 100644 --- a/azure_jumpstart_hcibox/bicep/main.azd.parameters.json +++ b/azure_jumpstart_hcibox/bicep/main.azd.parameters.json @@ -17,6 +17,9 @@ "spnTenantId": { "value": "${SPN_TENANT_ID}" }, + "spnProviderId": { + "value": "${SPN_PROVIDER_ID}" + }, "windowsAdminUsername": { "value": "${JS_WINDOWS_ADMIN_USERNAME}" }, @@ -24,16 +27,13 @@ "value": "${JS_RDP_PORT}" }, "deployBastion": { - "value": false - }, - "registerCluster": { - "value": true + "value": "${JS_DEPLOY_BASTION}" }, - "deployResourceBridge": { - "value": true + "autoDeployClusterResource": { + "value": "${JS_AUTO_DEPLOY_CLUSTER_RESOURCE}" }, - "deployAKSHCI": { - "value": true + "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 7b2dc1169f..3c4c25e4c2 100644 --- a/azure_jumpstart_hcibox/bicep/main.bicep +++ b/azure_jumpstart_hcibox/bicep/main.bicep @@ -8,6 +8,9 @@ param spnClientSecret string @description('Azure AD tenant id for your service principal') param spnTenantId string +@description('Azure AD object id for your Microsoft.AzureStackHCI resource provider') +param spnProviderId string + @description('Username for Windows account') param windowsAdminUsername string @@ -20,15 +23,6 @@ param windowsAdminPassword string @description('Name for your log analytics workspace') param logAnalyticsWorkspaceName string -@description('Option to disable automatic cluster registration. Setting this to false will also disable deploying AKS and Resource bridge') -param registerCluster bool = true - -@description('Option to deploy AKS-HCI with HCIBox') -param deployAKSHCI bool = true - -@description('Option to deploy Resource Bridge with HCIBox') -param deployResourceBridge bool = true - @description('Public DNS to use for the domain') param natDNS string = '8.8.8.8' @@ -47,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' = { @@ -80,16 +80,16 @@ module hostDeployment 'host/host.bicep' = { spnClientId: spnClientId spnClientSecret: spnClientSecret spnTenantId: spnTenantId + spnProviderId: spnProviderId workspaceName: logAnalyticsWorkspaceName stagingStorageAccountName: storageAccountDeployment.outputs.storageAccountName templateBaseUrl: templateBaseUrl subnetId: networkDeployment.outputs.subnetId deployBastion: deployBastion - registerCluster: registerCluster - deployAKSHCI: deployAKSHCI - deployResourceBridge: deployResourceBridge natDNS: natDNS location: location rdpPort: rdpPort + autoDeployClusterResource: autoDeployClusterResource + autoUpgradeClusterResource: autoUpgradeClusterResource } } diff --git a/azure_jumpstart_hcibox/bicep/main.json b/azure_jumpstart_hcibox/bicep/main.json deleted file mode 100644 index 5837fd8c4c..0000000000 --- a/azure_jumpstart_hcibox/bicep/main.json +++ /dev/null @@ -1,1027 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.12.40.16777", - "templateHash": "5463622317411077072" - } - }, - "parameters": { - "spnClientId": { - "type": "string", - "metadata": { - "description": "Azure service principal client id" - } - }, - "spnClientSecret": { - "type": "secureString", - "metadata": { - "description": "Azure service principal client secret" - } - }, - "spnTenantId": { - "type": "string", - "metadata": { - "description": "Azure AD tenant id for your service principal" - } - }, - "windowsAdminUsername": { - "type": "string", - "metadata": { - "description": "Username for Windows account" - } - }, - "windowsAdminPassword": { - "type": "secureString", - "maxLength": 123, - "minLength": 12, - "metadata": { - "description": "Password for Windows account. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. The value must be between 12 and 123 characters long" - } - }, - "logAnalyticsWorkspaceName": { - "type": "string", - "metadata": { - "description": "Name for your log analytics workspace" - } - }, - "registerCluster": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Option to disable automatic cluster registration. Setting this to false will also disable deploying AKS and Resource bridge" - } - }, - "deployAKSHCI": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Option to deploy AKS-HCI with HCIBox" - } - }, - "deployResourceBridge": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Option to deploy Resource Bridge with HCIBox" - } - }, - "natDNS": { - "type": "string", - "defaultValue": "8.8.8.8", - "metadata": { - "description": "Public DNS to use for the domain" - } - }, - "githubAccount": { - "type": "string", - "defaultValue": "microsoft", - "metadata": { - "description": "Target GitHub account" - } - }, - "githubBranch": { - "type": "string", - "defaultValue": "main", - "metadata": { - "description": "Target GitHub branch" - } - }, - "deployBastion": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Choice to deploy Bastion to connect to the client VM" - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Location to deploy resources" - } - } - }, - "variables": { - "templateBaseUrl": "[format('https://raw.githubusercontent.com/{0}/azure_arc/{1}/azure_jumpstart_hcibox/', parameters('githubAccount'), parameters('githubBranch'))]" - }, - "resources": [ - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", - "name": "mgmtArtifactsAndPolicyDeployment", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "workspaceName": { - "value": "[parameters('logAnalyticsWorkspaceName')]" - }, - "location": { - "value": "[parameters('location')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.12.40.16777", - "templateHash": "15958021173320564523" - } - }, - "parameters": { - "workspaceName": { - "type": "string", - "metadata": { - "description": "Name for your log analytics workspace" - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Azure Region to deploy the Log Analytics Workspace" - } - }, - "sku": { - "type": "string", - "defaultValue": "pergb2018", - "metadata": { - "description": "SKU, leave default pergb2018" - } - } - }, - "variables": { - "security": { - "name": "[format('Security({0})', parameters('workspaceName'))]", - "galleryName": "Security" - }, - "automationAccountName": "[format('HCIBox-Automation-{0}', uniqueString(resourceGroup().id))]", - "automationAccountLocation": "[if(equals(parameters('location'), 'eastus'), 'eastus2', if(equals(parameters('location'), 'eastus2'), 'eastus', parameters('location')))]" - }, - "resources": [ - { - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2021-06-01", - "name": "[parameters('workspaceName')]", - "location": "[parameters('location')]", - "properties": { - "sku": { - "name": "[parameters('sku')]" - } - } - }, - { - "type": "Microsoft.OperationsManagement/solutions", - "apiVersion": "2015-11-01-preview", - "name": "[format('VMInsights({0})', split(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName')), '/')[8])]", - "location": "[parameters('location')]", - "properties": { - "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]" - }, - "plan": { - "name": "[format('VMInsights({0})', split(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName')), '/')[8])]", - "product": "OMSGallery/VMInsights", - "promotionCode": "", - "publisher": "Microsoft" - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]" - ] - }, - { - "type": "Microsoft.OperationsManagement/solutions", - "apiVersion": "2015-11-01-preview", - "name": "[variables('security').name]", - "location": "[parameters('location')]", - "properties": { - "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]" - }, - "plan": { - "name": "[variables('security').name]", - "promotionCode": "", - "product": "[format('OMSGallery/{0}', variables('security').galleryName)]", - "publisher": "Microsoft" - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]" - ] - }, - { - "type": "Microsoft.Automation/automationAccounts", - "apiVersion": "2021-06-22", - "name": "[variables('automationAccountName')]", - "location": "[variables('automationAccountLocation')]", - "properties": { - "sku": { - "name": "Basic" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]" - ] - }, - { - "type": "Microsoft.OperationalInsights/workspaces/linkedServices", - "apiVersion": "2020-08-01", - "name": "[format('{0}/{1}', parameters('workspaceName'), 'Automation')]", - "properties": { - "resourceId": "[resourceId('Microsoft.Automation/automationAccounts', variables('automationAccountName'))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.Automation/automationAccounts', variables('automationAccountName'))]", - "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]" - ] - } - ] - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", - "name": "networkDeployment", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "deployBastion": { - "value": "[parameters('deployBastion')]" - }, - "location": { - "value": "[parameters('location')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.12.40.16777", - "templateHash": "12002154451255072834" - } - }, - "parameters": { - "virtualNetworkName": { - "type": "string", - "defaultValue": "HCIBox-VNet", - "metadata": { - "description": "Name of the VNet" - } - }, - "subnetName": { - "type": "string", - "defaultValue": "HCIBox-Subnet", - "metadata": { - "description": "Name of the subnet in the virtual network" - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Azure Region to deploy the Log Analytics Workspace" - } - }, - "deployBastion": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Choice to deploy Bastion to connect to the client VM" - } - }, - "networkSecurityGroupName": { - "type": "string", - "defaultValue": "HCIBox-NSG", - "metadata": { - "description": "Name of the Network Security Group" - } - }, - "bastionNetworkSecurityGroupName": { - "type": "string", - "defaultValue": "HCIBox-Bastion-NSG", - "metadata": { - "description": "Name of the Bastion Network Security Group" - } - } - }, - "variables": { - "addressPrefix": "172.16.0.0/16", - "subnetAddressPrefix": "172.16.1.0/24", - "bastionSubnetName": "AzureBastionSubnet", - "bastionSubnetRef": "[format('{0}/subnets/{1}', resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName')), variables('bastionSubnetName'))]", - "bastionName": "HCIBox-Bastion", - "bastionSubnetIpPrefix": "172.16.3.64/26", - "bastionPublicIpAddressName": "[format('{0}-PIP', variables('bastionName'))]" - }, - "resources": [ - { - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2021-03-01", - "name": "[parameters('virtualNetworkName')]", - "location": "[parameters('location')]", - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[variables('addressPrefix')]" - ] - }, - "subnets": "[if(equals(parameters('deployBastion'), true()), createArray(createObject('name', parameters('subnetName'), 'properties', createObject('addressPrefix', variables('subnetAddressPrefix'), 'privateEndpointNetworkPolicies', 'Enabled', 'privateLinkServiceNetworkPolicies', 'Enabled', 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))))), createObject('name', 'AzureBastionSubnet', 'properties', createObject('addressPrefix', variables('bastionSubnetIpPrefix'), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', parameters('bastionNetworkSecurityGroupName')))))), createArray(createObject('name', parameters('subnetName'), 'properties', createObject('addressPrefix', variables('subnetAddressPrefix'), 'privateEndpointNetworkPolicies', 'Enabled', 'privateLinkServiceNetworkPolicies', 'Enabled', 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName')))))))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('bastionNetworkSecurityGroupName'))]", - "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" - ] - }, - { - "type": "Microsoft.Network/networkSecurityGroups", - "apiVersion": "2021-03-01", - "name": "[parameters('networkSecurityGroupName')]", - "location": "[parameters('location')]", - "properties": { - "securityRules": [] - } - }, - { - "condition": "[equals(parameters('deployBastion'), true())]", - "type": "Microsoft.Network/networkSecurityGroups", - "apiVersion": "2021-05-01", - "name": "[parameters('bastionNetworkSecurityGroupName')]", - "location": "[parameters('location')]", - "properties": { - "securityRules": [ - { - "name": "bastion_allow_https_inbound", - "properties": { - "priority": 1010, - "protocol": "Tcp", - "access": "Allow", - "direction": "Inbound", - "sourceAddressPrefix": "Internet", - "sourcePortRange": "*", - "destinationAddressPrefix": "*", - "destinationPortRange": "443" - } - }, - { - "name": "bastion_allow_gateway_manager_inbound", - "properties": { - "priority": 1011, - "protocol": "Tcp", - "access": "Allow", - "direction": "Inbound", - "sourceAddressPrefix": "GatewayManager", - "sourcePortRange": "*", - "destinationAddressPrefix": "*", - "destinationPortRange": "443" - } - }, - { - "name": "bastion_allow_load_balancer_inbound", - "properties": { - "priority": 1012, - "protocol": "Tcp", - "access": "Allow", - "direction": "Inbound", - "sourceAddressPrefix": "AzureLoadBalancer", - "sourcePortRange": "*", - "destinationAddressPrefix": "*", - "destinationPortRange": "443" - } - }, - { - "name": "bastion_allow_host_comms", - "properties": { - "priority": 1013, - "protocol": "*", - "access": "Allow", - "direction": "Inbound", - "sourceAddressPrefix": "VirtualNetwork", - "sourcePortRange": "*", - "destinationAddressPrefix": "VirtualNetwork", - "destinationPortRanges": [ - "8080", - "5701" - ] - } - }, - { - "name": "bastion_allow_ssh_rdp_outbound", - "properties": { - "priority": 1014, - "protocol": "*", - "access": "Allow", - "direction": "Outbound", - "sourceAddressPrefix": "*", - "sourcePortRange": "*", - "destinationAddressPrefix": "VirtualNetwork", - "destinationPortRanges": [ - "22", - "3389" - ] - } - }, - { - "name": "bastion_allow_azure_cloud_outbound", - "properties": { - "priority": 1015, - "protocol": "Tcp", - "access": "Allow", - "direction": "Outbound", - "sourceAddressPrefix": "*", - "sourcePortRange": "*", - "destinationAddressPrefix": "AzureCloud", - "destinationPortRange": "443" - } - }, - { - "name": "bastion_allow_bastion_comms", - "properties": { - "priority": 1016, - "protocol": "*", - "access": "Allow", - "direction": "Outbound", - "sourceAddressPrefix": "VirtualNetwork", - "sourcePortRange": "*", - "destinationAddressPrefix": "VirtualNetwork", - "destinationPortRanges": [ - "8080", - "5701" - ] - } - }, - { - "name": "bastion_allow_get_session_info", - "properties": { - "priority": 1017, - "protocol": "*", - "access": "Allow", - "direction": "Outbound", - "sourceAddressPrefix": "*", - "sourcePortRange": "*", - "destinationAddressPrefix": "Internet", - "destinationPortRanges": [ - "80", - "443" - ] - } - } - ] - } - }, - { - "condition": "[equals(parameters('deployBastion'), true())]", - "type": "Microsoft.Network/publicIPAddresses", - "apiVersion": "2021-05-01", - "name": "[variables('bastionPublicIpAddressName')]", - "location": "[parameters('location')]", - "properties": { - "publicIPAllocationMethod": "Static", - "publicIPAddressVersion": "IPv4", - "idleTimeoutInMinutes": 4 - }, - "sku": { - "name": "Standard" - } - }, - { - "condition": "[equals(parameters('deployBastion'), true())]", - "type": "Microsoft.Network/bastionHosts", - "apiVersion": "2021-05-01", - "name": "[variables('bastionName')]", - "location": "[parameters('location')]", - "properties": { - "ipConfigurations": [ - { - "name": "IpConf", - "properties": { - "publicIPAddress": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('bastionPublicIpAddressName'))]" - }, - "subnet": { - "id": "[variables('bastionSubnetRef')]" - } - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]", - "[resourceId('Microsoft.Network/publicIPAddresses', variables('bastionPublicIpAddressName'))]" - ] - } - ], - "outputs": { - "vnetId": { - "type": "string", - "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]" - }, - "subnetId": { - "type": "string", - "value": "[reference(resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName')), '2021-03-01').subnets[0].id]" - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", - "name": "stagingStorageAccountDeployment", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "location": { - "value": "[parameters('location')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.12.40.16777", - "templateHash": "8234938478342075972" - } - }, - "parameters": { - "storageAccountType": { - "type": "string", - "defaultValue": "Standard_LRS", - "allowedValues": [ - "Standard_LRS", - "Standard_GRS", - "Standard_ZRS", - "Premium_LRS" - ], - "metadata": { - "description": "Storage Account type" - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Location for all resources." - } - } - }, - "variables": { - "storageAccountName": "[format('hcibox{0}', uniqueString(resourceGroup().id))]" - }, - "resources": [ - { - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2021-06-01", - "name": "[variables('storageAccountName')]", - "location": "[parameters('location')]", - "sku": { - "name": "[parameters('storageAccountType')]" - }, - "kind": "StorageV2", - "properties": { - "supportsHttpsTrafficOnly": true - } - } - ], - "outputs": { - "storageAccountName": { - "type": "string", - "value": "[variables('storageAccountName')]" - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2020-10-01", - "name": "hostVmDeployment", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "windowsAdminUsername": { - "value": "[parameters('windowsAdminUsername')]" - }, - "windowsAdminPassword": { - "value": "[parameters('windowsAdminPassword')]" - }, - "spnClientId": { - "value": "[parameters('spnClientId')]" - }, - "spnClientSecret": { - "value": "[parameters('spnClientSecret')]" - }, - "spnTenantId": { - "value": "[parameters('spnTenantId')]" - }, - "workspaceName": { - "value": "[parameters('logAnalyticsWorkspaceName')]" - }, - "stagingStorageAccountName": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'stagingStorageAccountDeployment'), '2020-10-01').outputs.storageAccountName.value]" - }, - "templateBaseUrl": { - "value": "[variables('templateBaseUrl')]" - }, - "subnetId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'networkDeployment'), '2020-10-01').outputs.subnetId.value]" - }, - "deployBastion": { - "value": "[parameters('deployBastion')]" - }, - "registerCluster": { - "value": "[parameters('registerCluster')]" - }, - "deployAKSHCI": { - "value": "[parameters('deployAKSHCI')]" - }, - "deployResourceBridge": { - "value": "[parameters('deployResourceBridge')]" - }, - "natDNS": { - "value": "[parameters('natDNS')]" - }, - "location": { - "value": "[parameters('location')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.12.40.16777", - "templateHash": "7350178746438702927" - } - }, - "parameters": { - "vmName": { - "type": "string", - "defaultValue": "HCIBox-Client", - "metadata": { - "description": "The name of your Virtual Machine" - } - }, - "windowsAdminUsername": { - "type": "string", - "defaultValue": "arcdemo", - "metadata": { - "description": "Username for the Virtual Machine" - } - }, - "windowsAdminPassword": { - "type": "secureString", - "maxLength": 123, - "minLength": 12, - "metadata": { - "description": "Password for Windows account. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. The value must be between 12 and 123 characters long" - } - }, - "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" - } - }, - "subnetId": { - "type": "string", - "metadata": { - "description": "Resource Id of the subnet in the virtual network" - } - }, - "resourceTags": { - "type": "object", - "defaultValue": { - "Project": "jumpstart_HCIBox" - } - }, - "spnClientId": { - "type": "string", - "metadata": { - "description": "Client id of the service principal" - } - }, - "spnClientSecret": { - "type": "secureString", - "metadata": { - "description": "Client secret of the service principal" - } - }, - "spnTenantId": { - "type": "string", - "metadata": { - "description": "Tenant id of the service principal" - } - }, - "stagingStorageAccountName": { - "type": "string", - "metadata": { - "description": "Name for the staging storage account using to hold kubeconfig. This value is passed into the template as an output from mgmtStagingStorage.json" - } - }, - "workspaceName": { - "type": "string", - "metadata": { - "description": "Name for the environment Azure Log Analytics workspace" - } - }, - "templateBaseUrl": { - "type": "string", - "metadata": { - "description": "The base URL used for accessing artifacts and automation artifacts." - } - }, - "registerCluster": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Option to disable automatic cluster registration. Setting this to false will also disable deploying AKS and Resource bridge" - } - }, - "deployBastion": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Choice to deploy Bastion to connect to the client VM" - } - }, - "deployAKSHCI": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Option to deploy AKS-HCI with HCIBox" - } - }, - "deployResourceBridge": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Option to deploy Resource Bridge with HCIBox" - } - }, - "natDNS": { - "type": "string", - "defaultValue": "8.8.8.8", - "metadata": { - "description": "Public DNS to use for the domain" - } - } - }, - "variables": { - "encodedPassword": "[base64(parameters('windowsAdminPassword'))]", - "bastionName": "HCIBox-Bastion", - "publicIpAddressName": "[if(equals(parameters('deployBastion'), false()), format('{0}-PIP', parameters('vmName')), format('{0}-PIP', variables('bastionName')))]", - "networkInterfaceName": "[format('{0}-NIC', parameters('vmName'))]", - "osDiskType": "Premium_LRS", - "PublicIPNoBastion": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIpAddressName'))]" - } - }, - "resources": [ - { - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2021-03-01", - "name": "[variables('networkInterfaceName')]", - "location": "[parameters('location')]", - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "subnet": { - "id": "[parameters('subnetId')]" - }, - "privateIPAllocationMethod": "Dynamic", - "publicIPAddress": "[if(equals(parameters('deployBastion'), false()), variables('PublicIPNoBastion'), json('null'))]" - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIpAddressName'))]" - ] - }, - { - "condition": "[equals(parameters('deployBastion'), false())]", - "type": "Microsoft.Network/publicIPAddresses", - "apiVersion": "2021-03-01", - "name": "[variables('publicIpAddressName')]", - "location": "[parameters('location')]", - "properties": { - "publicIPAllocationMethod": "Static", - "publicIPAddressVersion": "IPv4", - "idleTimeoutInMinutes": 4 - }, - "sku": { - "name": "Basic" - } - }, - { - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2022-03-01", - "name": "[parameters('vmName')]", - "location": "[parameters('location')]", - "tags": "[parameters('resourceTags')]", - "properties": { - "hardwareProfile": { - "vmSize": "Standard_D48s_v5" - }, - "storageProfile": { - "osDisk": { - "name": "[format('{0}-OSDisk', parameters('vmName'))]", - "caching": "ReadWrite", - "createOption": "FromImage", - "managedDisk": { - "storageAccountType": "[variables('osDiskType')]" - }, - "diskSizeGB": 1024 - }, - "imageReference": { - "publisher": "MicrosoftWindowsServer", - "offer": "WindowsServer", - "sku": "[parameters('windowsOSVersion')]", - "version": "latest" - }, - "dataDisks": [ - { - "name": "ASHCIHost001_DataDisk_0", - "diskSizeGB": 256, - "createOption": "Empty", - "lun": 0, - "caching": "None", - "writeAcceleratorEnabled": false, - "managedDisk": { - "storageAccountType": "Premium_LRS" - } - }, - { - "name": "ASHCIHost001_DataDisk_1", - "diskSizeGB": 256, - "createOption": "Empty", - "lun": 1, - "caching": "None", - "writeAcceleratorEnabled": false, - "managedDisk": { - "storageAccountType": "Premium_LRS" - } - }, - { - "name": "ASHCIHost001_DataDisk_2", - "diskSizeGB": 256, - "createOption": "Empty", - "lun": 2, - "caching": "None", - "writeAcceleratorEnabled": false, - "managedDisk": { - "storageAccountType": "Premium_LRS" - } - }, - { - "name": "ASHCIHost001_DataDisk_3", - "diskSizeGB": 256, - "createOption": "Empty", - "lun": 3, - "caching": "None", - "writeAcceleratorEnabled": false, - "managedDisk": { - "storageAccountType": "Premium_LRS" - } - }, - { - "name": "ASHCIHost001_DataDisk_4", - "diskSizeGB": 256, - "createOption": "Empty", - "lun": 4, - "caching": "None", - "writeAcceleratorEnabled": false, - "managedDisk": { - "storageAccountType": "Premium_LRS" - } - }, - { - "name": "ASHCIHost001_DataDisk_5", - "diskSizeGB": 256, - "createOption": "Empty", - "lun": 5, - "caching": "None", - "writeAcceleratorEnabled": false, - "managedDisk": { - "storageAccountType": "Premium_LRS" - } - }, - { - "name": "ASHCIHost001_DataDisk_6", - "diskSizeGB": 256, - "createOption": "Empty", - "lun": 6, - "caching": "None", - "writeAcceleratorEnabled": false, - "managedDisk": { - "storageAccountType": "Premium_LRS" - } - }, - { - "name": "ASHCIHost001_DataDisk_7", - "diskSizeGB": 256, - "createOption": "Empty", - "lun": 7, - "caching": "None", - "writeAcceleratorEnabled": false, - "managedDisk": { - "storageAccountType": "Premium_LRS" - } - } - ] - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" - } - ] - }, - "osProfile": { - "computerName": "[parameters('vmName')]", - "adminUsername": "[parameters('windowsAdminUsername')]", - "adminPassword": "[parameters('windowsAdminPassword')]", - "windowsConfiguration": { - "provisionVMAgent": true, - "enableAutomaticUpdates": false - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" - ] - }, - { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2022-03-01", - "name": "[format('{0}/{1}', parameters('vmName'), 'Bootstrap')]", - "location": "[parameters('location')]", - "properties": { - "publisher": "Microsoft.Compute", - "type": "CustomScriptExtension", - "typeHandlerVersion": "1.10", - "autoUpgradeMinorVersion": true, - "settings": { - "fileUris": [ - "[uri(parameters('templateBaseUrl'), 'artifacts/Bootstrap.ps1')]" - ], - "commandToExecute": "[format('powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername {0} -adminPassword {1} -spnClientId {2} -spnClientSecret {3} -spnTenantId {4} -subscriptionId {5} -resourceGroup {6} -azureLocation {7} -stagingStorageAccountName {8} -workspaceName {9} -templateBaseUrl {10} -registerCluster {11} -deployAKSHCI {12} -deployResourceBridge {13} -natDNS {14}', parameters('windowsAdminUsername'), variables('encodedPassword'), parameters('spnClientId'), parameters('spnClientSecret'), parameters('spnTenantId'), subscription().subscriptionId, resourceGroup().name, parameters('location'), parameters('stagingStorageAccountName'), parameters('workspaceName'), parameters('templateBaseUrl'), parameters('registerCluster'), parameters('deployAKSHCI'), parameters('deployResourceBridge'), parameters('natDNS'))]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]" - ] - } - ], - "outputs": { - "adminUsername": { - "type": "string", - "value": "[parameters('windowsAdminUsername')]" - }, - "publicIP": { - "type": "string", - "value": "[if(equals(parameters('deployBastion'), false()), concat(reference(resourceId('Microsoft.Network/publicIPAddresses', variables('publicIpAddressName')), '2021-03-01').ipAddress), '')]" - }, - "base64Output": { - "type": "string", - "value": "[variables('encodedPassword')]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', 'networkDeployment')]", - "[resourceId('Microsoft.Resources/deployments', 'stagingStorageAccountDeployment')]" - ] - } - ] -} \ No newline at end of file diff --git a/azure_jumpstart_hcibox/bicep/main.parameters.json b/azure_jumpstart_hcibox/bicep/main.parameters.json index 4f90587caf..4f19e63e35 100644 --- a/azure_jumpstart_hcibox/bicep/main.parameters.json +++ b/azure_jumpstart_hcibox/bicep/main.parameters.json @@ -11,6 +11,9 @@ "spnTenantId": { "value": "<your service principal tenant id>" }, + "spnProviderId": { + "value": "<your Microsoft.AzureStackHCI resource provider object id>" + }, "windowsAdminUsername": { "value": "arcdemo" }, @@ -23,14 +26,11 @@ "deployBastion": { "value": false }, - "registerCluster": { - "value": true - }, - "deployResourceBridge": { - "value": true + "autoDeployClusterResource": { + "value": false }, - "deployAKSHCI": { - "value": true + "autoUpgradeClusterResource": { + "value": false } } -} \ No newline at end of file +} diff --git a/azure_jumpstart_hcibox/scripts/postprovision.ps1 b/azure_jumpstart_hcibox/hooks/postprovision.ps1 similarity index 72% rename from azure_jumpstart_hcibox/scripts/postprovision.ps1 rename to azure_jumpstart_hcibox/hooks/postprovision.ps1 index 2d74453b0e..339f43e60b 100644 --- a/azure_jumpstart_hcibox/scripts/postprovision.ps1 +++ b/azure_jumpstart_hcibox/hooks/postprovision.ps1 @@ -31,8 +31,9 @@ If ($rdpPort -ne "3389") { # Client VM IP address -$ip = (Get-AzPublicIpAddress -ResourceGroupName $resourceGroup -Name "HCIBox-Client-PIP").IpAddress - -Write-Host "You can now connect to the client VM using the following command: " -NoNewline -WRite-Host "mstsc /v:$($ip):$($rdpPort)" -ForegroundColor Green -BackgroundColor Black -Write-Host "Remember to use the Windows admin user name [$env:JS_WINDOWS_ADMIN_USERNAME] and the password you specified." \ No newline at end of file +$ip = (Get-AzPublicIpAddress -ResourceGroupName $resourceGroup -Name "HCIBox-Client-PIP" -ErrorAction SilentlyContinue).IpAddress | Out-Null +if ($null -ne $ip) { + Write-Host "You can now connect to the client VM using the following command: " -NoNewline + Write-Host "mstsc /v:$($ip):$($rdpPort)" -ForegroundColor Green -BackgroundColor Black + Write-Host "Remember to use the Windows admin user name [$env:JS_WINDOWS_ADMIN_USERNAME] and the password you specified." +} \ No newline at end of file diff --git a/azure_jumpstart_hcibox/hooks/postprovision.sh b/azure_jumpstart_hcibox/hooks/postprovision.sh new file mode 100755 index 0000000000..9b156134a9 --- /dev/null +++ b/azure_jumpstart_hcibox/hooks/postprovision.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Configure NSG Rule for RDP (if needed) +if [ "$JS_RDP_PORT" != "3389" ]; then + echo "Configuring NSG Rule for RDP..." + az network nsg rule create \ + --resource-group $AZURE_RESOURCE_GROUP \ + --nsg-name HCIBox-NSG \ + --name "RDP-$JS_RDP_PORT" \ + --description "Allow RDP" \ + --access Allow \ + --protocol Tcp \ + --direction Inbound \ + --priority 100 \ + --source-address-prefixes '*' \ + --source-port-ranges '*' \ + --destination-address-prefixes '*' \ + --destination-port-ranges $JS_RDP_PORT \ + --output none +fi + +# Client VM IP address +ip=$(az network public-ip show --resource-group $AZURE_RESOURCE_GROUP --name "HCIBox-Client-PIP" --query ipAddress --output tsv 2>/dev/null) +if [ -n "$ip" ]; then + echo -e "You can now connect to the client VM using the following command: \033[0;32mmstsc /v:$ip:$rdpPort\033[0m" + echo "Remember to use the Windows admin user name [$JS_WINDOWS_ADMIN_USERNAME] and the password you specified." +fi \ No newline at end of file diff --git a/azure_jumpstart_hcibox/scripts/preprovision.ps1 b/azure_jumpstart_hcibox/hooks/preprovision.ps1 similarity index 60% rename from azure_jumpstart_hcibox/scripts/preprovision.ps1 rename to azure_jumpstart_hcibox/hooks/preprovision.ps1 index 85f2654f24..57d1d58987 100644 --- a/azure_jumpstart_hcibox/scripts/preprovision.ps1 +++ b/azure_jumpstart_hcibox/hooks/preprovision.ps1 @@ -12,19 +12,25 @@ if (-not (Get-Command -Name Get-AzContext)) { # If not signed in, run the Connect-AzAccount cmdlet if (-not (Get-AzContext)) { - Write-Host "Logging in to Azure..." + Write-Host "Logging in to Azure with subscription id $env:AZURE_SUBSCRIPTION_ID" If (-not (Connect-AzAccount -SubscriptionId $env:AZURE_SUBSCRIPTION_ID -ErrorAction Stop)){ Throw "Unable to login to Azure. Please check your credentials and try again." } } +$tenantId = (Get-AzContext).tenant.id +Write-Host "Setting Azure context with subscription id $env:AZURE_SUBSCRIPTION_ID and tenant id $tenantId..." +$context = Set-AzContext -SubscriptionId $env:AZURE_SUBSCRIPTION_ID -ErrorAction Stop -# Write-Host "Getting Azure Tenant Id..." -$tenantId = (Get-AzSubscription -SubscriptionId $env:AZURE_SUBSCRIPTION_ID).TenantId +# Check if Azure CLI is authenticated to the same tenant as Azure PowerShell +$azCliTenantId = (az account show --query "tenantId" -o tsv) 2>&1 +if ($azCliTenantId -ne $tenantId) { + Write-Host "Azure CLI is not authenticated to the same tenant as Azure PowerShell - performing login..." -# Write-Host "Setting Azure context..." -$context = Set-AzContext -SubscriptionId $env:AZURE_SUBSCRIPTION_ID -Tenant $tenantId -ErrorAction Stop + az login --tenant $tenantId -# Write-Host "Setting az subscription..." +} + +Write-Host "Setting Azure CLI context to the same subscription as Azure PowerShell..." az account set --subscription $env:AZURE_SUBSCRIPTION_ID # Register providers @@ -49,8 +55,8 @@ Function Get-AzAvailableCores ($location, $skuFriendlyNames, $minCores = 0) { # using az command because there is currently a bug in various versions of PowerShell that affects Get-AzVMUsage $usage = (az vm list-usage --location $location --output json --only-show-errors) | ConvertFrom-Json - $usage = $usage | - Where-Object {$_.localname -match $skuFriendlyNames} + $usage = $usage | + Where-Object {$_.localname -match $skuFriendlyNames} $enhanced = $usage | ForEach-Object { @@ -63,7 +69,7 @@ Function Get-AzAvailableCores ($location, $skuFriendlyNames, $minCores = 0) { $_ | Add-Member -MemberType NoteProperty -Name usableLocation -Value $false -Force -PassThru If ($_.available -ge $minCores) { $_.usableLocation = $true - } + } else { $_.usableLocation = $false } @@ -83,13 +89,13 @@ Function Get-AzAvailableLocations ($location, $skuFriendlyNames, $minCores = 0) -and $_.PhysicalLocation -ne "" } - $usableLocations = $locations | + $usableLocations = $locations | ForEach-Object { $available = Get-AzAvailableCores -location $_.location -skuFriendlyNames $skuFriendlyNames -minCores $minCores | Where-Object {$_.localName -ne "Total Regional vCPUs"} If ($available.usableLocation) { - $_ | Add-Member -MemberType NoteProperty -Name TotalCores -Value $available.limit -Force - $_ | Add-Member -MemberType NoteProperty -Name AvailableCores -Value $available.available -Force + $_ | Add-Member -MemberType NoteProperty -Name TotalCores -Value $available.limit -Force + $_ | Add-Member -MemberType NoteProperty -Name AvailableCores -Value $available.available -Force $_ | Add-Member -MemberType NoteProperty -Name usableLocation -Value $available.usableLocation -Force -PassThru } } @@ -108,12 +114,12 @@ $available = Get-AzAvailableCores -location $location -skuFriendlyNames $skuFrie If ($available.usableLocation -contains $false) { Write-Host "`n`u{274C} There is not enough VM capacity in the $location region to deploy the Jumpstart environment." -ForegroundColor Red - + Write-Host "`nChecking other regions in the same geography with enough capacity ($minCores cores)...`n" - $locations = Get-AzAvailableLocations -location $location -skuFriendlyNames $skuFriendlyNames -minCores $minCores | + $locations = Get-AzAvailableLocations -location $location -skuFriendlyNames $skuFriendlyNames -minCores $minCores | Format-Table Location, DisplayName, TotalCores, AvailableCores, UsableLocation -AutoSize | Out-String - + Write-Host $locations Write-Host "Please run ``azd env --new`` to create a new environment and select the new location.`n" @@ -135,6 +141,18 @@ if ($promptOutput = Read-Host "Enter the Windows Admin Username [$JS_WINDOWS_ADM # set the env variable azd env set JS_WINDOWS_ADMIN_USERNAME -- $JS_WINDOWS_ADMIN_USERNAME +######################################################################## +# Use Azure Bastion? +######################################################################## +$promptOutput = Read-Host "Configure Azure Bastion for accessing HCIBox host [Y/N]?" +$JS_DEPLOY_BASTION = $false +if ($promptOutput -like 'y') +{ + $JS_DEPLOY_BASTION = $true +} + +# set the env variable +azd env set JS_DEPLOY_BASTION $JS_DEPLOY_BASTION ######################################################################## # RDP Port @@ -143,39 +161,58 @@ $JS_RDP_PORT = '3389' If ($env:JS_RDP_PORT) { $JS_RDP_PORT = $env:JS_RDP_PORT } -if ($promptOutput = Read-Host "Enter the RDP Port for remote desktop connection [$JS_RDP_PORT]") { $JS_RDP_PORT = $promptOutput } +if ($promptOutput -notlike 'y') { + if ($promptOutput = Read-Host "Enter the RDP Port for remote desktop connection [$JS_RDP_PORT]") + { + $JS_RDP_PORT = $promptOutput + } +} + # set the env variable 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) { + 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" +} ######################################################################## # Create Azure Service Principal ######################################################################## -Write-Host "Creating Azure Service Principal..." - -$user = $context.Account.Id.split("@")[0] -$uniqueSpnName = "$user-jumpstart-spn-$(Get-Random -Minimum 1000 -Maximum 9999)" -try { - $spn = New-AzADServicePrincipal -DisplayName $uniqueSpnName -Role "Owner" -Scope "/subscriptions/$($env:AZURE_SUBSCRIPTION_ID)" -ErrorAction Stop -} -catch { - If ($error[0].ToString() -match "Forbidden"){ - Throw "You do not have permission to create a service principal. Please contact your Azure subscription administrator to grant you the Owner role on the subscription." - } - elseif ($error[0].ToString() -match "credentials") { - Throw "Please run Connect-AzAccount to sign and run 'azd up' again." - } - else { - Throw "An error occurred creating the service principal. Please try again." +Write-Host "Checking for existing stored Azure service principal..." +if ($null -ne $env:SPN_CLIENT_ID) { + Write-Host "Re-using existing service principal..." +} else { + Write-Host "Attempting to create new service principal with scope /subscriptions/$($env:AZURE_SUBSCRIPTION_ID)..." + $user = (Get-AzContext).Account.Id.split("@")[0] + $uniqueSpnName = "$user-jumpstart-spn-$(Get-Random -Minimum 1000 -Maximum 9999)" + try { + $spn = New-AzADServicePrincipal -DisplayName $uniqueSpnName -Role "Owner" -Scope "/subscriptions/$($env:AZURE_SUBSCRIPTION_ID)" -ErrorAction Stop + $SPN_CLIENT_ID = $spn.AppId + $SPN_CLIENT_SECRET = $spn.PasswordCredentials.SecretText + $SPN_TENANT_ID = (Get-AzContext).Tenant.Id + # Set environment variables + azd env set SPN_CLIENT_ID -- $SPN_CLIENT_ID + azd env set SPN_CLIENT_SECRET -- $SPN_CLIENT_SECRET + azd env set SPN_TENANT_ID -- $SPN_TENANT_ID } -} + catch { -$SPN_CLIENT_ID = $spn.AppId -$SPN_CLIENT_SECRET = $spn.PasswordCredentials.SecretText -$SPN_TENANT_ID = (Get-AzContext).Tenant.Id + If ($error[0].ToString() -match "Forbidden"){ + Throw "You do not have permission to create a service principal. Please contact your Azure subscription administrator to grant you the Owner role on the subscription." + } + else { + Throw "An error occurred creating the service principal. Error:" + $error[0].ToString() + } + } -# Set environment variables -azd env set SPN_CLIENT_ID -- $SPN_CLIENT_ID -azd env set SPN_CLIENT_SECRET -- $SPN_CLIENT_SECRET -azd env set SPN_TENANT_ID -- $SPN_TENANT_ID +} diff --git a/azure_jumpstart_hcibox/hooks/preprovision.sh b/azure_jumpstart_hcibox/hooks/preprovision.sh new file mode 100755 index 0000000000..9005d37dfb --- /dev/null +++ b/azure_jumpstart_hcibox/hooks/preprovision.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +# Register providers +echo "Registering Azure providers..." +az provider register --namespace Microsoft.HybridCompute --wait +az provider register --namespace Microsoft.GuestConfiguration --wait +az provider register --namespace Microsoft.Kubernetes --wait +az provider register --namespace Microsoft.KubernetesConfiguration --wait +az provider register --namespace Microsoft.ExtendedLocation --wait +az provider register --namespace Microsoft.AzureArcData --wait +az provider register --namespace Microsoft.OperationsManagement --wait +az provider register --namespace Microsoft.AzureStackHCI --wait +az provider register --namespace Microsoft.ResourceConnector --wait +az provider register --namespace Microsoft.OperationalInsights --wait + +# check for available capacity +echo "Checking for available capacity in $AZURE_LOCATION region..." + +sku="Standard_E32s_v5" +family="standardESv5Family" +minCores=32 # 32 vCPUs required for standard deployment with E32s v5 +available=$(az vm list-skus --location $AZURE_LOCATION --all --query "[?family=='$family'].capabilities[0][?name=='vCPUs'].value" -o tsv) + +if [[ $available -lt $minCores ]]; then + echo "There is not enough VM capacity in the $location region to deploy the Jumpstart environment. Exiting..." + exit 1 +fi + +# check for sku restriction +restriction=$(az vm list-skus --location $AZURE_LOCATION --all --query "[?name=='$sku'].restrictions[0].reasonCode" -o tsv) +if [[ $restriction == "NotAvailableForSubscription" ]]; then + echo "There is a restriction in the $AZURE_LOCATION region to deploy the required VM SKU. Exiting..." + exit 1 +fi + + +JS_WINDOWS_ADMIN_USERNAME='arcdemo' +read -p "Enter the Windows Admin Username [$JS_WINDOWS_ADMIN_USERNAME]: " promptOutput + +if [ -n "$promptOutput" ]; then + JS_WINDOWS_ADMIN_USERNAME=$promptOutput +fi +# set the env variable + +azd env set JS_WINDOWS_ADMIN_USERNAME $JS_WINDOWS_ADMIN_USERNAME + +######################################################################## +# Use Azure Bastion? +######################################################################## +read -p "Configure Azure Bastion for accessing HCIBox host [Y/N]? " promptOutput +JS_DEPLOY_BASTION=false +if [[ $promptOutput == "Y" ]] || [[ $promptOutput == "y" ]]; then + JS_DEPLOY_BASTION=true +fi + +# set the env variable +azd env set JS_DEPLOY_BASTION $JS_DEPLOY_BASTION + +######################################################################## +# RDP Port +######################################################################## +JS_RDP_PORT='3389' +if [ -n "$JS_RDP_PORT" ]; then + JS_RDP_PORT=$JS_RDP_PORT +else + JS_RDP_PORT='3389' # Default value if not previously set +fi + +read -p "Enter the RDP Port for remote desktop connection [$JS_RDP_PORT]: " promptOutput + +if [[ -n "$promptOutput" ]]; then + JS_RDP_PORT=$promptOutput +fi + +azd env set JS_RDP_PORT $JS_RDP_PORT + +######################################################################## +# Microsoft.AzureStackHCI provider ID +######################################################################## +echo "Attempting to retrieve Microsoft.AzureStackHCI provider id..." +spnProviderId=$(az ad sp list --display-name "Microsoft.AzureStackHCI" --query [0].id -o tsv) +if [ -n "$spnProviderId" ]; then + # Set the environment variable + azd env set SPN_PROVIDER_ID $spnProviderId +else + # Print warning and advice + echo "Warning: Microsoft.AzureStackHCI provider id not found, aborting..." + echo "Consider the following options:" + echo "1) Request access from a tenant administrator to get read-permissions to service principals." + echo "2) Ask a tenant administrator to run the command 'az ad sp list --display-name \"Microsoft.AzureStackHCI\" --output json | jq -r '.[].id'' 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." + exit 1 +fi + +######################################################################## +# Create Azure Service Principal +######################################################################## +echo "Checking for existing stored Azure service principal..." +if [ -n "$SPN_CLIENT_ID" ]; then + echo "Using existing Azure service principal..." +else + echo "Creating Azure service principal..." + spn=$(az ad sp create-for-rbac --name "http://AzureArcJumpstart" --role "Owner" --scopes "/subscriptions/$AZURE_SUBSCRIPTION_ID") + spnClientId=$(echo $spn | jq -r .appId) + spnClientSecret=$(echo $spn | jq -r .password) + spnTenantId=$(echo $spn | jq -r .tenant) + spnObjectId=$(az ad sp show --id $spnClientId --query id -o tsv) + # Set the environment variables + azd env set SPN_CLIENT_ID $spnClientId + azd env set SPN_CLIENT_SECRET $spnClientSecret + azd env set SPN_TENANT_ID $spnTenantId + azd env set SPN_OBJECT_ID $spnObjectId +fi