├── .vscode
└── settings.json
├── CODE_OF_CONDUCT.md
├── ClassTypes
├── Bash
│ └── FedoraDockerContainer
│ │ ├── Dockerfile
│ │ ├── Images
│ │ ├── DockerCmd1.png
│ │ ├── DockerCmd2.png
│ │ ├── DockerCmd3.png
│ │ ├── DockerDiagram.png
│ │ ├── RemoteFedoraDesktop.png
│ │ ├── X2GoLogin.png
│ │ ├── X2GoNewSession.png
│ │ └── X2GoSessionPreferences.png
│ │ ├── README.md
│ │ └── entrypoint.sh
├── PowerShell
│ ├── BigDataAnalytics
│ │ ├── Hortonworks-Scheduled-Task.xml
│ │ ├── PowerShell-Run.png
│ │ ├── README.md
│ │ ├── Run-Task.png
│ │ ├── Start-Hortonworks-DockerContainers.ps1
│ │ └── Task-Scheduler.png
│ ├── EthicalHacking
│ │ ├── Create-EthicalHackingLabplanAndLab.ps1
│ │ ├── README.md
│ │ └── Setup-EthicalHacking.ps1
│ └── HyperV
│ │ ├── CheckHypervVm
│ │ ├── README.md
│ │ ├── Test-HypervVmConfig.ps1
│ │ ├── ethical-hacking-vm-config.json
│ │ ├── example-config.json
│ │ └── vm-config-schema.json
│ │ ├── README.md
│ │ └── SetupForNestedVirtualization.ps1
└── README.md
├── GeneralScripts
└── PowerShell
│ ├── BringImageToSharedImageGallery
│ ├── BringImageToSharedImageGallery.ps1
│ └── Readme.md
│ └── CustomPolicies
│ └── RemovePolicy.ps1
├── LICENSE
├── LabManagement
├── ARM
│ ├── BC_CompSci_AI_200_Schedule_Sample.csv
│ ├── BellowsCollegeLabs_Sample.csv
│ ├── Bulk_CreateLab_ARM.ps1
│ ├── LabTemplate_Sample.json
│ ├── LabTemplate_Simple_.json
│ ├── README.md
│ ├── SimpleArmDeploy.ps1
│ └── Utilities.psm1
└── PowerShell
│ ├── BulkOperations
│ ├── Az.LabServices.BulkOperations.psm1
│ ├── Examples
│ │ ├── AADGroupMembers.csv
│ │ ├── Add-AzLabRoleAssignments.ps1
│ │ ├── Add-LabUsers.ps1
│ │ ├── BC_CompSci_AI_200_Schedule_Sample.csv
│ │ ├── BellowsCollegeLabs_Sample.csv
│ │ ├── Create-AzLabPlans.ps1
│ │ ├── Create-AzLabs.ps1
│ │ ├── Create-Publish-AzLabs.ps1
│ │ ├── CustomRoleAssignments.csv
│ │ ├── Get-RegistrationLinks.ps1
│ │ ├── PickALab.ps1
│ │ ├── Publish-AzLabs.ps1
│ │ ├── PublishAll.ps1
│ │ ├── Remove-AzLabPlans.ps1
│ │ ├── Remove-AzLabs.ps1
│ │ ├── Remove-Students.ps1
│ │ ├── Reset-StudentAvailableHours.ps1
│ │ ├── Send-Invitations.ps1
│ │ └── Validate-AzLabs.ps1
│ └── Readme.md
│ ├── JITUserQuota
│ ├── JITQuotaWithManagedId.ps1
│ ├── JITQuotaWithRunAs.ps1
│ ├── JITQuotaWithScheduledTask.ps1
│ └── readme.md
│ └── Quota
│ ├── Measure-LabServicesNeededQuota.ps1
│ └── Readme.md
├── README.md
├── SECURITY.md
├── SUPPORT.md
└── TemplateManagement
├── Bash
└── LinuxGraphicalDesktopSetup
│ ├── GNOME_MATE
│ ├── ReadMe.md
│ └── Ubuntu
│ │ ├── x2go-mate.sh
│ │ └── xrdp-gnome.sh
│ └── XFCE_Xubuntu
│ ├── ReadMe.md
│ └── Ubuntu
│ ├── x2go-xfce4.sh
│ └── x2go-xubuntu.sh
└── PowerShell
└── GenericPreparation
├── Prepare-MicrosoftStoreApplications.ps1
├── Prepare-OneDrive.ps1
├── Prepare-Updates.ps1
└── Readme.md
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "Hortonworks",
4 | "runbook",
5 | "runbooks",
6 | "Xrdp",
7 | "Xubuntu"
8 | ]
9 | }
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/ClassTypes/Bash/FedoraDockerContainer/Dockerfile:
--------------------------------------------------------------------------------
1 | from fedora:32
2 |
3 | RUN dnf clean all && dnf -y update \
4 | && dnf -y install openssh-server passwd\
5 | && dnf -y groupinstall "Xfce Desktop" \
6 | && dnf -y install x2goserver \
7 | && dnf clean all
8 |
9 | COPY entrypoint.sh /entrypoint.sh
10 |
11 | ENTRYPOINT ["/entrypoint.sh"]
12 | CMD ["/usr/sbin/sshd", "-D"]
13 |
14 | EXPOSE 22
15 |
--------------------------------------------------------------------------------
/ClassTypes/Bash/FedoraDockerContainer/Images/DockerCmd1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/LabServices/630f4c03e54557fef3ed977f66c59e1516ed2f90/ClassTypes/Bash/FedoraDockerContainer/Images/DockerCmd1.png
--------------------------------------------------------------------------------
/ClassTypes/Bash/FedoraDockerContainer/Images/DockerCmd2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/LabServices/630f4c03e54557fef3ed977f66c59e1516ed2f90/ClassTypes/Bash/FedoraDockerContainer/Images/DockerCmd2.png
--------------------------------------------------------------------------------
/ClassTypes/Bash/FedoraDockerContainer/Images/DockerCmd3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/LabServices/630f4c03e54557fef3ed977f66c59e1516ed2f90/ClassTypes/Bash/FedoraDockerContainer/Images/DockerCmd3.png
--------------------------------------------------------------------------------
/ClassTypes/Bash/FedoraDockerContainer/Images/DockerDiagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/LabServices/630f4c03e54557fef3ed977f66c59e1516ed2f90/ClassTypes/Bash/FedoraDockerContainer/Images/DockerDiagram.png
--------------------------------------------------------------------------------
/ClassTypes/Bash/FedoraDockerContainer/Images/RemoteFedoraDesktop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/LabServices/630f4c03e54557fef3ed977f66c59e1516ed2f90/ClassTypes/Bash/FedoraDockerContainer/Images/RemoteFedoraDesktop.png
--------------------------------------------------------------------------------
/ClassTypes/Bash/FedoraDockerContainer/Images/X2GoLogin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/LabServices/630f4c03e54557fef3ed977f66c59e1516ed2f90/ClassTypes/Bash/FedoraDockerContainer/Images/X2GoLogin.png
--------------------------------------------------------------------------------
/ClassTypes/Bash/FedoraDockerContainer/Images/X2GoNewSession.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/LabServices/630f4c03e54557fef3ed977f66c59e1516ed2f90/ClassTypes/Bash/FedoraDockerContainer/Images/X2GoNewSession.png
--------------------------------------------------------------------------------
/ClassTypes/Bash/FedoraDockerContainer/Images/X2GoSessionPreferences.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/LabServices/630f4c03e54557fef3ed977f66c59e1516ed2f90/ClassTypes/Bash/FedoraDockerContainer/Images/X2GoSessionPreferences.png
--------------------------------------------------------------------------------
/ClassTypes/Bash/FedoraDockerContainer/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | This sample walks through the high-level steps for configuring a lab that runs a Fedora Docker container on the student’s lab VM.
4 |
5 | ## Directions
6 |
7 | ### Prepare the template VM
8 |
9 | 1. [Create your lab](https://docs.microsoft.com/azure/lab-services/classroom-labs/how-to-manage-classroom-labs#create-a-classroom-lab) – in this example, let’s use the following settings:
10 |
11 | - Medium compute size (4 Cores, 8 GB RAM)
12 | - Ubuntu Server 16.04 LTS
13 |
14 | 1. Once the lab is created, connect to the template VM using SSH.
15 | 1. Note: Do not check "Non-administrator accounts prevent the user from installing software or changing the configuration." setting when creating the lab.
16 | 1. Then, install [Docker engine](https://docs.docker.com/engine/install/) which is used to run and manage Docker containers on the VM. For this sample, you should follow the instructions for [installing the latest version of the Docker engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository).
17 |
18 | ### Build a custom Docker Fedora image
19 |
20 | With Docker, you can use prebuilt images provided by Docker Hub and other container registries; or you can create your own custom container images. In this sample, we’ll build our own custom container image that:
21 |
22 | - Is based on the latest version of the [official Docker container image for Fedora](https://hub.docker.com/_/fedora).
23 | - Installs SSH server package.
24 | - Installs GUI packages for X2Go and XFCE desktop.
25 |
26 | Here are steps to build our custom Fedora container image. These instructions assume that you are still connected to the template VM using SSH:
27 |
28 | 1. Download the [Dockerfile](https://docs.docker.com/engine/reference/builder/) and [entrypoint.sh](https://docs.docker.com/engine/reference/builder/#entrypoint).
29 |
30 | ```bash
31 | curl -O https://raw.githubusercontent.com/Azure/LabServices/main/ClassTypes/Bash/FedoraDockerContainer/Dockerfile -O https://raw.githubusercontent.com/Azure/LabServices/main/ClassTypes/Bash/FedoraDockerContainer/entrypoint.sh
32 | ```
33 |
34 | In this sample, we’ll assume these files have been copied to a directory called **docker_fedora**:
35 |
36 | 
37 |
38 | The Dockerfile is required to build the image and the entrypoint.sh script is executed when the container runs. Also, notice that in the entrypoint.sh file, we have configured the credentials that you will use in later steps to connect using SSH and the GUI remote desktop:
39 |
40 | - **login**: testuser
41 | - **password**: Fedora#123
42 |
43 | 2. Next, we’ll need to change permissions on the entrypoint.sh file to ensure that it is executable (otherwise you will see a permission denied error when we try to run the container). Do this by running the following command:
44 |
45 | ```bash
46 | sudo chmod +x entrypoint.sh
47 | ```
48 |
49 | > [!IMPORTANT]
50 | > If you inadvertently skip this step, you will receive the following error when you attempt to run the container later on in the steps.
51 | >
52 | >docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"/entrypoint.sh\": permission denied": unknown.`
53 | >
54 | >To correct this, you’ll need to change the permissions as shown above and rebuild the Fedora container image.
55 |
56 | 3. Build the custom Fedora container image by running the following command. This will take several minutes to run since it will pull all the necessary packages to the template VM. Also, ensure that the ‘.’ is included at the end of the command:
57 |
58 | ```bash
59 | sudo docker build --rm -t customfedora .
60 | ```
61 |
62 | 4. When the build is done, use the following command to verify that you have a Docker container image called **customfedora**:
63 |
64 | ```bash
65 | sudo docker images
66 | ```
67 |
68 | 
69 |
70 | ### Create and run a Docker container using the custom image
71 |
72 | 1. To run the container with the custom image that we created in the previous step, execute the following command:
73 |
74 | ```bash
75 | sudo docker run --name fedora_container --restart always -d -p 2200:22 customfedora
76 | ```
77 |
78 | > [!NOTE]
79 | > The restart parameter is used to automatically restart the container whenever the container is stopped. This will ensure that the container restarts whenever a student stops\starts their VM.
80 |
81 | 1. Use the following command to verify that you have a running Docker container called **fedora_container** that forwards SSH port 22 on the Docker container to port 2200 on the host VM:
82 |
83 | ```bash
84 | sudo docker ps
85 | ```
86 |
87 | 
88 |
89 | ### Connect from your local computer directly to the Docker container
90 |
91 | To connect directly from your local computer to the Docker container, you need to complete the following steps on your local computer. These are the same steps that students would need to follow to connect from their local computer to the Fedora Docker container running on their lab VM:
92 |
93 | #### Install a remote desktop client
94 |
95 | First, you need to install a remote desktop client on the local computer. The remote desktop client is used to connect to the GUI desktop of the Fedora Docker container. For this sample, we'll use [X2Go](https://github.com/Azure/LabServices/tree/main/TemplateManagement/Bash/LinuxGraphicalDesktopSetup).
96 |
97 | #### Create an SSH tunnel
98 |
99 | Next, you need to create an SSH tunnel between your local computer and the template VM (or student VM):
100 |
101 | 1. Ensure the VM is started.
102 | 1. Copy the SSH connection string for your template VM (or student VM). Your connection string will look similar to this:
103 |
104 | ```bash
105 | ssh -p 12345 testuser@ml-lab-00000000-0000-0000-0000-000000000000.centralus.cloudapp.azure.com
106 | ```
107 |
108 | 1. On your local computer, open a terminal or command prompt. Paste the SSH connection string and add **-L 2200:localhost:2200** into the command string, which creates a “tunnel” between your local computer and the template VM (or student VM). The final command string should look like this:
109 |
110 | ``` bash
111 | ssh -p 12345 -L 2200:localhost:2200 testuser@ml-lab-00000000-0000-0000-0000-000000000000.centralus.cloudapp.azure.com
112 | ```
113 |
114 | Hit **Enter** to run the command. When prompted, provide your password to the template VM. At this point, the tunnel should now be established. Leave this command window open.
115 |
116 | > [!IMPORTANT]
117 | >If you receive the following message, specify **yes** to continue.
118 | >
119 | > The authenticity of host '[ml-lab-00000000-0000-0000-0000-000000000000.central.cloudapp.azure.com]:12345 ([52.191.16.234]:12345)' can't be established.
120 | ECDSA key fingerprint is SHA256:46MUVWAZ+gKvzUuReiIpfZrrlXACqL+6hrelT8UNT9U.
121 | Are you sure you want to continue connecting (yes/no)?" then the password.
122 |
123 | #### Connect to the Docker container using X2Go and SSH
124 |
125 | 1. Open the X2Go client on your local computer and create a new session using the following settings:
126 |
127 | - **Host**: localhost
128 | - **Login**: testuser
129 | - **SSH port**: 2200
130 | - **Session type**: XFCE
131 |
132 | 
133 |
134 | 1. Click **OK** to create the session. Next, click on your session in the right-hand pane.
135 |
136 | 
137 |
138 | 1. When prompted, enter the credentials to connect to the Docker container and click **OK** to start the GUI desktop session. Remember that the credentials are based on what we configured in the entrypoint.sh file:
139 |
140 | - **login**: testuser
141 | - **password**: Fedora#123
142 |
143 | 
144 |
145 | 1. Finally, you should now be connected to Fedora's GUI Desktop:
146 |
147 | > [!IMPORTANT]
148 | >If you receive the following message, specify **yes** to continue.
149 | >
150 | >The server is unknown. Do you trust the host key? Public key hash: localhost:2200 - 7b:34:xxxxxx Yes/No
151 |
152 | 
153 |
154 | After following the above steps to set up your template VM, you can then publish your lab. Students will be able to directly connect to the Fedora Docker container on their assigned VM by creating the SSH tunnel and then using a GUI desktop client, such as X2Go. To do this, students will also need to follow the steps outlined above on their local computer.
155 |
156 | 1. Remember to stop your VM when you are done.
157 |
--------------------------------------------------------------------------------
/ClassTypes/Bash/FedoraDockerContainer/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | : ${USERNAME:=testuser}
4 | : ${USERPASS:=Fedora#123}
5 |
6 | __create_rundir() {
7 | mkdir -p /var/run/sshd
8 | chmod 1777 /dev/shm
9 | }
10 |
11 | __create_user() {
12 | # Create a user
13 | useradd $USERNAME
14 | echo -e "$USERPASS\n$USERPASS" | (passwd --stdin $USERNAME)
15 | echo user password: $USERPASS
16 | usermod -a -G x2gouser $USERNAME
17 | usermod -a -G wheel $USERNAME
18 | }
19 |
20 | __create_hostkeys() {
21 | ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N ''
22 | }
23 |
24 | __setup_ssh() {
25 |
26 | echo 'root:Fedora#2020' | chpasswd
27 | sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
28 | sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
29 | echo 'export NOTVISIBLE="in users profile"' >> ~/.bashrc
30 | root@containerID$ echo "export VISIBLE=now" >> /etc/profile
31 |
32 | ssh-keygen -A -N ''
33 | rm -f /run/nologin
34 | }
35 |
36 | # Call all functions
37 | __create_rundir
38 | __create_hostkeys
39 | __setup_ssh
40 | __create_user
41 |
42 | exec "$@"
--------------------------------------------------------------------------------
/ClassTypes/PowerShell/BigDataAnalytics/Hortonworks-Scheduled-Task.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | \HortonWorksStartup
5 |
6 |
7 |
8 | true
9 |
10 |
11 |
12 | IgnoreNew
13 | true
14 | true
15 | true
16 | false
17 | false
18 |
19 | true
20 | false
21 |
22 | true
23 | true
24 | false
25 | false
26 | false
27 | true
28 | false
29 | PT72H
30 | 7
31 |
32 |
33 |
34 | powershell.exe
35 | -ExecutionPolicy Bypass "TODO\HortonWorksDockerStartup.ps1"
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/ClassTypes/PowerShell/BigDataAnalytics/PowerShell-Run.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/LabServices/630f4c03e54557fef3ed977f66c59e1516ed2f90/ClassTypes/PowerShell/BigDataAnalytics/PowerShell-Run.png
--------------------------------------------------------------------------------
/ClassTypes/PowerShell/BigDataAnalytics/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | To provide students with an easy-to-use experience for a big data analytics class that uses [Hortonworks Data Platform's](https://www.cloudera.com/products/hdp.html) (HDP) Docker deployment, this PowerShell script automatically:
4 |
5 | - Starts the HDP's Docker containers when a student logs into their VM.
6 | - Launches the browser and navigates to the [Sandbox Welcome Page](https://www.cloudera.com/tutorials/learning-the-ropes-of-the-hdp-sandbox.html#welcome-page).
7 |
8 | Windows Task Scheduler is used to automatically run this script when the student logs into their VM.
9 |
10 | > [!NOTE]
11 | > This script assumes that you have already installed Docker and deployed Hortonworks Data Platform. Refer to the article on the [Big Data Analytics](https://docs.microsoft.com/azure/lab-services/classroom-labs/class-type-ethical-hacking) class type for more information.
12 |
13 | ## Directions
14 |
15 | 1. [Connect to the template machine](https://docs.microsoft.com/azure/lab-services/classroom-labs/how-to-create-manage-template#update-a-template-vm) for your class.
16 | 1. Download the **Start-Hortonworks-DockerContainers.ps1** PowerShell script and the **Hortonworks-Scheduled-Task.xml** Windows Task Scheduler file onto the template machine through one of these ways:
17 | - Clone this repository
18 | - Manually download the files
19 | - Use Invoke-WebRequest to download the files:
20 |
21 | ```powershell
22 | Invoke-WebRequest "https://raw.githubusercontent.com/Azure/LabServices/main/ClassTypes/PowerShell/BigDataAnalytics/Start-Hortonworks-DockerContainers.ps1" -OutFile Start-Hortonworks-DockerContainers.ps1
23 |
24 | Invoke-WebRequest "https://github.com/Azure/LabServices/blob/main/ClassTypes/PowerShell/BigDataAnalytics/Hortonworks-Scheduled-Task.xml" -OutFile Hortonworks-Scheduled-Task.xml
25 | ```
26 |
27 | > [!WARNING]
28 | > Ensure that you download the files to a directory location that does *not* include whitespace in the file path. Whitespace can have negative side effects with Windows Task Scheduler.
29 |
30 | 1. Open the Windows Task Scheduler xml file (e.g. **Hortonworks-Scheduled-Task.xml**) and update it so that it points to the correct path of the PowerShell script (e.g. **Start-Hortonworks-DockerContainers.ps1**). For example, if you have downloaded both files to C:\BigDataAnalytics, update the following section in **Hortonworks-Scheduled-Task.xml**:
31 |
32 | ```xml
33 |
34 |
35 | powershell.exe
36 | -ExecutionPolicy Bypass "C:\BigDataAnalytics\Start-Hortonworks-DockerContainers.ps1"
37 |
38 |
39 | ```
40 |
41 | 1. Open Windows Task Scheduler, right-click on **Task Scheduler Library** and select **Import Task**. Browse to the location of **Hortonworks-Scheduled-Task.xml** file and click **Open** to complete the import.
42 | 
43 | 1. Finally, test this task by:
44 | - Running the task within Windows Task Scheduler. To do this, right-click on the task and select **Run**.
45 | 
46 | - Disconnect from the template machine, restart it, and log in. The task should automatically run when you log in.
47 | In both cases, you should see the PowerShell script run, the Docker containers start, and then the browser launch to .
48 | 
49 | 1. After you have tested this on your template machine, you can now [publish](https://docs.microsoft.com/azure/lab-services/classroom-labs/how-to-create-manage-template#publish-the-template-vm) to create the students' machines. Now, when your students log into their machines, they also will see the Docker containers automatically start and the browser open to .
50 |
--------------------------------------------------------------------------------
/ClassTypes/PowerShell/BigDataAnalytics/Run-Task.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/LabServices/630f4c03e54557fef3ed977f66c59e1516ed2f90/ClassTypes/PowerShell/BigDataAnalytics/Run-Task.png
--------------------------------------------------------------------------------
/ClassTypes/PowerShell/BigDataAnalytics/Start-Hortonworks-DockerContainers.ps1:
--------------------------------------------------------------------------------
1 | $SleepTime = 1
2 | $Timeout = 300
3 | $HdpContainer = 'sandbox-hdp'
4 | $ProxyContainer = 'sandbox-proxy'
5 |
6 | function Start-Container{
7 | Param([string]$containerName)
8 |
9 | Start-Process -FilePath 'docker.exe' -WindowStyle Hidden -ArgumentList "start $containerName" -Wait
10 |
11 | Write-Host ''
12 | Write-Host "Starting $containerName container" -NoNewLine
13 |
14 | do
15 | {
16 | Write-Host '.'
17 | Start-Sleep -Seconds $SleepTime
18 | $docker = Start-Process -FilePath 'docker.exe' -WindowStyle Hidden -ArgumentList "container top $containerName" -Wait
19 | }
20 | while ($docker.ExitCode -gt 0 -and $timer.Elapsed.TotalSeconds -lt $Timeout)
21 |
22 | $result = $true
23 | if ($docker.ExitCode -gt 0)
24 | {
25 | $result = $false
26 | Write-Host ''
27 | Write-Error -Message "Error: Container $containerName failed to start." -Category ResourceUnavailable
28 | }
29 |
30 | return $result
31 | }
32 |
33 | $timer = [Diagnostics.Stopwatch]::StartNew()
34 |
35 | Write-Host ''
36 | Write-Host '*****************************************************************************************************************'
37 | Write-Host 'Starting the HortonWorks HDP and proxy sandbox environment. This may take up to 5 minutes to finish.'
38 | Write-Host 'Please do NOT close this window until the sandbox environment has finished starting.'
39 | Write-Host '*****************************************************************************************************************'
40 | Write-Host ''
41 | Write-Host 'Waiting for Docker to start' -NoNewline
42 |
43 | do
44 | {
45 | #Wait for docker since it should be configured on the VM to automatically start when the VM starts
46 | $docker = Start-Process -FilePath 'docker.exe' -WindowStyle Hidden -ArgumentList 'ps -a' -Wait -PassThru
47 |
48 | Write-Host '.' -NoNewline
49 | Start-Sleep -Seconds $SleepTime
50 | }
51 | while ($docker.ExitCode -gt 0 -and $timer.Elapsed.TotalSeconds -lt $Timeout)
52 |
53 | if ($docker.ExitCode -gt 0)
54 | {
55 | Write-Host ''
56 | Write-Error -Message 'Error: Docker failed to start.' -Category ResourceUnavailable
57 | Read-Host -Prompt 'Press Enter to close'
58 | }
59 | else
60 | {
61 | Write-Host ''
62 |
63 | #Start containers
64 | if (Start-Container($HDPContainer))
65 | {
66 | if (Start-Container($ProxyContainer))
67 | {
68 | #Wait some extra time to allow the proxy container to connect
69 | Write-Host ''
70 | Write-Host 'Waiting for the sandbox-proxy container to connect' -NoNewline
71 |
72 | for ($i=0; $i -lt 30; $i++)
73 | {
74 | Write-Host '.' -NoNewline
75 | Start-Sleep -Seconds $SleepTime
76 | }
77 |
78 | Write-Host ''
79 | Write-Host ''
80 | Write-Host 'Launching the browser: http://localhost:1080'
81 |
82 | $browser = Start-Process 'http://localhost:1080/'
83 |
84 | Write-Host ''
85 | if ($docker.ExitCode -gt 0)
86 | {
87 | Write-Host 'Launch the browser and open the following url: http://localhost:1080'
88 |
89 | }
90 |
91 | Write-Host 'The sandbox environment has finished starting!'
92 | }
93 | }
94 | }
95 |
96 | Write-Host ''
97 | Read-Host -Prompt 'Press Enter to close'
--------------------------------------------------------------------------------
/ClassTypes/PowerShell/BigDataAnalytics/Task-Scheduler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/LabServices/630f4c03e54557fef3ed977f66c59e1516ed2f90/ClassTypes/PowerShell/BigDataAnalytics/Task-Scheduler.png
--------------------------------------------------------------------------------
/ClassTypes/PowerShell/EthicalHacking/Create-EthicalHackingLabplanAndLab.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | The MIT License (MIT)
3 | Copyright (c) Microsoft Corporation
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7 | .SYNOPSIS
8 | Creates a Azure Lab Services lab plan and lab that can be used to create an ethical hacking lab.
9 | .DESCRIPTION
10 | Creates a Azure Lab Services lab plan and lab that can be used to create an ethical hacking lab.
11 | .PARAMETER Username
12 | The username for the local administrator account on the VM.
13 | .PARAMETER Password
14 | The password for the local administrator account on the VM. See https://learn.microsoft.com/azure/virtual-machines/windows/faq#what-are-the-password-requirements-when-creating-a-vm-
15 | .PARAMETER Location
16 | Location name for Azure region where the lab plan and lab should reside. Run `Get-AzLocation | Format-Table` to see all available.
17 | .PARAMETER ClassName
18 | Name for class. Must be a valid Azure Resource name. Defaults to 'EthicalHacking'
19 | #>
20 |
21 | [CmdletBinding()]
22 | param(
23 |
24 | [parameter(Mandatory = $true, HelpMessage = "Username for all VMs")]
25 | [string]$UserName = "AdminUser",
26 |
27 | [parameter(Mandatory = $true, HelpMessage = "Password for all VMs")]
28 | [securestring]$Password,
29 |
30 | [parameter(Mandatory = $true, HelpMessage = "Location for lab plan")]
31 | [string]$Location,
32 |
33 | [parameter(Mandatory = $false, HelpMessage = "Name of the class")]
34 | $ClassName = "EthicalHacking"
35 | )
36 |
37 | ###################################################################################################
38 | #
39 | # Handle all errors in this script.
40 | #
41 |
42 | trap {
43 | # NOTE: This trap will handle all errors. There should be no need to use a catch below in this
44 | # script, unless you want to ignore a specific error.
45 | $message = $Error[0].Exception.Message
46 | if ($message) {
47 | Write-Host -Object "`nERROR: $message" -ForegroundColor Red
48 | }
49 |
50 | Write-Host "`nThe script failed to run.`n"
51 |
52 | # IMPORTANT NOTE: Throwing a terminating error (using $ErrorActionPreference = "Stop") still
53 | # returns exit code zero from the PowerShell script when using -File. The workaround is to
54 | # NOT use -File when calling this script and leverage the try-catch-finally block and return
55 | # a non-zero exit code from the catch block.
56 | exit -1
57 | }
58 |
59 | ###################################################################################################
60 | #
61 | # Main execution block.
62 | #
63 |
64 | if ((Get-Module -ListAvailable -Name 'Az.Accounts') -and
65 | (Get-Module -ListAvailable -Name 'Az.Resources') -and
66 | (Get-Module -ListAvailable -Name 'Az.LabServices')) {
67 | Import-Module -Name Az.Accounts
68 | Import-Module -Name Az.Resources
69 | Import-Module -Name Az.LabServices
70 | }
71 | else {
72 | Write-Error "Unable to run script, Az modules are missing. Please install them via 'Install-Module -Name Az -Force' from an elevated command prompt"
73 | exit -1
74 | }
75 |
76 | # Check password
77 | if ( -not ($Password -match "[A-Za-z0-9_@]{16,}")){
78 | Write-Error "Password must be 16 characters long and only contain following characters: A-Z a-z 0-9 _ @ "
79 | exit -1
80 | }
81 |
82 | $ClassName = $ClassName.Trim().Replace(' ','-')
83 |
84 | # Configure parameter names
85 | $rgName = "rg-$ClassName-$(Get-Random)".ToLower()
86 | $labPlanName = "lp-$ClassName$(Get-Random)"
87 | $labName = "$($ClassName)Lab"
88 |
89 | # Create resource group
90 | Write-Host "Creating resource group $rgName"
91 | $rg = New-AzResourceGroup -Name $rgName -Location $Location
92 |
93 | # Create Lab Plan
94 | Write-Host "Creating lab plan $labPlanName"
95 | $labPlan = New-AzLabServicesLabPlan -ResourceGroupName $rgName -Name $labPlanName -Location $Location -AllowedRegion @($Location)
96 |
97 | # Ensure that image needed for the VM is available
98 | $imageName = "Windows Server 2022 Datacenter (Gen2)"
99 | $sku = "2022-datacenter-g2"
100 | Write-Host "Locating '$imageName' image for use in template virtual machine"
101 | $imageObject = $labPlan | Get-AzLabServicesPlanImage | Where-Object {$_.DisplayName -EQ $imageName -and $_.Sku -EQ $sku -and (-not [string]::IsNullOrEmpty($_.EnabledState))} | Where-Object -Property EnabledState -eq "Enabled"
102 |
103 | if($null -eq $imageObject) {
104 | Write-Error "Image '$imageName' was not found in the gallery images. Couldn't create lab $labName."
105 | exit -1
106 | }
107 |
108 | # Create lab using the lab plan
109 | Write-Host "Creating $labName with '$($imageObject.Name)' image"
110 | Write-Warning " Warning: Creating template vm may take up to 20 minutes."
111 | $lab = New-AzLabServicesLab -Name $labName `
112 | -ResourceGroupName $rgName `
113 | -Location $Location `
114 | -LabPlanId $labPlan.Id.ToString() `
115 | -AdditionalCapabilityInstallGpuDriver Disabled `
116 | -AdminUserPassword $Password `
117 | -AdminUserUsername $UserName `
118 | -AutoShutdownProfileShutdownOnDisconnect Enabled `
119 | -AutoShutdownProfileDisconnectDelay $(New-Timespan) `
120 | -AutoShutdownProfileShutdownOnIdle "LowUsage" `
121 | -AutoShutdownProfileIdleDelay $(New-TimeSpan -Minutes 15) `
122 | -AutoShutdownProfileShutdownWhenNotConnected Enabled `
123 | -AutoShutdownProfileNoConnectDelay $(New-TimeSpan -Minutes 15) `
124 | -ConnectionProfileClientRdpAccess Public `
125 | -ConnectionProfileClientSshAccess None `
126 | -ConnectionProfileWebRdpAccess None `
127 | -ConnectionProfileWebSshAccess None `
128 | -Description "Ethical Hacking lab." `
129 | -ImageReferenceOffer $imageObject.Offer.ToString() `
130 | -ImageReferencePublisher $imageObject.Publisher.ToString() `
131 | -ImageReferenceSku $imageObject.Sku.ToString() `
132 | -ImageReferenceVersion $imageObject.Version.ToString() `
133 | -SecurityProfileOpenAccess Disabled `
134 | -SkuCapacity 2 `
135 | -SkuName "Classic_Dsv4_4_16GB_128_P_SSD" `
136 | -Title $labName `
137 | -VirtualMachineProfileCreateOption "TemplateVM" `
138 | -VirtualMachineProfileUseSharedPassword Enabled
139 |
140 | # If lab created, perform next configuration
141 | if($null -eq $lab) {
142 | Write-Error "Lab failed to create."
143 | exit -1
144 | }
145 |
146 | Write-Host "Done! Lab plan and lab have been created." -ForegroundColor Green
--------------------------------------------------------------------------------
/ClassTypes/PowerShell/EthicalHacking/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | These scripts will guide you to create and setup an Azure Lab Services lab that is configured to run an [ethical hacking class](https://docs.microsoft.com/azure/lab-services/classroom-labs/class-type-ethical-hacking). Part 1 of these instructions will be to create the lab plan and lab resource in Azure. Part 2 of these instructions will be to prepare the template VM instance of the newly created lab to be used by your class.
4 |
5 | - - - -
6 |
7 | ## Part 1 - Create Azure Lab Resources
8 |
9 | This script will help create a lab plan and lab in your Azure subscription.
10 |
11 | ### Prerequisites
12 |
13 | To create a lab using the following instructions, you must have
14 |
15 | - Contributor permissions on the subscription in which the lab will be created
16 |
17 | ### Directions
18 |
19 | 1. Open a PowerShell window. Make sure that the window notes it is running under *administrator* privileges.
20 | 1. Download the `Create-EthicalHackingLabplanAndLab.ps1` PowerShell script onto your **local machine**:
21 |
22 | ```powershell
23 | Invoke-WebRequest "https://raw.githubusercontent.com/Azure/LabServices/main/ClassTypes/PowerShell/EthicalHacking/Create-EthicalHackingLabplanAndLab.ps1" -OutFile Create-EthicalHackingLabplanAndLab.ps1
24 | ```
25 |
26 | 1. Run `Create-EthicalHackingLabplanAndLab.ps1` script.
27 |
28 | ```powershell
29 | Install-Module 'Az' -Force
30 | Login-AzAccount
31 | ./Create-EthicalHackingLabplanAndLab.ps1 -UserName "AdminUser" -Password $(ConvertTo-SecureString "" -AsPlainText -Force) -Location "centralus"
32 | ```
33 |
34 | > [!NOTE]
35 | > Run `Get-help .\Create-EthicalHackingLabplanAndLab.ps1 -Detailed` to see more information about script.
36 |
37 |
38 | 1. Open the [Azure Labs Services website](https://labs.azure.com) and login with your Azure credentials to see the lab created by this script.
39 |
40 | - - - -
41 |
42 | ## Part 2 - Prepare your template virtual machine
43 |
44 | This script will help prepare your template virtual machine for a ethical hacking class. Script will:
45 |
46 | - Enable Hyper-V.
47 | - Install [7-Zip](https://www.7-zip.org/download.html) to extra Kali Linux Hyper-V disk.
48 | - Create a Hyper-V virtual machine with a [Kali Linux](https://www.kali.org/). Kali is a Linux distribution that includes tools for penetration testing and security auditing.
49 | - Install [Starwind V2V Converter](https://www.starwindsoftware.com/download-starwind-products#download) to convert Metasploitable VMWare disk to Hyper-V disk.
50 | - Create a Hyper-V virtual machine with a [Metasploitable](https://github.com/rapid7/metasploitable3) image is created. The Rapid7 Metasploitable image is an image purposely configured with security vulnerabilities. You'll use this image to test and find issues.
51 |
52 | ### Prerequisites
53 |
54 | - Lab with a template VM.
55 | - Template VM for lab has a Windows Server OS.
56 |
57 | ### Directions
58 |
59 | 1. Open the [Azure Labs Services website](https://labs.azure.com) and login with your Azure credentials to see the lab created by script in Part 1.
60 | 1. [Connect to template machine](https://learn.microsoft.com/azure/lab-services/how-to-create-manage-template#update-a-template-vm) for your lab.
61 | 1. Download the `SetupForNestedVirtualization.ps1` and `Setup-EthicalHacking.ps1` and PowerShell scripts onto the **Template Virtual Machine**:
62 |
63 | ```powershell
64 | Invoke-WebRequest 'https://aka.ms/azlabs/scripts/hyperV-powershell' -Outfile SetupForNestedVirtualization.ps1
65 | Invoke-WebRequest ' https://aka.ms/azlabs/scripts/EthicalHacking-powershell' -Outfile Setup-EthicalHacking.ps1
66 | ```
67 |
68 | 1. Open a PowerShell window. Make sure that the window notes it is running under *administrator* privileges.
69 | 1. Run `SetupForNestedVirtualization.ps1`. This installs the necessary features to create HyperV virtual machines.
70 |
71 | ```powershell
72 | .\SetupForNestedVirtualization.ps1
73 | ```
74 |
75 | > [!NOTE]
76 | > The script may ask you to restart the machine and re-run it. A note that the script is completed will show in the PowerShell window when no further action is needed.
77 |
78 | 1. Run `Setup-EthicalHacking.ps1`
79 |
80 | If you are using Windows 10 or Windows 11, the following command:
81 |
82 | ```powershell
83 | .\Setup-EthicalHacking.ps1 -SwitchName 'Default Switch'
84 | ```
85 |
86 | If you are using Windows Server, the following command.
87 |
88 | ```powershell
89 | .\Setup-EthicalHacking.ps1
90 | ```
91 |
92 | Consider enabling Hyper-V [DHCP guard](/archive/blogs/virtual_pc_guy/hyper-v-networkingdhcp-guard) and [Router guard](/archive/blogs/virtual_pc_guy/hyper-v-networkingrouter-guard).
93 |
94 | ```powershell
95 | Get-VMNetworkAdapter * | Set-VMNetworkAdapter -RouterGuard On -DhcpGuard On
96 | ```
97 |
98 | > [!WARNING]
99 | > Use `Setup-EthicalHacking.ps1 -Force` will cause any software to be installed silently. By using the Force switch you are automatically accepting the terms for the installed software.
100 |
--------------------------------------------------------------------------------
/ClassTypes/PowerShell/HyperV/CheckHypervVm/README.md:
--------------------------------------------------------------------------------
1 | # Check my Hyper-V VMs
2 |
3 | [Test-HypervVmConfig](./Test-HypervVmConfig.ps1) will verify you setup follows best practices when using nested virtualization in Azure Lab Services. It is recommended to run this script on a lab's template VM before publishing the lab.
4 |
5 | For all local users:
6 |
7 | - Verify user can use Hyper-V.
8 |
9 | For each Hyper-V client VM:
10 |
11 | - Verify the Hyper-V VM is **not** in a saved state.
12 | - Verify the AutomaticStopAction is set to Stop.
13 | - Check the number of vCPUs.
14 | - Verify adequate memory assigned.
15 | - Verify variable memory enabled.
16 | - Verify disks are VHDX.
17 |
18 | For the host VM:
19 |
20 | - Verify DHCP role is not installed.
21 | - Verify sufficient free space on OS disk.
22 |
23 | ## Usage
24 |
25 | On the host VM (not the Hyper-V VMs), open an Adminstrator PowerShell window. Run the following command?
26 |
27 | ```powershell
28 | ./Test-HypervVmConfig.ps1
29 | ```
30 |
31 | In some cases, the suggested defaults may not apply to your Hyper-V VM. To override the defaults for a VM, provide a config file. Schema for config file is available at [vm-config-schema.json](vm-config-schema.json).
32 |
33 | For example:
34 |
35 | ```powershell
36 | ./Test-HypervVmConfig.ps1 -$ConfigFilePath ./ethical-hacking-vm-config.json.
37 | ```
38 |
--------------------------------------------------------------------------------
/ClassTypes/PowerShell/HyperV/CheckHypervVm/ethical-hacking-vm-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/Azure/LabServices/main/ClassTypes/PowerShell/HyperV/CheckHypervVm/vm-config-schema.json",
3 | "VMConfigurations": [
4 | {
5 | "Name": "metasploitable",
6 | "Properties": {
7 | "ProcessorCount": 1,
8 | "Memory": {
9 | "Startup": "512MB",
10 | "DynamicMemoryEnabled": true,
11 | "Minimum": "512MB",
12 | "Maximum": "1GB"
13 | }
14 | }
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/ClassTypes/PowerShell/HyperV/CheckHypervVm/example-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/Azure/LabServices/main/ClassTypes/PowerShell/HyperV/CheckHypervVm/vm-config-schema.json",
3 | "VMConfigurations": [
4 | {
5 | "Name": "my-vm",
6 | "Properties": {
7 | "ProcessorCount": 1,
8 | "Memory": {
9 | "Startup": "512MB",
10 | "DynamicMemoryEnabled": true,
11 | "Minimum": "512MB",
12 | "Maximum": "1GB"
13 | }
14 | }
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/ClassTypes/PowerShell/HyperV/CheckHypervVm/vm-config-schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-04/schema#",
3 | "type": "object",
4 | "properties": {
5 | "VMConfigurations": {
6 | "type": "array",
7 | "items": [
8 | {
9 | "type": "object",
10 | "properties": {
11 | "Name": {
12 | "type": "string"
13 | },
14 | "Properties": {
15 | "type": "object",
16 | "properties": {
17 | "ProcessorCount": {
18 | "type": "integer"
19 | },
20 | "Memory": {
21 | "type": "object",
22 | "properties": {
23 | "Startup": {
24 | "type": "string"
25 | },
26 | "DynamicMemoryEnabled": {
27 | "type": "boolean"
28 | },
29 | "Minimum": {
30 | "type": "string"
31 | },
32 | "Maximum": {
33 | "type": "string"
34 | }
35 | }
36 | }
37 | }
38 | }
39 | },
40 | "required": [
41 | "Name",
42 | "Properties"
43 | ]
44 | }
45 | ]
46 | }
47 | },
48 | "required": [
49 | "VMConfigurations"
50 | ]
51 | }
--------------------------------------------------------------------------------
/ClassTypes/PowerShell/HyperV/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | This script will help prepare your template virtual machine for a classroom lab to use Hyper-V nested virtualization.
4 |
5 | ## Notes
6 |
7 | - Script must be executed using administrator privileges.
8 | - If running this script on a client O.S. like Windows 10 which does not support DHCP Server, use the "Default Switch" in the network settings.
9 |
10 | ## Directions
11 |
12 | 1. Connect to template machine for your class.
13 | 2. Clone repository or download files in this folder onto the template machine.
14 | 3. Open a PowerShell window. Make sure that the window notes it is running under administrator privileges.
15 | 4. Run `SetupForNestedVirtualization.ps1`.
16 | 5. The script may ask you to restart the machine and re-run it. A note that the script is completed will show if no further action is needed.
17 |
--------------------------------------------------------------------------------
/ClassTypes/PowerShell/HyperV/SetupForNestedVirtualization.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | The MIT License (MIT)
3 | Copyright (c) Microsoft Corporation
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7 | .SYNOPSIS
8 | This script prepares a Windows Server machine to use virtualization. This includes enabling Hyper-V, enabling DHCP and setting up a switch to allow client virtual machines to have internet access.
9 | #>
10 |
11 | [CmdletBinding()]
12 | param(
13 | [Parameter(Mandatory=$false)][bool] $InstallDhcp = $true,
14 | [Parameter(Mandatory=$false)][switch]$Force = $false
15 | )
16 |
17 | ###################################################################################################
18 | #
19 | # PowerShell configurations
20 | #
21 |
22 | # NOTE: Because the $ErrorActionPreference is "Stop", this script will stop on first failure.
23 | # This is necessary to ensure we capture errors inside the try-catch-finally block.
24 | $ErrorActionPreference = "Stop"
25 |
26 | # Hide any progress bars, due to downloads and installs of remote components.
27 | $ProgressPreference = "SilentlyContinue"
28 |
29 | # Ensure we set the working directory to that of the script.
30 | Push-Location $PSScriptRoot
31 |
32 | # Discard any collected errors from a previous execution.
33 | $Error.Clear()
34 |
35 | # Configure strict debugging.
36 | Set-PSDebug -Strict
37 |
38 | # Configure variables for ShouldContinue prompts
39 | $YesToAll = $Force
40 | $NoToAll = $false
41 |
42 | ###################################################################################################
43 | #
44 | # Handle all errors in this script.
45 | #
46 |
47 | trap {
48 | # NOTE: This trap will handle all errors. There should be no need to use a catch below in this
49 | # script, unless you want to ignore a specific error.
50 | $message = $Error[0].Exception.Message
51 | if ($message) {
52 | Write-Host -Object "`nERROR: $message" -ForegroundColor Red
53 | }
54 |
55 | Write-Host "`nThe script failed to run.`n"
56 |
57 | # IMPORTANT NOTE: Throwing a terminating error (using $ErrorActionPreference = "Stop") still
58 | # returns exit code zero from the PowerShell script when using -File. The workaround is to
59 | # NOT use -File when calling this script and leverage the try-catch-finally block and return
60 | # a non-zero exit code from the catch block.
61 | exit -1
62 | }
63 |
64 | ###################################################################################################
65 | #
66 | # Functions used in this script.
67 | #
68 |
69 | <#
70 | .SYNOPSIS
71 | Returns true is script is running with administrator privileges and false otherwise.
72 | #>
73 | function Get-RunningAsAdministrator {
74 | [CmdletBinding()]
75 | param()
76 |
77 | $isAdministrator = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
78 | Write-Verbose "Running with Administrator privileges (t/f): $isAdministrator"
79 | return $isAdministrator
80 | }
81 |
82 | <#
83 | .SYNOPSIS
84 | Returns true is current machine is a Windows Server machine and false otherwise.
85 | #>
86 | function Get-RunningServerOperatingSystem {
87 | [CmdletBinding()]
88 | param()
89 |
90 | return ($null -ne $(Get-Module -ListAvailable -Name 'servermanager') )
91 | }
92 |
93 | <#
94 | .SYNOPSIS
95 | Enables Hyper-V role, including PowerShell cmdlets for Hyper-V and management tools.
96 | #>
97 | function Install-HypervAndTools {
98 | [CmdletBinding()]
99 | param()
100 |
101 | if (Get-RunningServerOperatingSystem) {
102 | Install-HypervAndToolsServer
103 | } else
104 | {
105 | Install-HypervAndToolsClient
106 | }
107 | }
108 |
109 | <#
110 | .SYNOPSIS
111 | Enables Hyper-V role for server, including PowerShell cmdlets for Hyper-V and management tools.
112 | #>
113 | function Install-HypervAndToolsServer {
114 | [CmdletBinding()]
115 | param()
116 |
117 |
118 | if ($null -eq $(Get-WindowsFeature -Name 'Hyper-V')) {
119 | Write-Error "This script only applies to machines that can run Hyper-V."
120 | }
121 | else {
122 | $roleInstallStatus = Install-WindowsFeature -Name Hyper-V -IncludeManagementTools
123 | if ($roleInstallStatus.RestartNeeded -eq 'Yes') {
124 | Write-Error "Restart required to finish installing the Hyper-V role . Please restart and re-run this script."
125 | }
126 | }
127 |
128 | # Install PowerShell cmdlets
129 | $featureStatus = Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Management-PowerShell
130 | if ($featureStatus.RestartNeeded -eq $true) {
131 | Write-Error "Restart required to finish installing the Hyper-V PowerShell Module. Please restart and re-run this script."
132 | }
133 | }
134 |
135 | <#
136 | .SYNOPSIS
137 | Enables Hyper-V role for client (Win10), including PowerShell cmdlets for Hyper-V and management tools.
138 | #>
139 | function Install-HypervAndToolsClient {
140 | [CmdletBinding()]
141 | param()
142 |
143 |
144 | if ($null -eq $(Get-WindowsOptionalFeature -Online -FeatureName 'Microsoft-Hyper-V-All')) {
145 | Write-Error "This script only applies to machines that can run Hyper-V."
146 | }
147 | else {
148 | $roleInstallStatus = Enable-WindowsOptionalFeature -Online -FeatureName 'Microsoft-Hyper-V-All'
149 | if ($roleInstallStatus.RestartNeeded) {
150 | Write-Error "Restart required to finish installing the Hyper-V role . Please restart and re-run this script."
151 | }
152 |
153 | $featureEnableStatus = Get-WmiObject -Class Win32_OptionalFeature -Filter "name='Microsoft-Hyper-V-Hypervisor'"
154 | if ($featureEnableStatus.InstallState -ne 1) {
155 | Write-Error "This script only applies to machines that can run Hyper-V."
156 | goto(finally)
157 | }
158 |
159 | }
160 | }
161 |
162 | <#
163 | .SYNOPSIS
164 | Enables DHCP role, including management tools.
165 | #>
166 | function Install-DHCP {
167 | [CmdletBinding()]
168 | param()
169 |
170 | if ($null -eq $(Get-WindowsFeature -Name 'DHCP')) {
171 | Write-Error "This script only applies to machines that can run DHCP."
172 | }
173 | else {
174 | $roleInstallStatus = Install-WindowsFeature -Name DHCP -IncludeManagementTools
175 | if ($roleInstallStatus.RestartNeeded -eq 'Yes') {
176 | Write-Error "Restart required to finish installing the DHCP role . Please restart and re-run this script."
177 | }
178 | }
179 |
180 | # Tell Windows we are done installing DHCP
181 | Set-ItemProperty -Path registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ServerManager\Roles\12 -Name ConfigurationState -Value 2
182 | }
183 |
184 | function Install-DhcpScope {
185 | param (
186 | [Parameter(Mandatory=$true)][string] $RouterIpAddress,
187 | [Parameter(Mandatory=$true)][string] $StartRangeForClientIps,
188 | [Parameter(Mandatory=$true)][string] $EndRangeForClientIps,
189 | [Parameter(Mandatory=$true)][string] $SubnetMaskForClientIps
190 | )
191 | $dnsServerIp = "168.63.129.16"
192 |
193 | # Add scope for client vm ip address
194 | $scopeName = "LabServicesDhcpScope"
195 |
196 | $dhcpScope = Select-ResourceByProperty `
197 | -PropertyName 'Name' -ExpectedPropertyValue $scopeName `
198 | -List @(Get-DhcpServerV4Scope) `
199 | -NewObjectScriptBlock { Add-DhcpServerv4Scope -name $scopeName -StartRange $StartRangeForClientIps -EndRange $EndRangeForClientIps -SubnetMask $SubnetMaskForClientIps -State Active
200 | Set-DhcpServerV4OptionValue -DnsServer $dnsServerIp -Router $RouterIpAddress
201 | }
202 | Write-Host "Using $dhcpScope"
203 | }
204 |
205 | <#
206 | .SYNOPSIS
207 | Funtion will find object in given list with specified property of the specified expected value. If object cannot be found, a new one is created by executing scropt in the NewObjectScriptBlock parameter.
208 | .PARAMETER PropertyName
209 | Property to check with looking for object.
210 | .PARAMETER ExpectedPropertyValue
211 | Expected value of property being checked.
212 | .PARAMETER List
213 | List of objects in which to look.
214 | .PARAMETER NewObjectScriptBlock
215 | Script to run if object with the specified value of specified property name is not found.
216 |
217 | #>
218 | function Select-ResourceByProperty {
219 | param(
220 | [Parameter(Mandatory = $true)][string]$PropertyName ,
221 | [Parameter(Mandatory = $true)][string]$ExpectedPropertyValue,
222 | [Parameter(Mandatory = $false)][array]$List = @(),
223 | [Parameter(Mandatory = $true)][scriptblock]$NewObjectScriptBlock,
224 | [Parameter(Mandatory = $false)][string] $ShouldContinuePrompt
225 | )
226 |
227 | $returnValue = $null
228 | $items = @($List | Where-Object $PropertyName -Like $ExpectedPropertyValue)
229 |
230 | if ($items.Count -eq 0) {
231 | Write-Verbose "Creating new item with $PropertyName = $ExpectedPropertyValue."
232 | if (-not [String]::IsNullOrEmpty($ShouldContinuePrompt) -and $PSCmdlet.ShouldContinue($ShouldContinuePrompt, $env:COMPUTERNAME, [ref] $YesToAll, [ref] $NoToAll)){
233 | $returnValue = & $NewObjectScriptBlock
234 | }else{
235 | return $null
236 | }
237 | }
238 | elseif ($items.Count -eq 1) {
239 | $returnValue = $items[0]
240 | }
241 | else {
242 | $choice = -1
243 | $choiceTable = New-Object System.Data.DataTable
244 | $choiceTable.Columns.Add($(new-object System.Data.DataColumn("Option Number")))
245 | $choiceTable.Columns[0].AutoIncrement = $true
246 | $choiceTable.Columns[0].ReadOnly = $true
247 | $choiceTable.Columns.Add($(New-Object System.Data.DataColumn($PropertyName)))
248 | $choiceTable.Columns.Add($(New-Object System.Data.DataColumn("Details")))
249 |
250 | $choiceTable.Rows.Add($null, "\< Exit \>", "Choose this option to exit the script.") | Out-Null
251 | $items | ForEach-Object { $choiceTable.Rows.Add($null, $($_ | Select-Object -ExpandProperty $PropertyName), $_.ToString()) } | Out-Null
252 |
253 | Write-Host "Found multiple items with $PropertyName = $ExpectedPropertyValue. Please choose on of the following options."
254 | $choiceTable | ForEach-Object { Write-Host "$($_[0]): $($_[1]) ($($_[2]))" }
255 |
256 | while (-not (($choice -ge 0 ) -and ($choice -le $choiceTable.Rows.Count - 1 ))) {
257 | $choice = Read-Host "Please enter option number. (Between 0 and $($choiceTable.Rows.Count - 1))"
258 | }
259 |
260 | if ($choice -eq 0) {
261 | Write-Error "User cancelled script."
262 | }
263 | else {
264 | $returnValue = $items[$($choice - 1)]
265 | }
266 |
267 | }
268 |
269 | return $returnValue
270 | }
271 |
272 | ###################################################################################################
273 | #
274 | # Main execution block.
275 | #
276 |
277 | try {
278 |
279 | # Check that script is being run with Administrator privilege.
280 | Write-Host "Verify running as administrator."
281 | if (-not (Get-RunningAsAdministrator)) { Write-Error "Please re-run this script as Administrator." }
282 |
283 | # Install HyperV service and client tools
284 | if ($PSCmdlet.ShouldContinue("Install Hyper-V feature and tools.", $env:COMPUTERNAME, [ref] $YesToAll, [ref] $NoToAll )){
285 | Write-Host "Installing Hyper-V, if needed."
286 | Install-HypervAndTools
287 | }else{
288 | Write-Error "Hyper-V feature and tools not installed."
289 | exit;
290 | }
291 |
292 | # Pin Hyper-V to the user's desktop.
293 | if ($PSCmdlet.ShouldContinue("Install Hyper-V feature and tools.", $env:COMPUTERNAME, [ref] $YesToAll, [ref] $NoToAll)){
294 | Write-Host "Creating shortcut to Hyper-V Manager on desktop."
295 | $Shortcut = (New-Object -ComObject WScript.Shell).CreateShortcut($(Join-Path "$env:UserProfile\Desktop" "Hyper-V Manager.lnk"))
296 | $Shortcut.TargetPath = "$env:SystemRoot\System32\virtmgmt.msc"
297 | $Shortcut.Save()
298 | }
299 |
300 | if (Get-RunningServerOperatingSystem) {
301 |
302 | # Ip addresses and range information.
303 | $ipAddress = "192.168.0.1"
304 | $ipAddressPrefixRange = "24"
305 | $ipAddressPrefix = "192.168.0.0/$ipAddressPrefixRange"
306 | $startRangeForClientIps = "192.168.0.100"
307 | $endRangeForClientIps = "192.168.0.200"
308 | $subnetMaskForClientIps = "255.255.255.0"
309 |
310 |
311 | if ($InstallDhcp){
312 | # Install DHCP so client vms will automatically get an IP address.
313 | Write-Warning "Installing DHCP role on an Azure VM is not a supported scenario. It is recommended to manually set the ip address for Hyper-V VMs. See https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq#can-i-deploy-a-dhcp-server-in-a-vnet"
314 | if ($PSCmdlet.ShouldContinue("Install DHCP role and scope.", $env:COMPUTERNAME, [ref] $YesToAll, [ref] $NoToAll)) {
315 | Write-Host "Installing DHCP, if needed."
316 | Install-DHCP
317 | Install-DhcpScope -RouterIpAddress $ipAddress -StartRangeForClientIps $startRangeForClientIps -EndRangeForClientIps $endRangeForClientIps -SubnetMaskForClientIps $subnetMaskForClientIps
318 | }
319 | }
320 |
321 | # Create Switch
322 | Write-Host "Setting up network for client virtual machines."
323 | $switchName = "LabServicesSwitch"
324 | $vmSwitch = Select-ResourceByProperty `
325 | -PropertyName 'Name' -ExpectedPropertyValue $switchName `
326 | -List (Get-VMSwitch -SwitchType Internal) `
327 | -NewObjectScriptBlock { New-VMSwitch -Name $switchName -SwitchType Internal } `
328 | -ShouldContinuePrompt "Create switch named $switchName."
329 | if ($null -eq $vmSwitch) { Write-Error "VM switch $switchName not created or found"}
330 | Write-Host "Using switch '$vmSwitch'"
331 |
332 | # Get network adapter information
333 | $netAdapter = Select-ResourceByProperty `
334 | -PropertyName "Name" -ExpectedPropertyValue "*$switchName*" `
335 | -List @(Get-NetAdapter) `
336 | -NewObjectScriptBlock { Write-Error "No network adapaters with name $switchName found." }
337 | Write-Host "Using network adapter '$netAdapter'"
338 | Write-Host "Adapter found is $($netAdapter.ifAlias) and Interface Index is $($netAdapter.ifIndex)"
339 |
340 | # Create IP Address
341 | $netIpAddr = Select-ResourceByProperty `
342 | -PropertyName 'IPAddress' -ExpectedPropertyValue $ipAddress `
343 | -List @(Get-NetIPAddress) `
344 | -NewObjectScriptBlock { New-NetIPAddress -IPAddress $ipAddress -PrefixLength $ipAddressPrefixRange -InterfaceIndex $netAdapter.ifIndex } `
345 | -ShouldContinuePrompt "Create IP address $ipAddress"
346 | if ($null -eq $netIpAddr) {
347 | Write-Error "Couldn't create or find IP address $ipAddress."
348 | }elseif (($netIpAddr.PrefixLength -ne $ipAddressPrefixRange) -or ($netIpAddr.InterfaceIndex -ne $netAdapter.ifIndex)) {
349 | Write-Error "Found Net IP Address $netIpAddr, but prefix $ipAddressPrefix ifIndex not $($netAdapter.ifIndex)."
350 | }else{
351 | Write-Host "Net ip address found is $ipAddress"
352 | }
353 |
354 | # Create NAT
355 | $natName = "LabServicesNat"
356 | $netNat = Select-ResourceByProperty `
357 | -PropertyName 'Name' `
358 | -ExpectedPropertyValue $natName `
359 | -List @(Get-NetNat) `
360 | -NewObjectScriptBlock { New-NetNat -Name $natName -InternalIPInterfaceAddressPrefix $ipAddressPrefix } `
361 | -ShouldContinuePrompt "Create NAT network $ipAddressPrefix"
362 | if ($null -eq $netNat){
363 | Write-Error "Couldn't create or find NAT network $ipAddressPrefix"
364 | }elseif ($netNat.InternalIPInterfaceAddressPrefix -ne $ipAddressPrefix) {
365 | Write-Error "Found nat with name $natName, but InternalIPInterfaceAddressPrefix is not $ipAddressPrefix."
366 | }else{
367 | Write-Host "Nat found is $netNat"
368 | }
369 |
370 | #Make sure WinNat will start automatically so Hyper-V VMs will have internet connectivity.
371 | if (((Get-Service -Name WinNat | Select-Object -ExpandProperty StartType) -ne 'Automatic') -and $PSCmdlet.ShouldContinue($env:COMPUTERNAME, "Automatically start WinNat service.", [ref] $YesToAll, [ref] $NoToAll)) {
372 | Set-Service -Name WinNat -StartupType Automatic
373 | }
374 | if ($(Get-Service -Name WinNat | Select-Object -ExpandProperty StartType) -ne 'Automatic')
375 | {
376 | Write-Warning "Unable to set WinNat service to Automatic. Hyper-V virtual machines will not have internet connectivity when service is not running."
377 | }
378 | }
379 | else {
380 | Write-Host -Object "On Windows 10 and later, use 'Default Switch' when configuring network connection for Hyper-V VMs." -ForegroundColor Yellow
381 | }
382 | # Tell the user script is done.
383 | Write-Host "Script completed." -ForegroundColor Green
384 | }
385 | finally {
386 | # Restore system to state prior to execution of this script.
387 | Pop-Location
388 | }
--------------------------------------------------------------------------------
/ClassTypes/README.md:
--------------------------------------------------------------------------------
1 | # Class Type Scripts
2 |
3 | This folder contains scripts that aid in the setup of example classes explained in [Azure Lab Services Class Types](https://learn.microsoft.com/azure/lab-services/class-types).
4 |
5 | | Class Type Name | Description |
6 | |-----------------|-------------|
7 | | [BigDataAnalytics](./PowerShell/BigDataAnalytics/) | Provides students with an easy-to-use experience for a big data analytics class that uses [Hortonworks Data Platform's](https://www.cloudera.com/products/hdp.html) (HDP) Docker deployment.|
8 | | [FedoraDockerContainer](./Bash/FedoraDockerContainer/) | Walks through the high-level steps for configuring a lab that runs a Fedora Docker container on the student’s lab VM |
9 | | [EthicalHacking](./PowerShell/EthicalHacking/) | Creates lab that is configured to run an [ethical hacking class](https://docs.microsoft.com/azure/lab-services/classroom-labs/class-type-ethical-hacking). |
10 | | [HyperV](./PowerShell/HyperV/) | Prepares a template virtual machine for a classroom lab to use Hyper-V [nested virtualization](https://learn.microsoft.com/azure/lab-services/concept-nested-virtualization-template-vm). Nested virtualization is required for the [Ethical Hacking](./PowerShell/EthicalHacking/) and [Networking with GNS3](https://learn.microsoft.com/azure/lab-services/class-types#networking-with-gns3) class types. |
11 |
--------------------------------------------------------------------------------
/GeneralScripts/PowerShell/BringImageToSharedImageGallery/Readme.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | This script is used to bring a custom image from an Azure virtual machine (VM) to an Azure Compute Gallery (formerly Shared Image Gallery).
4 |
5 | ## Prerequisites
6 |
7 | - Ensure that you have the [Azure PowerShell module](https://docs.microsoft.com/powershell/azure) installed.
8 |
9 | ## Directions
10 |
11 | 1. Open a PowerShell window.
12 | 2. Run `BringImageToSharedImageGallery.ps1`. You can either pass all the required parameters when you run the script. Or, you can run the script without the parameters so that you are prompted as shown in the next bullet.
13 | 3. When prompted, enter information about where the source VM resides and the shared image gallery where the custom image will be created. Here are some helpful tips:
14 |
15 | - After you provide your subscription, you will be prompted with the following question: **Is this a DTL VM?**. Assuming that you used an Azure VM to set up your image, you should answer **No** for this question.
16 |
17 | - The script will ask you to choose the **Image Definition in Shared Image Gallery** that will be used to create the custom image. You can either choose an existing image definition or you can choose to create a new one. To create a new image definition, you should choose the option **Create a new resource...**.
18 |
19 | - When you create a new image definition, you will be prompted for the following information:
20 | - Name of the image definition.
21 | - Whether the image is [specialized or generalized](https://docs.microsoft.com/azure/virtual-machines/shared-image-galleries#generalized-and-specialized-images).
22 | - Publisher. For more information about the value to enter, see [Image definitions](https://docs.microsoft.com/azure/virtual-machines/shared-image-galleries#image-definitions).
23 | - Offer. For more information about the value to enter, see [Image definitions](https://docs.microsoft.com/azure/virtual-machines/shared-image-galleries#image-definitions).
24 | - HyperVGeneration. You should enter **v1**.
25 |
26 | The script will automatically create an an [image version](https://docs.microsoft.com/azure/virtual-machines/shared-image-galleries#image-versions) of 1.0.0.
27 |
28 | - If you select an existing image definition, the script will automatically create an image version that has the PatchVersion incremented. For example, if the previous image version is 1.0.0, the new version is 1.0.1.
29 |
30 | For related information, refer to the following articles:
31 |
32 | - [Bring a custom image to Shared Image Gallery](https://docs.microsoft.com/azure/lab-services/upload-custom-image-shared-image-gallery)
33 | - [Shared Image Gallery overview](https://docs.microsoft.com/azure/virtual-machines/shared-image-galleries)
34 | - [Attach or detach a Shared Image Gallery](https://docs.microsoft.com/azure/lab-services/how-to-attach-detach-shared-image-gallery)
35 | - [Use an Azure Compute Gallery](https://docs.microsoft.com/azure/lab-services/how-to-use-shared-image-gallery)
36 |
--------------------------------------------------------------------------------
/GeneralScripts/PowerShell/CustomPolicies/RemovePolicy.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [string]
5 | $PolicyName,
6 |
7 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
8 | [string]
9 | $PolicyScope
10 | )
11 |
12 | Set-StrictMode -Version Latest
13 | $ErrorActionPreference = 'Stop'
14 |
15 | $scriptstartTime = Get-Date
16 | Write-Host "Removing Policy $PolicyName with Scope $PolicyScope, starting at $scriptstartTime" -ForegroundColor Green
17 |
18 | Remove-AzPolicyAssignment -Name $PolicyName -Scope $PolicyScope
19 |
20 | Write-Host "Complete policy removal, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/LabManagement/ARM/BC_CompSci_AI_200_Schedule_Sample.csv:
--------------------------------------------------------------------------------
1 | Frequency,FromDate,ToDate,StartTime,EndTime,WeekDays,TimeZoneId, Notes
2 | Weekly,12/1/2022," ""12/24/2022"""," ""10:00"""," ""12:00"""," ""Monday; Friday""",America/Los_Angeles," ""A recurrent class"""
3 | Once,12/1/2022," ""12/1/2022"""," ""08:00"""," ""09:30"""," """"",America/Los_Angeles," ""A workshop"""
4 |
--------------------------------------------------------------------------------
/LabManagement/ARM/BellowsCollegeLabs_Sample.csv:
--------------------------------------------------------------------------------
1 | Id,Tags,ResourceGroupName,Location,LabPlanName,LabName,SharedGalleryId,ImageName,AadGroupId,MaxUsers,UsageQuota,UsageMode,SharedPassword,Size,Title,Descr,TemplateVmState, UserName,Password,LinuxRdp, Emails,LabOwnerEmails, idleGracePeriod, idleOsGracePeriod, idleNoConnectGracePeriod, Invitation, Schedules
2 | id001,Tag=District1;Level=Senior,BellowsCollege_rg,westcentralus,BellowsCollege_Econ_plan,BC_Economics_101,,Ubuntu Server 20.04 LTS,,2,10,Restricted,ENABLED,Classic_Fsv2_2_4GB_128_S_SSD,MacroEconomics 101,Beginning MacroEconomics,Enabled,testuser0000,Test00000000,TRUE,AndreLawson@bellowsCollege.com;JuanMorgan@bellowsCollege.com,r_e_best@hotmail.com,,,,,
3 | id002,Tag=District1;Level=Junior,BellowsCollege_rg,westcentralus,BellowsCollege_CompSci_plan,BC_CompSci_Office_101,,Visual Studio 2022 Community (latest release) on Windows 11 Enterprise N (x64),,2,20,Restricted,ENABLED,Classic_Fsv2_2_4GB_128_S_SSD,Introduction to Office,"Introduction to Office, including Word, Excel, PowerPoint, and Visio",,testuser0000,Test00000000,FALSE,AndreLawson@bellowsCollege.com;JuanMorgan@bellowsCollege.com,,,,,Office 101,
4 | id003,Tag=District1;Level=Junior,BellowsCollege_rg,westcentralus,BellowsCollege_CompSci_plan,BC_CompSci_AI_200,,Windows 11 Pro (Gen2),,2,25,Unrestricted,DISABLED,Classic_Fsv2_2_4GB_128_S_SSD,Beginning AI development,Using .Net to develop AI,Disabled,testuser0000,Test00000000,FALSE,HaydenCook@bellowscollege.com,,15,15,15,AI development,BC_CompSci_AI_200_Schedule_Sample
5 | id004,Tag=District1;Level=Junior,BellowsCollege_rg,westcentralus,BellowsCollege_CompSci_plan,BC_CompSci_AI_201,,Windows 11 Pro (Gen2),,2,25,Unrestricted,DISABLED,Classic_Fsv2_2_4GB_128_S_SSD,Beginning AI development B,Using .Net to develop AI,Disabled,testuser0000,Test00000000,FALSE,,,15,15,15,AI development,BC_CompSci_AI_200_Schedule_Sample
6 |
--------------------------------------------------------------------------------
/LabManagement/ARM/Bulk_CreateLab_ARM.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [ValidateNotNullOrEmpty()]
5 | [string] $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
8 | [ValidateNotNullOrEmpty()]
9 | [string] $CsvOutputFile,
10 |
11 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
12 | [ValidateNotNullOrEmpty()]
13 | [string] $ARMFile,
14 |
15 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
16 | [ValidateNotNullOrEmpty()]
17 | [switch] $publish,
18 |
19 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
20 | [ValidateNotNullOrEmpty()]
21 | [switch] $force
22 | )
23 |
24 | Set-StrictMode -Version Latest
25 | $ErrorActionPreference = 'Stop'
26 |
27 | # Make sure the input file does exist
28 | if (-not (Test-Path -Path $CsvConfigFile)) {
29 | Write-Error "Input CSV File must exist, please choose a valid file location..."
30 | }
31 |
32 | # Make sure the output file doesn't exist
33 | if ((Test-Path -Path $CsvOutputFile) -and (-not $force.IsPresent)) {
34 | Write-Error "Output File cannot already exist, please choose a location to create a new output file..."
35 | }
36 |
37 | Import-Module ".\Utilities.psm1" -Force
38 |
39 | $scriptstartTime = Get-Date
40 | Write-Host "Executing Lab Creation Script, starting at $scriptstartTime" -ForegroundColor Green
41 |
42 | $labs = $CsvConfigFile | Import-LabsCsv
43 |
44 | $jobs = @{}
45 |
46 | ForEach ($lab in $labs) {
47 |
48 | # Reduce image get cost.
49 | if (-not [boolean](Get-Variable $($lab.LabPlanName) -ErrorAction SilentlyContinue)) {
50 | Write-Host "Getting new images $($lab.LabPlanName)"
51 | New-Variable -Name $lab.LabPlanName -Value $(Get-AzLabServicesPlanImage -ResourceGroupName $lab.ResourceGroupName -LabPlanName $lab.LabPlanName)
52 | }
53 |
54 | #Handle tags
55 | $tagParam = @{}
56 | if ($lab.Tags) {
57 | $tags = $lab.Tags.Split(";")
58 | foreach($tag in $tags) {
59 | $tagParts = $tag.Split("=")
60 | $tagParam.Add($tagParts[0],$tagParts[1])
61 | }
62 | }
63 |
64 |
65 | $hashParam = @{LabName = $($lab.LabName); `
66 | Title = $($lab.Title); `
67 | Tags = $($tagParam); `
68 | LabPlanName = $($lab.LabPlanName); `
69 | Location = $($lab.Location); `
70 | AadGroupId = $($lab.AadGroupId); `
71 | SkuSize = $($lab.Size); `
72 | Capacity = [int]$($lab.MaxUsers); `
73 | UsageQuota = $($lab.UsageQuota); `
74 | SharedPassword = $($lab.SharedPassword); `
75 | DisconnectDelay = $(if ($lab.idleOsGracePeriod) {$lab.idleOsGracePeriod} else {"0"}); `
76 | NoConnectDelay = $( if ($lab.idleNoConnectGracePeriod) {$lab.idleNoConnectGracePeriod} else {"0"}); `
77 | IdleDelay = $(if ($lab.idleGracePeriod) {$lab.idleGracePeriod} else {"0"}); `
78 | AdminUser = $($lab.UserName); `
79 | AdminPassword = $($lab.Password); }
80 |
81 | if ($lab.GpuDriverEnabled -eq "Enabled") {
82 | $hashParam.Add("GpuDrivers", "Enabled")
83 | } else {
84 | $hashParam.Add("GpuDrivers", "Disabled")
85 | }
86 |
87 | if ($lab.UsageMode -eq "Restricted"){
88 | $hashParam.Add("SecurityOpenAccess","Disabled")
89 | } else {
90 | $hashParam.Add("SecurityOpenAccess","Enabled")
91 | }
92 |
93 | if ($lab.SharedGalleryId) {
94 |
95 | $image = Get-AzGalleryImageDefinition -ResourceId $lab.SharedGalleryId
96 | $hashParam.Add("ImageOffer", $image.Identifier.Offer)
97 | $hashParam.Add("ImagePublisher", $image.Identifier.Publisher)
98 | $hashParam.Add("ImageSku", $image.Identifier.Sku)
99 | $hashParam.Add("ImageVersion", "latest")
100 | } else {
101 | $images = Get-Variable -Name $lab.LabPlanName -ValueOnly
102 | $image = $images | Where-Object { ($_.DisplayName -eq $lab.ImageName) -and ($_.EnabledState.ToString() -eq "Enabled")}
103 | if (-not ($image)) {
104 | Write-Host "Unable to find an image with display name $($lab.ImageName)"
105 | exit
106 | }
107 | if (($image | Measure-Object).Count -gt 1) {
108 | Write-Host "Found multiples images with display name $($lab.ImageName)"
109 | exit
110 | }
111 | if ($image.EnabledState.ToString() -ne "Enabled") {
112 | Write-Host "Image $($image.DisplayName) not enabled, enabling."
113 | Update-AzLabServicesPlanImage -LabPlanName $lab.LabPlanName -ResourceGroupName $lab.ResourceGroupName -Name $image.Name -EnabledState "Enabled"
114 | New-Variable -Name $lab.LabPlanName -Value $(Get-AzLabServicesPlanImage -ResourceGroupName $lab.ResourceGroupName -LabPlanName $lab.LabPlanName) -Force
115 | }
116 |
117 | $hashParam.Add("ImageOffer", $image.Offer)
118 | $hashParam.Add("ImagePublisher", $image.Publisher)
119 | $hashParam.Add("ImageSku", $image.Sku)
120 | $hashParam.Add("ImageVersion", $image.Version)
121 | }
122 |
123 | #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
124 | # Create Users array
125 | $hashParam.Add("LabUsers", $lab.Emails)
126 | #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
127 | # Create Schedules Array
128 | $scheduleArray = @()
129 | if ($lab.Schedules) {
130 | foreach($schedule in $lab.Schedules) {
131 | $sdate = [datetime]::Parse($schedule.FromDate)
132 | $stime = [datetime]::Parse($schedule.StartTime.Replace('"',''))
133 | $startd = [datetime]::New($sdate.Year, $sdate.Month, $sdate.Day, $stime.Hour, $stime.Minute, 0)
134 | $fullStart = $startd.ToString('u')
135 |
136 | $etime = [datetime]::Parse($schedule.EndTime.Replace('"',''))
137 | $endd = [datetime]::New($sdate.Year, $sdate.Month, $sdate.Day, $etime.Hour, $etime.Minute, 0)
138 | $fullEnd = $endd.ToString('u')
139 |
140 | $edate = [datetime]::Parse($schedule.ToDate.Replace('"',''))
141 | $duntil = [datetime]::New($edate.Year, $edate.Month, $edate.Day, 23, 59, 59)
142 | $fullUntil = $duntil.ToString('u')
143 |
144 | $weekdays = @() #$null
145 | foreach ($day in ($schedule.WeekDays -Split ";")) {
146 | $weekdays += $day.Trim("""").ToString()
147 | }
148 |
149 |
150 | if ($schedule.Frequency -eq "Once") {
151 | $singleEvent = @{
152 | startAt = $fullStart
153 | stopAt = $fullEnd
154 | timeZoneId = $($schedule.TimeZoneId)
155 | notes = $($schedule.Notes)
156 | }
157 | $scheduleArray += $singleEvent
158 | } else {
159 | $repeatEvent = @{
160 | startAt = $fullStart
161 | stopAt = $fullEnd
162 | timeZoneId = $($schedule.TimeZoneId)
163 | notes = $($schedule.Notes)
164 | recurrencePattern = @{
165 | frequency = $($schedule.Frequency)
166 | weekDays = $weekdays
167 | interval = 1
168 | expirationDate = $(Get-Date $fullUntil)
169 | }
170 | }
171 | $scheduleArray += $repeatEvent
172 | }
173 | }
174 | $hashParam.Add("LabSchedules", $scheduleArray)
175 | } else {
176 | $hashParam.Add("LabSchedules", @())
177 | }
178 | #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
179 |
180 | Write-Host "Start creating lab $($lab.LabName)"
181 | $key = $lab.ResourceGroupName + ":" + $lab.LabName
182 | $value = New-AzResourceGroupDeployment -Name $lab.LabName -AsJob -ResourceGroupName $($lab.ResourceGroupName) -TemplateFile $ARMFile -TemplateParameterObject $hashParam | Get-Job
183 | $jobs.Add($key,$value)
184 |
185 | }
186 |
187 | Watch-Jobs -Labs $labs -Jobs $jobs -ResultColumnName "CreateLabResult" | Out-Null
188 |
189 | Write-Host "All Labs Creation finished, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
190 |
191 | $labs | Export-LabsCsv -CsvConfigFile $CsvOutputFile -Force:$true
192 |
193 |
194 | #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
195 |
196 | if ($true) {
197 | #if ($publish.IsPresent) {
198 | $jobs = @{}
199 | $pubscriptstartTime = Get-Date
200 | Write-Host "Executing Lab Creation Publish Script, starting at $pubscriptstartTime" -ForegroundColor Green
201 |
202 | foreach ($lab in $labs) {
203 | $key = $lab.ResourceGroupName + ":" + $lab.LabName
204 | $value = Publish-AzLabServicesLab -Name $lab.LabName -ResourceGroupName $lab.ResourceGroupName -AsJob | Get-Job
205 | $jobs.Add($key,$value)
206 | }
207 |
208 | Watch-Jobs -Labs $labs -Jobs $jobs -ResultColumnName "PublishLabResult" | Out-Null
209 | $labs | Export-LabsCsv -CsvConfigFile $CsvOutputFile -Force:$true
210 | Write-Host "Lab Publish finished, total duration $([math]::Round(((Get-Date) - $pubscriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
211 |
212 | }
213 |
214 | Write-Host "Completed running Bulk Lab ARM Creation script, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
215 |
216 |
--------------------------------------------------------------------------------
/LabManagement/ARM/LabTemplate_Sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "Title": {
6 | "type": "string"
7 | },
8 | "LabName": {
9 | "type": "String"
10 | },
11 | "LabPlanName": {
12 | "type": "String"
13 | },
14 | "Location": {
15 | "type": "String"
16 | },
17 | "SSHAccess":{
18 | "type":"String",
19 | "allowedValues": ["Public","Private","None"],
20 | "defaultValue": "None"
21 | },
22 | "RDPAccess": {
23 | "type": "string",
24 | "allowedValues": ["Public","Private","None"],
25 | "defaultValue": "Public"
26 | },
27 | "AdminUser": {
28 | "type": "string"
29 | },
30 | "AdminPassword": {
31 | "type": "securestring"
32 | },
33 | "SkuSize": {
34 | "type": "string"
35 | },
36 | "Capacity": {
37 | "type": "int"
38 | },
39 | "GpuDrivers": {
40 | "type": "string"
41 | },
42 | "UsageQuota": {
43 | "type": "string"
44 | },
45 | "SecurityOpenAccess": {
46 | "type": "string",
47 | "allowedValues": ["Enabled", "Disabled"]
48 | },
49 | "SharedPassword": {
50 | "type": "string",
51 | "allowedValues": ["Enabled","Disabled"]
52 | },
53 | "DisconnectDelay": {
54 | "type": "string"
55 | },
56 | "NoConnectDelay": {
57 | "type": "string"
58 | },
59 | "IdleDelay": {
60 | "type": "string"
61 | },
62 | "ImageOffer": {
63 | "type": "string"
64 | },
65 | "ImagePublisher": {
66 | "type": "string"
67 | },
68 | "ImageSku": {
69 | "type": "string"
70 | },
71 | "ImageVersion": {
72 | "type": "string"
73 | },
74 | "AadGroupId":{
75 | "type": "string"
76 | },
77 | "LabUsers": {
78 | "type": "array"
79 | },
80 | "LabSchedules": {
81 | "type": "array"
82 | },
83 | "Tags": {
84 | "type": "object"
85 | }
86 | },
87 | "variables": {
88 | "disconnectDelayVar": "[if(equals(parameters('DisconnectDelay'), '0'), '15', parameters('DisconnectDelay'))]",
89 | "noConnectDelayVar": "[if(equals(parameters('noConnectDelay'), '0'), '15', parameters('noConnectDelay'))]",
90 | "idleDelayVar": "[if(equals(parameters('idleDelay'), '0'), '15', parameters('idleDelay'))]"
91 | },
92 | "resources": [
93 | {
94 | "type": "Microsoft.LabServices/labs",
95 | "apiVersion": "2021-11-15-preview",
96 | "name": "[parameters('LabName')]",
97 | "location": "[parameters('Location')]",
98 | "tags": "[parameters('Tags')]",
99 | "properties": {
100 | "networkProfile": {},
101 | "autoShutdownProfile": {
102 | "shutdownOnDisconnect": "[if(equals(parameters('DisconnectDelay'), '0'), 'Disabled', 'Enabled')]",
103 | "shutdownWhenNotConnected": "[if(equals(parameters('NoConnectDelay'), '0'), 'Disabled', 'Enabled')]",
104 | "shutdownOnIdle": "[if(equals(parameters('IdleDelay'), '0'), 'None', 'UserAbsence')]",
105 | "disconnectDelay": "[concat('PT', variables('disconnectDelayVar'), 'M')]",
106 | "noConnectDelay": "[concat('PT', variables('noConnectDelayVar'), 'M')]",
107 | "idleDelay": "[concat('PT', variables('idleDelayVar'), 'M')]"
108 | },
109 | "connectionProfile": {
110 | "webSshAccess": "None",
111 | "webRdpAccess": "None",
112 | "clientSshAccess": "[parameters('SSHAccess')]",
113 | "clientRdpAccess": "[parameters('RDPAccess')]"
114 | },
115 | "virtualMachineProfile": {
116 | "createOption": "Image",
117 | "imageReference": {
118 | "offer": "[parameters('ImageOffer')]",
119 | "publisher": "[parameters('ImagePublisher')]",
120 | "sku": "[parameters('ImageSku')]",
121 | "version": "[parameters('ImageVersion')]"
122 | },
123 | "sku": {
124 | "name": "[parameters('SkuSize')]",
125 | "capacity": "[parameters('Capacity')]"
126 | },
127 | "additionalCapabilities": {
128 | "installGpuDrivers": "[parameters('GpuDrivers')]"
129 | },
130 | "usageQuota": "[concat('PT', parameters('UsageQuota'), 'H')]",
131 | "useSharedPassword": "[parameters('SharedPassword')]",
132 | "adminUser": {
133 | "username": "[parameters('AdminUser')]",
134 | "password" : "[parameters('AdminPassword')]"
135 | }
136 | },
137 | "securityProfile": {
138 | "openAccess": "[parameters('SecurityOpenAccess')]"
139 | },
140 | "rosterProfile": {
141 | "activeDirectoryGroupId": "[if(equals(parameters('AadGroupId'),''), json('null'), parameters('AadGroupId'))]"
142 | },
143 | "labPlanId": "[resourceId('Microsoft.LabServices/labPlans', parameters('LabPlanName'))]",
144 | "title": "[parameters('Title')]"
145 | }
146 | },
147 | {
148 | "condition": "[not(empty(parameters('LabUsers')))]",
149 | "type": "Microsoft.LabServices/labs/users",
150 | "apiVersion": "2021-11-15-preview",
151 | "name": "[concat(parameters('LabName'), '/', uniqueString(parameters('LabUsers')[copyIndex()]))]",
152 | "dependsOn": [
153 | "[resourceId('Microsoft.LabServices/labs', parameters('LabName'))]"
154 | ],
155 | "properties": {
156 | "email": "[parameters('LabUsers')[copyIndex()]]",
157 | "additionalUsageQuota": "PT0S"
158 | },
159 | "copy":{
160 | "name": "usercopy",
161 | "count": "[length(parameters('LabUsers'))]"
162 | }
163 | },
164 | {
165 | "condition": "[not(empty(parameters('LabSchedules')))]",
166 | "type": "Microsoft.LabServices/labs/schedules",
167 | "apiVersion": "2021-11-15-preview",
168 | "name": "[concat(parameters('LabName'), '/', uniqueString(parameters('LabSchedules')[copyIndex()].startat))]",
169 | "dependsOn": [
170 | "[resourceId('Microsoft.LabServices/labs', parameters('LabName'))]"
171 | ],
172 | "properties": "[parameters('LabSchedules')[copyIndex()]]",
173 | "copy":{
174 | "name": "schedulecopy",
175 | "count": "[length(parameters('LabSchedules'))]"
176 | }
177 | }
178 | ]
179 | }
180 |
--------------------------------------------------------------------------------
/LabManagement/ARM/LabTemplate_Simple_.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "LabName": {
6 | "type": "String"
7 | },
8 | "LabPlanName": {
9 | "type": "String"
10 | },
11 | "Location": {
12 | "type": "String"
13 | },
14 | "AdminUser": {
15 | "type": "string"
16 | },
17 | "AdminPassword": {
18 | "type": "string"
19 | }
20 | },
21 | "variables": {},
22 | "resources": [
23 | {
24 | "type": "Microsoft.LabServices/labs",
25 | "apiVersion": "2021-11-15-preview",
26 | "name": "[parameters('LabName')]",
27 | "location": "[parameters('Location')]",
28 | "properties": {
29 | "networkProfile": {},
30 | "autoShutdownProfile": {
31 | "shutdownOnDisconnect": "Disabled",
32 | "shutdownWhenNotConnected": "Disabled",
33 | "shutdownOnIdle": "None",
34 | "disconnectDelay": "PT15M",
35 | "noConnectDelay": "PT15M",
36 | "idleDelay": "PT15M"
37 | },
38 | "connectionProfile": {
39 | "webSshAccess": "None",
40 | "webRdpAccess": "None",
41 | "clientSshAccess": "None",
42 | "clientRdpAccess": "Public"
43 | },
44 | "virtualMachineProfile": {
45 | "createOption": "Image",
46 | "imageReference": {
47 | "offer": "windows-11",
48 | "publisher": "microsoftwindowsdesktop",
49 | "sku": "win11-21h2-pro",
50 | "version": "latest"
51 | },
52 | "sku": {
53 | "name": "Classic_Fsv2_2_4GB_128_S_SSD",
54 | "capacity": 6
55 | },
56 | "additionalCapabilities": {
57 | "installGpuDrivers": "Disabled"
58 | },
59 | "usageQuota": "PT10H",
60 | "useSharedPassword": "Enabled",
61 | "adminUser": {
62 | "username": "[parameters('AdminUser')]",
63 | "password" : "[parameters('AdminPassword')]"
64 | }
65 | },
66 | "securityProfile": {
67 | "openAccess": "Disabled"
68 | },
69 | "rosterProfile": {},
70 | "labPlanId": "[resourceId('Microsoft.LabServices/labPlans', parameters('LabPlanName'))]",
71 | "title": "[parameters('LabName')]"
72 | }
73 | }
74 | ]
75 | }
--------------------------------------------------------------------------------
/LabManagement/ARM/README.md:
--------------------------------------------------------------------------------
1 | # Lab Services ARM support
2 |
3 | ## Simple ARM example.
4 | This section contains a simple lab creation using an ARM template with a PowerShell script to execute the Resource Group deployment.
5 | - LabTemplate_Simple_.json
6 | - This ARM template has only five input parameters (LabName, LabPlanName, Location, AdminUser, AdminPassword) the rest of the options are hardcoded into the template.
7 | - SimpleArmDeploy.ps1
8 | - Passes the parameters into the ResourceGroupDeployment.
9 |
10 | ## Bulk deployment using ARM template.
11 |
12 | Use a CSV file to create multiple labs asynchronously
13 | - BellowsCollegeLabs_Sample.csv
14 | - Lab to be created CSV sample.
15 | - BC_CompSci_AI_200_Schedule_Sample.csv
16 | - Schedules for the labs sample.
17 | - Bulk_CreateLab_ARM.ps1
18 | - Create Labs, Add Users, add schedules
19 |
--------------------------------------------------------------------------------
/LabManagement/ARM/SimpleArmDeploy.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 |
4 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
5 | [ValidateNotNullOrEmpty()]
6 | [string] $LabPlanName,
7 |
8 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
9 | [ValidateNotNullOrEmpty()]
10 | [string] $LabName,
11 |
12 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
13 | [ValidateNotNullOrEmpty()]
14 | [string] $ResourceGroupName,
15 |
16 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
17 | [ValidateNotNullOrEmpty()]
18 | [string] $Location,
19 |
20 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
21 | [ValidateNotNullOrEmpty()]
22 | [string] $AdminName,
23 |
24 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
25 | [ValidateNotNullOrEmpty()]
26 | [string] $AdminPassword,
27 |
28 |
29 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
30 | [ValidateNotNullOrEmpty()]
31 | [string] $ARMFile
32 |
33 | )
34 |
35 |
36 | Set-StrictMode -Version Latest
37 | $ErrorActionPreference = 'Stop'
38 |
39 | $hashParameters = @{LabName = $LabName; LabPlanName = $LabPlanName; Location = $Location; AdminUser = $AdminName; AdminPassword = $AdminPassword}
40 | New-AzResourceGroupDeployment -Name "TestDeploy" -AsJob -ResourceGroupName $ResourceGroupName -TemplateFile $ARMFile -TemplateParameterObject $hashParameters
41 |
42 | Get-Job | Wait-Job
43 |
44 |
--------------------------------------------------------------------------------
/LabManagement/ARM/Utilities.psm1:
--------------------------------------------------------------------------------
1 |
2 | Set-StrictMode -Version Latest
3 | $ErrorActionPreference = 'Stop'
4 |
5 | function Import-LabsCsv {
6 | param(
7 | [parameter(Mandatory = $true, ValueFromPipeline = $true)]
8 | [string]
9 | $CsvConfigFile
10 | )
11 |
12 | function Import-Schedules {
13 | param($schedules)
14 |
15 | $file = "./$schedules.csv"
16 |
17 | $scheds = Import-Csv $file
18 | $scheds | Foreach-Object {
19 | $_.WeekDays = ($_.WeekDays.Split(',')).Trim().Replace("; ", ";")
20 | }
21 | return $scheds
22 | }
23 |
24 | $labs = Import-Csv -Path $CsvConfigFile
25 |
26 | Write-Verbose ($labs | Format-Table | Out-String)
27 |
28 | # Validate that if a resource group\lab account appears more than once in the csv, that it also has the same SharedGalleryId and EnableSharedGalleryImages values.
29 | $lacs = $labs | Select-Object -Property ResourceGroupName, LabAccountName, SharedGalleryId, EnableSharedGalleryImages, Tags | Sort-Object -Property ResourceGroupName, LabAccountName
30 | $lacNames = $lacs | Select-Object -Property ResourceGroupName, LabAccountName, Tags -Unique
31 |
32 | foreach ($lacName in $lacNames){
33 | $matchLacs = $lacs | Where-Object {$_.ResourceGroupName -eq $lacName.ResourceGroupName -and $_.LabAccountName -eq $lacName.LabAccountName}
34 | $firstLac = $matchLacs[0]
35 |
36 | $mismatchSIGs = $matchLacs | Where-Object {$_.SharedGalleryId -ne $firstLac.SharedGalleryId -or $_.EnableSharedGalleryImages -ne $firstLac.EnableSharedGalleryImages}
37 | $mismatchSIGs | Foreach-Object {
38 | $msg1 = "SharedGalleryId - Expected: $($firstLac.SharedGalleryId) Actual: $($_.SharedGalleryId)"
39 | $msg2 = "EnabledSharedGalleryImages - Expected: $($firstLac.EnableSharedGalleryImages) Actual: $($_.EnableSharedGalleryImages)"
40 | Write-Error "Lab account $lacName SharedGalleryId and EnableSharedGalleryImages values are not consistent. $msg1. $msg2."
41 | }
42 | }
43 |
44 | $labs | ForEach-Object {
45 |
46 | # First thing, we need to save the original properties in case they're needed later (for export)
47 | Add-Member -InputObject $_ -MemberType NoteProperty -Name OriginalProperties -Value $_.PsObject.Copy()
48 |
49 | # Validate that the name is good, before we start creating labs
50 | if (-not ($_.LabName -match "^[a-zA-Z0-9_, '`"!|-]*$")) {
51 | Write-Error "Lab Name '$($_.LabName)' can't contain special characters..."
52 | }
53 |
54 | if ((Get-Member -InputObject $_ -Name 'AadGroupId') -and ($_.AadGroupId)) {
55 | # Validate that the aadGroupId (if it exists) isn't a null guid since that's not valid (it's in the default csv this way)
56 | if ($_.AadGroupId -ieq "00000000-0000-0000-0000-000000000000") {
57 | Write-Error "AadGroupId cannot be all 0's for Lab '$($_.LabName)', please enter a valid AadGroupId"
58 | }
59 |
60 | # We have to ensure
61 | if ((Get-Member -InputObject $_ -Name 'MaxUsers') -and ($_.MaxUsers)) {
62 | Write-Warning "Max users and AadGroupId cannot be specified together, MaxUsers will be ignored for lab '$($_.LabName)'"
63 | $_.MaxUsers = ""
64 | }
65 | }
66 |
67 | # Checking to ensure the user has changed the example username/passwork in CSV files
68 | if ($_.UserName -and ($_.UserName -ieq "test0000")) {
69 | Write-Warning "Lab $($_.LabName) is using the default UserName from the example CSV, please update it for security reasons"
70 | }
71 | if ($_.Password -and ($_.Password -ieq "Test00000000")) {
72 | Write-Warning "Lab $($_.LabName) is using the default Password from the example CSV, please update it for security reasons"
73 | }
74 |
75 | if ((Get-Member -InputObject $_ -Name 'Emails') -and ($_.Emails)) {
76 | # This is to force a single email to an array
77 | $emailValues = @()
78 | $emailValues += ($_.Emails.Split(';')).Trim()
79 | $_.Emails = $emailValues
80 | }
81 | else {
82 | #Assign to empty array since New-AzLab expects this property to exist, but this property should be optional in the csv
83 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "Emails" -Value @() -Force
84 | }
85 |
86 | if ((Get-Member -InputObject $_ -Name 'LabOwnerEmails') -and ($_.LabOwnerEmails)) {
87 | $_.LabOwnerEmails = ($_.LabOwnerEmails.Split(';')).Trim()
88 | }
89 | else {
90 | #Assign to empty array since New-AzLab expects this property to exist, but this property should be optional in the csv
91 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "LabOwnerEmails" -Value @() -Force
92 | }
93 |
94 | # TODO: There are two odd things about this code:
95 | # 1.) The name of this property on the input object is "SharedPassword", but the the New-AzLab function expects the parameter name to be "SharedPasswordEnabled".
96 | # 2.) The Set-AzLab function expects this property to have Enabled\Disabled values, but New-AzLab expects true\false.
97 | # In the future, we need to resolve these inconsistencies. It works right now because:
98 | # 1.) New-AzLabsBulk calls New-AzLab to create a new lab. When New-AzLab is called, it ignores this "SharedPassword" property value and defaults it to False because New-AzLab is expecting the property to be named "SharedPasswordEnabled".
99 | # 2.) New-AzLabsBulk then calls Set-AzLab. When Set-AzLab is called, the value of this "SharedPassword" property is explicitly passed in as the "SharedPasswordEnabled" parameter.
100 | if (Get-Member -InputObject $_ -Name 'SharedPassword') {
101 | if (($_.SharedPassword -ne "Enabled") -or ($_.SharedPassword -ne "Disabled")) {
102 | if (($_.SharedPassword -eq "True") -or ($_.SharedPassword -eq "False")) {
103 | if ([System.Convert]::ToBoolean($_.SharedPassword)) {
104 | $_.SharedPassword = 'Enabled'
105 | } else {
106 | $_.SharedPassword = 'Disabled'
107 | }
108 | } else {
109 | $_.SharedPassword = 'Disabled'
110 | }
111 | }
112 | } else {
113 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "SharedPassword" -Value 'Disabled' -Force
114 | }
115 |
116 | if (Get-Member -InputObject $_ -Name 'GpuDriverEnabled') {
117 | if (($_.GpuDriverEnabled -eq "True") -or ($_.GpuDriverEnabled -eq "False")) {
118 | if ([System.Convert]::ToBoolean($_.GpuDriverEnabled)) {
119 | $_.GpuDriverEnabled = $True
120 | } else {
121 | $_.GpuDriverEnabled = $False
122 | }
123 | } else {
124 | $_.GpuDriverEnabled = $False
125 | }
126 | } else {
127 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "GpuDriverEnabled" -Value $false -Force
128 | }
129 |
130 | if (Get-Member -InputObject $_ -Name 'LinuxRdp') {
131 | if (($_.LinuxRdp -eq "True") -or ($_.GpuDriverEnabled -eq "False")) {
132 | if ([System.Convert]::ToBoolean($_.LinuxRdp)) {
133 | $_.LinuxRdp = $true
134 | } else {
135 | $_.LinuxRdp = $false
136 | }
137 | } else {
138 | $_.LinuxRdp = $false
139 | }
140 | }
141 | else {
142 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "LinuxRdp" -Value $false -Force
143 | }
144 |
145 | if ((Get-Member -InputObject $_ -Name 'Schedules') -and ($_.Schedules)) {
146 | Write-Verbose "Setting schedules for $($_.LabName)"
147 | $_.Schedules = Import-Schedules -schedules $_.Schedules
148 | }
149 | else {
150 | #Assign to empty array since New-AzLab expects this property to exist, but this property should be optional in the csv
151 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "Schedules" -Value @() -Force
152 | }
153 |
154 | }
155 |
156 | Write-Verbose ($labs | ConvertTo-Json -Depth 10 | Out-String)
157 |
158 | return ,$labs # PS1 Magick here, the comma is actually needed. Don't ask why.
159 | # Ok, here is why, PS1 puts each object in the collection on the pipeline one by one
160 | # unless you say explicitely that you want to pass it as a single object
161 | }
162 |
163 | function Export-LabsCsv {
164 | param(
165 | [parameter(Mandatory = $true, ValueFromPipeline = $true)]
166 | [object[]]
167 | $labs,
168 |
169 | [parameter(Mandatory = $true)]
170 | [string]
171 | $CsvConfigFile,
172 |
173 | [parameter(Mandatory = $false)]
174 | [switch] $Force
175 | )
176 |
177 | begin
178 | {
179 | $outArray = @()
180 | }
181 |
182 | process
183 | {
184 | # Iterate over the labs and pull out the inner properties (orig object) and add in result fields
185 | $labs | ForEach-Object {
186 | $obj = $_
187 |
188 | # If we don't have the underlying properties, need to bail out
189 | if (-not (Get-Member -InputObject $_ -Name OriginalProperties)) {
190 | Write-Error "Cannot write out labs CSV, input labs object doesn't contain original properties"
191 | }
192 |
193 | $outObj = $_.OriginalProperties
194 |
195 | # We need to copy any 'result' fields over to the original object we're writing out
196 | Get-Member -InputObject $obj -Name "*Result" | ForEach-Object {
197 | if (Get-Member -InputObject $outObj -Name $_.Name) {
198 | $outObj.$($_.Name) = $obj.$($_.Name)
199 | }
200 | else {
201 | Add-Member -InputObject $outObj -MemberType NoteProperty -Name $_.Name $obj.$($_.Name)
202 | }
203 | }
204 |
205 | # Add the object to the cumulative array
206 | $outArray += $outObj
207 | }
208 | }
209 |
210 | end
211 | {
212 | if ($Force.IsPresent) {
213 | $outArray | Export-Csv -Path $CsvConfigFile -NoTypeInformation -Force
214 | }
215 | else {
216 | $outArray | Export-Csv -Path $CsvConfigFile -NoTypeInformation -NoClobber
217 | }
218 | }
219 | }
220 |
221 | function Watch-Jobs {
222 | param (
223 | [parameter(Mandatory = $true, ValueFromPipeline = $true)]
224 | [object[]]
225 | $labs,
226 |
227 | [parameter(Mandatory = $true, ValueFromPipeline = $true)]
228 | [hashtable]
229 | $jobs,
230 |
231 | [parameter(Mandatory = $true, ValueFromPipeline = $true)]
232 | [string]
233 | $resultColumnName
234 |
235 | )
236 |
237 | Write-Host "Waiting for jobs to complete."
238 | $jobs.Values | Wait-Job
239 |
240 | foreach ($currentJob in $jobs.GetEnumerator()) {
241 |
242 | $labForJob = $labs | Where-Object {($_.ResourceGroupName -eq $($currentJob.Key.Split(":")[0])) -and ($_.LabName -eq $($currentJob.Key.Split(":")[1]))}
243 | if ($labForJob) {
244 |
245 | if ([string]::IsNullOrEmpty($currentJob.Value.Error) -and [string]::IsNullOrEmpty($currentJob.Value.Warning)) {
246 | Add-Member -InputObject $labForJob -MemberType NoteProperty -Name $resultColumnName -Value "Success" -Force
247 | } else {
248 | Add-Member -InputObject $labForJob -MemberType NoteProperty -Name $resultColumnName -Value "Failed: $($currentJob.Value.Warning + ":" + $currentJob.Value.Error)" -Force
249 | }
250 | Remove-Job -Job $currentJob.Value | Out-Null
251 | } else {
252 | Write-Host "Unable to match job with lab: $($currentJob.Key)"
253 | }
254 | }
255 | return $labs
256 | }
257 |
258 | Export-ModuleMember -Function Import-LabsCsv,
259 | Export-LabsCsv,
260 | Watch-Jobs
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/AADGroupMembers.csv:
--------------------------------------------------------------------------------
1 | AADGroupName,EMail
2 | AzureLabs_Class1,exampleuser1@contoso.com
3 | AzureLabs_Class1,exampleuser2@contoso.com
4 | AzureLabs_Class1,exampleuser3@contoso.com
5 | AzureLabs_Class1,exampleuser4@contoso.com
6 | AzureLabs_Class1,exampleuser5@contoso.com
7 | AzureLabs_Class1,exampleuser6@contoso.com
8 | AzureLabs_Class2,exampleuser1@contoso.com
9 | AzureLabs_Class2,exampleuser2@contoso.com
10 | AzureLabs_Class2,exampleuser7@contoso.com
11 | AzureLabs_Class2,exampleuser8@contoso.com
12 | AzureLabs_Class2,exampleuser9@contoso.com
13 | AzureLabs_Class2,exampleuser10@contoso.com
14 | AzureLabs_Class2,exampleuser11@contoso.com
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/Add-AzLabRoleAssignments.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [string]
5 | $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
8 | [string]
9 | $RoleDefinitionName
10 | )
11 |
12 | Set-StrictMode -Version Latest
13 | Import-Module -Name Az.Resources -Force
14 |
15 | # Make sure the input file does exist
16 | if (-not (Test-Path -Path $CsvConfigFile)) {
17 | Write-Error "Input CSV File must exist, please choose a valid file location..."
18 | }
19 |
20 | $scriptstartTime = Get-Date
21 | Write-Host "Executing bulk teacher permission add script, starting at $scriptstartTime" -ForegroundColor Green
22 |
23 | # Import the CSV file (plain import not using the library)
24 | $labs = Import-Csv -Path $CsvConfigFile
25 |
26 | # Loop through the labs to apply role assignments
27 | foreach ($lab in $labs) {
28 |
29 | # continue only if we have 'Teachers' column and it has data
30 | if ($lab.PSObject.Properties['Teachers'] -and $lab.Teachers)
31 | {
32 | # Get the lab
33 | $labObj = Get-AzResource -ResourceGroupName $lab.ResourceGroupName -Name $lab.LabName
34 |
35 | $lab.Teachers.Split(";") | ForEach-Object {
36 | # make sure we didn't accidently have an extra semicolon with empty data
37 | if ($_) {
38 |
39 | $roleAssignment = Get-AzRoleAssignment -SignInName $_ -Scope $labObj.ResourceId -RoleDefinitionName $RoleDefinitionName -ErrorAction SilentlyContinue
40 | if ($roleAssignment) {
41 | Write-Host "Role assignment for teacher $_ already exists in lab $($lab.LabName) in Resource Group Name $($lab.ResourceGroupName)" -ForegroundColor Yellow
42 | }
43 | else {
44 | Write-Host "Adding role assignment for teacher $_ in lab $($lab.LabName) in Resource Group Name $($lab.ResourceGroupName)"
45 | New-AzRoleAssignment -SignInName $_ -RoleDefinitionName $RoleDefinitionName -Scope $labObj.ResourceId | Out-Null
46 | }
47 | }
48 | }
49 | }
50 | }
51 |
52 | Write-Host "Completed bulk teacher permission add script, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/Add-LabUsers.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [ValidateNotNullOrEmpty()]
5 | [string] $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
8 | [ValidateNotNullOrEmpty()]
9 | [string] $CsvOutputFile,
10 |
11 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
12 | [ValidateNotNullOrEmpty()]
13 | [switch] $force,
14 |
15 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
16 | [int] $ThrottleLimit = 10
17 | )
18 |
19 | Set-StrictMode -Version Latest
20 | $ErrorActionPreference = 'Stop'
21 |
22 | # Make sure the input file does exist
23 | if (-not (Test-Path -Path $CsvConfigFile)) {
24 | Write-Error "Input CSV File must exist, please choose a valid file location..."
25 | }
26 |
27 | # Make sure the output file doesn't exist
28 | if ((Test-Path -Path $CsvOutputFile) -and (-not $force.IsPresent)) {
29 | Write-Error "Output File cannot already exist, please choose a location to create a new output file..."
30 | }
31 |
32 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
33 |
34 | $scriptstartTime = Get-Date
35 | Write-Host "Executing Lab Creation Script, starting at $scriptstartTime" -ForegroundColor Green
36 |
37 | $labs = $CsvConfigFile | Import-LabsCsv | Add-AzLabsUsersBulk -ThrottleLimit $ThrottleLimit
38 |
39 | $labs | Export-LabsCsv -CsvConfigFile $CsvOutputFile -Force:$force.IsPresent
40 |
41 | Write-Host "Completed running Bulk Lab Creation script, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
42 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/BC_CompSci_AI_200_Schedule_Sample.csv:
--------------------------------------------------------------------------------
1 | Frequency,FromDate,ToDate,StartTime,EndTime,WeekDays,TimeZoneId, Notes
2 | Weekly,12/1/2022," ""12/24/2022"""," ""10:00"""," ""12:00"""," ""Monday; Friday""",America/Los_Angeles," ""A recurrent class"""
3 | Once,12/1/2022," ""12/1/2022"""," ""08:00"""," ""09:30"""," """"",America/Los_Angeles," ""A workshop"""
4 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/BellowsCollegeLabs_Sample.csv:
--------------------------------------------------------------------------------
1 | Id,Tags,ResourceGroupName,Location,LabPlanName,LabName,SharedGalleryId,ImageName,AadGroupId,MaxUsers,UsageQuota,UsageMode,SharedPassword,Size,Title,Descr,TemplateVmState, UserName,Password, NonAdminUserName,NonAdminPassword,LinuxRdp, Emails,LabOwnerEmails, idleGracePeriod, idleOsGracePeriod, idleNoConnectGracePeriod, Invitation, Schedules
2 | id001,Tag=District1;Level=Senior,BellowsCollege_rg,westcentralus,BellowsCollege_Econ_plan,BC_Economics_101,,Ubuntu Server 20.04 LTS,,2,10,Restricted,ENABLED,Classic_Fsv2_2_4GB_128_S_SSD,MacroEconomics 101,Beginning MacroEconomics,Enabled,testadmin,TestAdmin00000000,testnonadmin,TestNonAdmin00000000,TRUE,AndreLawson@bellowsCollege.com;JuanMorgan@bellowsCollege.com,r_e_best@hotmail.com,,,,,
3 | id002,Tag=District1;Level=Junior,BellowsCollege_rg,westcentralus,BellowsCollege_CompSci_plan,BC_CompSci_Office_101,,Visual Studio 2022 Community (latest release) on Windows 11 Enterprise N (x64),,2,20,Restricted,ENABLED,Classic_Fsv2_2_4GB_128_S_SSD,Introduction to Office,"Introduction to Office, including Word, Excel, PowerPoint, and Visio",,testadmin,TestAdmin00000000,testnonadmin,TestNonAdmin00000000,FALSE,AndreLawson@bellowsCollege.com;JuanMorgan@bellowsCollege.com,,,,,Office 101,
4 | id003,Tag=District1;Level=Junior,BellowsCollege_rg,westcentralus,BellowsCollege_CompSci_plan,BC_CompSci_AI_200,,Windows 11 Pro (Gen2),,2,25,Unrestricted,DISABLED,Classic_Fsv2_2_4GB_128_S_SSD,Beginning AI development,Using .Net to develop AI,Disabled,testadmin,TestAdmin00000000,testnonadmin,TestNonAdmin00000000,FALSE,HaydenCook@bellowscollege.com,,15,15,15,AI development,BC_CompSci_AI_200_Schedule_Sample
5 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/Create-AzLabPlans.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [ValidateNotNullOrEmpty()]
5 | [string] $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
8 | [ValidateNotNullOrEmpty()]
9 | [string] $CsvOutputFile,
10 |
11 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
12 | [ValidateNotNullOrEmpty()]
13 | [switch] $force,
14 |
15 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
16 | [int] $ThrottleLimit = 10
17 | )
18 |
19 | Set-StrictMode -Version Latest
20 | $ErrorActionPreference = 'Stop'
21 |
22 | # Make sure the input file does exist
23 | if (-not (Test-Path -Path $CsvConfigFile)) {
24 | Write-Error "Input CSV File must exist, please choose a valid file location..."
25 | }
26 |
27 | # Make sure the output file doesn't exist
28 | if ((Test-Path -Path $CsvOutputFile) -and (-not $force.IsPresent)) {
29 | Write-Error "Output File cannot already exist, please choose a location to create a new output file..."
30 | }
31 |
32 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
33 |
34 | $scriptstartTime = Get-Date
35 | Write-Host "Executing Lab Account Creation Script, starting at $scriptstartTime" -ForegroundColor Green
36 |
37 | $labPlans = $CsvConfigFile | Import-LabsCsv | New-AzLabPlansBulk -ThrottleLimit $ThrottleLimit
38 |
39 | $labPlans | Export-LabsCsv -CsvConfigFile $CsvOutputFile -Force:$force.IsPresent
40 |
41 | Write-Host "Completed running Bulk Lab Account Creation script, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
42 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/Create-AzLabs.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [ValidateNotNullOrEmpty()]
5 | [string] $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
8 | [ValidateNotNullOrEmpty()]
9 | [string] $CsvOutputFile,
10 |
11 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
12 | [ValidateNotNullOrEmpty()]
13 | [switch] $force,
14 |
15 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
16 | [int] $ThrottleLimit = 10
17 | )
18 |
19 | Set-StrictMode -Version Latest
20 | $ErrorActionPreference = 'Stop'
21 |
22 | # Make sure the input file does exist
23 | if (-not (Test-Path -Path $CsvConfigFile)) {
24 | Write-Error "Input CSV File must exist, please choose a valid file location..."
25 | }
26 |
27 | # Make sure the output file doesn't exist
28 | if ((Test-Path -Path $CsvOutputFile) -and (-not $force.IsPresent)) {
29 | Write-Error "Output File cannot already exist, please choose a location to create a new output file..."
30 | }
31 |
32 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
33 |
34 | $scriptstartTime = Get-Date
35 | Write-Host "Executing Lab Creation Script, starting at $scriptstartTime" -ForegroundColor Green
36 |
37 | $labs = $CsvConfigFile | Import-LabsCsv | New-AzLabsBulk -ThrottleLimit $ThrottleLimit
38 |
39 | $labs | Export-LabsCsv -CsvConfigFile $CsvOutputFile -Force:$force.IsPresent
40 |
41 | Write-Host "Completed running Bulk Lab Creation script, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
42 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/Create-Publish-AzLabs.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [string]
5 | $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
8 | [string]
9 | $CsvOutputFile,
10 |
11 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
12 | [ValidateNotNullOrEmpty()]
13 | [switch] $force,
14 |
15 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
16 | [int]
17 | $ThrottleLimit = 10
18 | )
19 |
20 | # Make sure the input file does exist
21 | if (-not (Test-Path -Path $CsvConfigFile)) {
22 | Write-Error "Input CSV File must exist, please choose a valid file location..."
23 | }
24 |
25 | # Make sure the output file doesn't exist
26 | if ((Test-Path -Path $CsvOutputFile) -and (-not $force.IsPresent)) {
27 | Write-Error "Output File cannot already exist, please choose a location to create a new output file..."
28 | }
29 |
30 | $outerScriptstartTime = Get-Date
31 | Write-Host "Executing Lab Creation Script, starting at $outerScriptstartTime" -ForegroundColor Green
32 |
33 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
34 |
35 | $labPlanResults = $CsvConfigFile |
36 | Import-LabsCsv | # Import the CSV File into objects, including validation & transforms
37 | New-AzLabsBulk -ThrottleLimit $ThrottleLimit | # Create all labs
38 | Publish-AzLabsBulk -ThrottleLimit $ThrottleLimit # Publish all the labs
39 |
40 | # Write out the results
41 | $labPlanResults | Export-LabsCsv -CsvConfigFile $CsvOutputFile -Force:$force.IsPresent
42 | $labPlanResults | Select-Object -Property ResourceGroupName, LabPlanName, LabName, LabPlanResult, LabResult, PublishResult | Format-Table
43 |
44 | Write-Host "Completed running Bulk Lab Creation script, total duration $(((Get-Date) - $outerScriptstartTime).TotalMinutes) minutes" -ForegroundColor Green
45 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/CustomRoleAssignments.csv:
--------------------------------------------------------------------------------
1 | ResourceGroupName,LabAccountName,LabAccountCustomRoleEmails
2 | hogwarts-rg2,Muggle,customuser1@contoso.com
3 | hogwarts-rg2,Wizardy,customuser2@contoso.com
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/Get-RegistrationLinks.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [ValidateNotNullOrEmpty()]
5 | [string] $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
8 | [ValidateNotNullOrEmpty()]
9 | [string] $CsvOutputFile,
10 |
11 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
12 | [int] $ThrottleLimit = 10
13 | )
14 |
15 | Set-StrictMode -Version Latest
16 | $ErrorActionPreference = 'Stop'
17 |
18 | # Make sure the input file does exist
19 | if (-not (Test-Path -Path $CsvConfigFile)) {
20 | Write-Error "Input CSV File must exist, please choose a valid file location..."
21 | }
22 |
23 | # Make sure the output file doesn't exist
24 | if (Test-Path -Path $CsvOutputFile) {
25 | Write-Error "Output File cannot already exist, please choose a location to create a new output file..."
26 | }
27 |
28 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
29 |
30 | $scriptstartTime = Get-Date
31 | Write-Host "Executing Script to get all registration links, starting at $scriptstartTime" -ForegroundColor Green
32 |
33 | $labs = $CsvConfigFile | Import-Csv | Get-AzLabsRegistrationLinkBulk -ThrottleLimit $ThrottleLimit
34 |
35 | $labs | Export-Csv -Path $CsvOutputFile -NoTypeInformation
36 |
37 | Write-Host "Completed script to get registration links, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
38 |
39 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/PickALab.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [string]
5 | $CsvConfigFile
6 | )
7 |
8 | #Import-Module ../../Az.LabServices.psm1 -Force
9 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
10 |
11 | $CsvConfigFile | Import-LabsCsv | Show-LabMenu -PickLab | Publish-Labs
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/Publish-AzLabs.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [ValidateNotNullOrEmpty()]
5 | [string] $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
8 | [ValidateNotNullOrEmpty()]
9 | [string] $CsvOutputFile,
10 |
11 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
12 | [int] $ThrottleLimit = 10
13 | )
14 |
15 | Set-StrictMode -Version Latest
16 | $ErrorActionPreference = 'Stop'
17 |
18 | # Make sure the input file does exist
19 | if (-not (Test-Path -Path $CsvConfigFile)) {
20 | Write-Error "Input CSV File must exist, please choose a valid file location..."
21 | }
22 |
23 | # Make sure the output file doesn't exist
24 | if (Test-Path -Path $CsvOutputFile) {
25 | Write-Error "Output File cannot already exist, please choose a location to create a new output file..."
26 | }
27 |
28 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
29 |
30 | $scriptstartTime = Get-Date
31 | Write-Host "Executing Lab Publish Script, starting at $scriptstartTime" -ForegroundColor Green
32 |
33 | $labs = $CsvConfigFile | Import-LabsCsv | Publish-AzLabsBulk -EnableCreatingLabs $false -ThrottleLimit $ThrottleLimit
34 |
35 | $labs | Export-Csv -Path $CsvOutputFile -NoTypeInformation
36 |
37 | Write-Host "Completed running Bulk Lab Publish script, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
38 |
39 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/PublishAll.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [string]
5 | $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
8 | [string]
9 | $CsvOutputFile,
10 |
11 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
12 | [ValidateNotNullOrEmpty()]
13 | [switch] $force,
14 |
15 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
16 | [int]
17 | $ThrottleLimit = 10
18 | )
19 |
20 | # Make sure the input file does exist
21 | if (-not (Test-Path -Path $CsvConfigFile)) {
22 | Write-Error "Input CSV File must exist, please choose a valid file location..."
23 | }
24 |
25 | # Make sure the output file doesn't exist
26 | if ((Test-Path -Path $CsvOutputFile) -and (-not $force.IsPresent)) {
27 | Write-Error "Output File cannot already exist, please choose a location to create a new output file..."
28 | }
29 |
30 | $outerScriptstartTime = Get-Date
31 | Write-Host "Executing Lab Creation Script, starting at $outerScriptstartTime" -ForegroundColor Green
32 |
33 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
34 |
35 | $labAccountResults = $CsvConfigFile |
36 | Import-LabsCsv | # Import the CSV File into objects, including validation & transforms
37 | New-AzLabPlansBulk -ThrottleLimit $ThrottleLimit | # Create all the lab accounts
38 | New-AzLabsBulk -ThrottleLimit $ThrottleLimit | # Create all labs
39 | Publish-AzLabsBulk -ThrottleLimit $ThrottleLimit # Publish all the labs
40 |
41 | # Write out the results
42 | $labAccountResults | Export-LabsCsv -CsvConfigFile $CsvOutputFile -Force:$force.IsPresent
43 | $labAccountResults | Select-Object -Property ResourceGroupName, LabPlanName, LabName, LabPlanResult, LabResult, PublishResult | Format-Table
44 |
45 | Write-Host "Completed running Bulk Lab Creation script, total duration $(((Get-Date) - $outerScriptstartTime).TotalMinutes) minutes" -ForegroundColor Green
46 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/Remove-AzLabPlans.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [ValidateNotNullOrEmpty()]
5 | [string] $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
8 | [switch] $Force
9 | )
10 |
11 | Set-StrictMode -Version Latest
12 | $ErrorActionPreference = 'Stop'
13 |
14 | # Make sure the input file does exist
15 | if (-not (Test-Path -Path $CsvConfigFile)) {
16 | Write-Error "Input CSV File must exist, please choose a valid file location..."
17 | }
18 |
19 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
20 |
21 | $scriptstartTime = Get-Date
22 | Write-Host "Executing Lab Account Deletion Script, starting at $scriptstartTime" -ForegroundColor Green
23 |
24 | $configObjects = $CsvConfigFile | Import-Csv
25 | $labPlans = $configObjects | Select-Object -Property ResourceGroupName, LabPlanName -Unique
26 |
27 | $labPlans | ForEach-Object {
28 | $labPlan = Get-AzLabServicesLabPlan -ResourceGroupName $_.ResourceGroupName -LabPlanName $_.LabPlanName -ErrorAction SilentlyContinue
29 | if ($labPlan) {
30 | $labPlan | Remove-AzLabServicesLabPlan
31 | Write-Host "Initiated removing Lab plan '$($_.LabPlanName)' in resource group '$($_.ResourceGroupName)'"
32 | }
33 | else {
34 | Write-Host "Lab Plan '$($_.LabPlanName)' in resource group '$($_.ResourceGroupName)' doesn't exist, cannot delete..."
35 | }
36 | }
37 |
38 | Write-Host "Completed running Lab Plan deletion, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
39 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/Remove-AzLabs.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [string]
5 | $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
8 | [int]
9 | $ThrottleLimit = 7
10 | )
11 |
12 | Set-StrictMode -Version Latest
13 | $ErrorActionPreference = 'Stop'
14 |
15 | # Make sure the input file does exist
16 | if (-not (Test-Path -Path $CsvConfigFile)) {
17 | Write-Error "Input CSV File must exist, please choose a valid file location..."
18 | }
19 |
20 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
21 |
22 | $scriptstartTime = Get-Date
23 | Write-Host "Executing Bulk Lab deletion Script, starting at $scriptstartTime" -ForegroundColor Green
24 |
25 | $CsvConfigFile | Import-Csv | Remove-AzLabsBulk -ThrottleLimit $ThrottleLimit
26 |
27 | Write-Host "Completed running Bulk Lab deletion Script, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/Remove-Students.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
4 | [string]
5 | $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
8 | [int]
9 | $ThrottleLimit = 10
10 | )
11 |
12 | Set-StrictMode -Version Latest
13 | $ErrorActionPreference = 'Stop'
14 |
15 | # Make sure the input file does exist
16 | Write-Host $CsvConfigFile
17 | if (-not (Test-Path -Path $CsvConfigFile)) {
18 | Write-Error "Input CSV File must exist, please choose a valid file location..."
19 | }
20 |
21 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
22 |
23 | $scriptstartTime = Get-Date
24 | Write-Host "Executing Bulk Student deletion Script, starting at $scriptstartTime" -ForegroundColor Green
25 | $CsvConfigFile | Import-LabsCsv | Remove-AzLabUsersBulk
26 |
27 | Write-Host "Completed running Bulk student deletion Script, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/Reset-StudentAvailableHours.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [string]
5 | $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
8 | [int]
9 | $ThrottleLimit = 5
10 | )
11 |
12 | Set-StrictMode -Version Latest
13 | $ErrorActionPreference = 'Stop'
14 |
15 | # Make sure the input file does exist
16 | if (-not (Test-Path -Path $CsvConfigFile)) {
17 | Write-Error "Input CSV File must exist, please choose a valid file location..."
18 | }
19 |
20 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
21 |
22 | $scriptstartTime = Get-Date
23 | Write-Host "Executing Bulk User Quota Script, starting at $scriptstartTime" -ForegroundColor Green
24 |
25 | $CsvConfigFile | Import-Csv | Reset-AzLabUserQuotaBulk -ThrottleLimit $ThrottleLimit
26 |
27 | Write-Host "Completed running Bulk User Quota script, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
28 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/Send-Invitations.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [ValidateNotNullOrEmpty()]
5 | [string] $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
8 | [ValidateNotNullOrEmpty()]
9 | [string] $CsvOutputFile,
10 |
11 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
12 | [ValidateNotNullOrEmpty()]
13 | [switch] $force,
14 |
15 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
16 | [int] $ThrottleLimit = 10
17 | )
18 |
19 | Set-StrictMode -Version Latest
20 | $ErrorActionPreference = 'Stop'
21 |
22 | # Make sure the input file does exist
23 | if (-not (Test-Path -Path $CsvConfigFile)) {
24 | Write-Error "Input CSV File must exist, please choose a valid file location..."
25 | }
26 |
27 | # Make sure the output file doesn't exist
28 | if ((Test-Path -Path $CsvOutputFile) -and (-not $force.IsPresent)) {
29 | Write-Error "Output File cannot already exist, please choose a location to create a new output file..."
30 | }
31 |
32 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
33 |
34 | $scriptstartTime = Get-Date
35 | Write-Host "Executing Script to send all invitations, starting at $scriptstartTime" -ForegroundColor Green
36 |
37 | $labs = $CsvConfigFile | Import-LabsCsv | Send-AzLabsInvitationBulk -ThrottleLimit $ThrottleLimit
38 |
39 | $labs | Export-LabsCsv -CsvConfigFile $CsvOutputFile -Force:$force.IsPresent
40 |
41 | Write-Host "Completed sending lab invitations, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
42 |
43 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Examples/Validate-AzLabs.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
4 | [ValidateNotNullOrEmpty()]
5 | [string] $CsvConfigFile,
6 |
7 | [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
8 | [ValidateNotNullOrEmpty()]
9 | [string] $CsvOutputFile,
10 |
11 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
12 | [int] $ThrottleLimit = 10,
13 |
14 | [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
15 | [ValidateSet('Created', 'Published')]
16 | [string]
17 | $ExpectedLabState = 'Created'
18 | )
19 |
20 | Set-StrictMode -Version Latest
21 | $ErrorActionPreference = 'Stop'
22 |
23 | # Make sure the input file does exist
24 | if (-not (Test-Path -Path $CsvConfigFile)) {
25 | Write-Error "Input CSV File must exist, please choose a valid file location..."
26 | }
27 |
28 | # Make sure the output file doesn't exist
29 | if (Test-Path -Path $CsvOutputFile) {
30 | Write-Error "Output File cannot already exist, please choose a location to create a new output file..."
31 | }
32 |
33 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
34 |
35 | $scriptstartTime = Get-Date
36 | Write-Host "Executing Lab validation Script, starting at $scriptstartTime" -ForegroundColor Green
37 |
38 | $labs = $CsvConfigFile | Import-LabsCsv | Confirm-AzLabsBulk -ThrottleLimit $ThrottleLimit -ExpectedLabState $ExpectedLabState
39 |
40 | $labs | Export-Csv -Path $CsvOutputFile -NoTypeInformation
41 |
42 | Write-Host "Completed running validation script, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes" -ForegroundColor Green
43 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/BulkOperations/Readme.md:
--------------------------------------------------------------------------------
1 | # Bulk Lab Creation Module
2 | The [Az.LabServices.BulkOperations.psm1](https://github.com/Azure/LabServices/Lab_Management/PowerShell/BulkOperations/Az.LabServices.BulkOperations.psm1) module enables bulk operations on **Resource Groups**, **Lab Plans** and **Labs** via commandline based on declarative configuration information. The standard pattern of usage is by composing a pipeline as follows:
3 |
4 | `Load configuration info from db/csv/...` => `Transform configuration info` => `Publish the labs`
5 |
6 | ## Table of Contents
7 | - [Table of Contents](#table-of-contents)
8 | - [Getting Started](#getting-started)
9 | - [Examples](#examples)
10 | - [Publish all the labs in a CSV file (Examples/PublishAll.ps1)](#publish-all-the-labs-in-a-csv-file-examplespublishallps1)
11 | - [Show a menu asking to select one lab (Examples/PickALab.ps1)](#show-a-menu-asking-to-select-one-lab-examplespickalabps1)
12 | - [Structure of the example BellowsCollegeLabs_Sample.csv](#structure-of-the-example-bellowscollegelabs_samplecsv)
13 | - [Structure of the example BC_CompSci_AI_200_Schedule_Sample.csv](#structure-of-the-example-bc_compsci_ai_200_schedule_sample.csv)
14 |
15 | ## Getting Started
16 | The [Bulk Lab Creation functions](./Examples) can be run from an authenticated Azure PowerShell session and requires [PowerShell](https://github.com/PowerShell/PowerShell/releases) and the [Azure PowerShell](https://docs.microsoft.com/en-us/powershell/azure/) module. The script will automatically install the [ThreadJob](https://docs.microsoft.com/en-us/powershell/module/threadjob) Powershell Module.
17 |
18 | To get started, using the example configuration csv files:
19 |
20 | 1. Get a local copy of the Bulk Operations scripts by either [cloning the repo](https://github.com/Azure/LabServices.git) or by [downloading a copy](https://raw.githubusercontent.com/Azure/LabServices/main/Lab_Management/PowerShell/BulkOperations/Az.LabServices.BulkOperations.psm1)
21 | 1. Get a local copy of the example [BellowsCollegeLabs_Sample.csv](./Examples/BellowsCollegeLabs_Sample.csv) file and example [BC_CompSci_AI_200_Schedule_Sample.csv](./Examples/BC_CompSci_AI_200_Schedule_Sample.csv) file
22 | 1. Launch a PowerShell session
23 | 1. Ensure [Azure PowerShell](https://docs.microsoft.com/en-us/powershell/azure/install-az-ps) installed
24 | 1. Update the example CSV files to configure the resources to be created. For additional labs, create additional lines in the CSV file. The CSV files can be modified directly with Microsoft Excel.
25 |
26 | ## Examples
27 | The functions are generic and can be composed together to achieve different aims. In the following examples, we load the configuration information from a CSV file. The examples work the same if the information is loaded from a database. You need to substitute the first function with a database retrieving one.
28 |
29 | The examples scripts expect the modules used to be in the following directories:
30 |
31 | ```powershell
32 | Import-Module ../Az.LabServices.BulkOperations.psm1 -Force
33 | ```
34 |
35 | The full code for the example is immediately after the title in parenthesis.
36 |
37 | ### Publish all the labs in a CSV file ([Examples/PublishAll.ps1](./Examples/PublishAll.ps1))
38 |
39 | ```powershell
40 | ".\BellowsCollegeLabs_Sample.csv" | Import-LabsCsv | Publish-Labs
41 | ```
42 | * `Import-LabsCsv` loads the configuration information from the csv file. It also loads schedule information for each lab from a separate file.
43 | * `Publish-Labs` publishes the labs and it is the natural end to all our pipelines. You can specify how many concurrent threads to use with the parameter `ThrottleLimit`.
44 |
45 | ### Show a menu asking to select one lab ([Examples/PickALab.ps1](./Examples/PickALab.ps1))
46 |
47 | ```console
48 | ".\hogwarts.csv" | Import-LabsCsv | Show-LabMenu -PickLab | Publish-Labs
49 |
50 | LABS
51 | [0] id001 hogwarts-rg2 History of Magic
52 | [1] id002 hogwarts-rg2 Transfiguration
53 | [2] id003 hogwarts-rg2 Charms
54 | Please select the lab to create:
55 | ```
56 |
57 | * The fields displayed for the various labs are fixed. Log an issue if you want me to make them configurable.
58 |
59 |
60 | ## Structure of the example [BellowsCollegeLabs_Sample.csv](./Examples/BellowsCollegeLabs_Sample.csv)
61 | Item | Description
62 | ----------------- | -------------
63 | Id | A unique id for the lab
64 | Tags | A set of tags applied to the lab.
65 | ResourceGroupName | The name of the resource group that the lab plan will be created in. If the resource group doesn't already exist, it will be created.
66 | Location | The region that the Lab will be created in, if the lab doesn't already exist. If the Lab in the Lab Plan's resource group already exists, this row is skipped.
67 | LabPlanName | The name of the Lab Plan to be created, if the lab plan doesn't already exist or if different will be adjusted to the defaults. If your lab plan needs advanced networking, we recommend that you manually create your lab plan and only use this script for deploying labs.
68 | LabName | The name of the Lab to be created.
69 | ImageName | The image name that the lab will be based on. Wildcards are accepted, but the ImageName field should match only 1 image.
70 | AadGroupId | The AadGroupId, used to connect the lab for syncing users. Used to enable Microsoft Teams support for this lab.
71 | MaxUsers | Maximum number of users expected for the lab.
72 | UsageQuota | Maximum quota per student.
73 | UsageMode | Type of usage expected for the lab. Either "Restricted" - only those who are registered in the lab, or "Open" anyone.
74 | SharedPassword | Enabled\Disabled values indicate whether the lab should use a shared password. "Enabled" means the lab uses a single shared password for the student's virtual machines, "Disabled" means the students will be prompted to change their password on first login.
75 | Size | The Virtual Machine size to use for the Lab. Please see details below on how these map to the Azure Portal.
76 | Title | The title for the lab. This is the value that the students/teachers will see for the name of the lab in the labs.azure.com portal.
77 | Descr | The description for the lab.
78 | UserName | The default username for admin account.
79 | Password | The default password for admin account.
80 | NonAdminUserName | Username for optional non-admin account.
81 | NonAdminPassword | Password for optional non-admin account.
82 | LinuxRdp | Set to "True" if the Virtual Machine requires Linux RDP, otherwise "False".
83 | Emails | Semicolon separated string of student emails to be added to the lab. For example: "bob@test.com;charlie@test.com"
84 | LabOwnerEmails | [DEPRECATED] This column is no longer supported; if you need support for this column, please log an issue.
85 | Invitation | Note to include in the invitation email to students. If you leave this field blank, invitation emails won't be sent during lab creation.
86 | Schedules | The name of the csv file that contains the schedule for this class. For example: "charms.csv". If left blank, a schedule won't be applied.
87 | TemplateVmState | Enabled\Disabled values indicate whether the lab should have a template VM created.
88 | IdleGracePeriod | Number of minutes between 15-59 to shut down lab VMs after idle state is detected.
89 | IdleOsGracePeriod | Number of minutes between 15-59 to disconnect lab VMs after a user disconnects.
90 | IdleNoConnectGracePeriod | Number of minutes between 15-59 to shut down lab VMs when a user doesn't connect.
91 |
92 | ## Structure of the example [BC_CompSci_AI_200_Schedule_Sample.csv](./Examples/BC_CompSci_AI_200_Schedule_Sample.csv)
93 | Item | Description
94 | ----------------- | -------------
95 | Frequency | How often, "Weekly" or "Once"
96 | FromDate | Start Date
97 | ToDate | End Date
98 | StartTime | Start Time
99 | EndTime | End Time
100 | WeekDays | Days of the week. "Monday, Tuesday, Friday". The days are comma separated with the text. If Frequency is "Once" use an empty string ""
101 | TimeZoneId | Time zone for the classes. "Central Standard Time"
102 | Notes | Additional notes
103 |
104 | ## Virtual Machine Sizes
105 | There are three categories of VM sizes that you can use: **Default VM sizes**, **Alternative VM sizes**, and **Classic VM sizes**. More information can be found in the [Lab Services Admin Guide](https://docs.microsoft.com/azure/lab-services/administrator-guide#vm-sizing).
106 |
107 | When you use the bulk deployment script to create labs, you must specify either the VM SKU Name or VM SKU Size that is expected by the underlying API. If you specify the friendly name shown in the portal, you will get an error.
108 |
109 | For the **Default VM sizes**, the following table shows the mapping between the friendly name shown in the portal and the underlying VM SKU Name/Size expected by the API.
110 |
111 | Friendly Name (shown in portal)| Underlying VM SKU Name | Underlying VM SKU Size
112 | -------------------------------|------------------------|--------------------------------
113 | Small | Basic | Fsv2_2_4GB_128_S_SSD
114 | Medium | Standard | Fsv2_4_8GB_128_S_SSD
115 | Medium (nested virtualization) | Virtualization | Dsv4_4_16GB_128_P_SSD
116 | Large | Large | Fsv2_8_16GB_128_S_SSD
117 | Large (nested virtualization) | Performance | Dsv4_8_32GB_128_P_SSD
118 | Small GPU (visualization) | SmallGPUVisualization | NVv4_8_28GB_128_S_SSD
119 | Small GPU (Compute) | SmallGPUCompute | Ncv3t4_8_56GB_128_S_SSD
120 | Medium GPU (visualization) | MediumGPUVisualization | NVv3_12_112GB_128_S_SSD
121 |
122 | For the **Alternative VM sizes**, it's easiest to specify the underlying VM SKU Size. The following table shows the mapping between the friendly name in the portal and the underlying VM SKU Size expected by the API.
123 |
124 | Friendly Name (shown in portal) | Underlying VM SKU Size
125 | ----------------------------------|---------------------------------------
126 | Alt. Small GPU (compute) | NCsv3_6_112GB_128_S_SSD
127 | Alt. Small GPU (visualization) | NVadsA10v5_6_55GB_128_S_SSD
128 | Alt. Medium GPU (visualization) | NVadsA10v5_12_110GB_128_S_SSD
129 |
130 | Likewise, for the **Classic VM sizes**, it's easiest to specify the underlying VM SKU Size. The following table shows the mapping between the friendly name in the portal and the underlying VM SKU Size expected by the API.
131 |
132 | Friendly Name (shown in portal) | Underlying VM SKU Size
133 | ----------------------------------|---------------------------------------
134 | Classic Small | Av2_2_4GB_128_S_SSD
135 | Classic Medium | Av2_4_8GB_128_S_SSD
136 | Classic Large | Av2_8_16GB_128_S_SSD
137 | Classic Medium (nested virt.) | Dsv3_4_16GB_128_P_SSD
138 | Classic Large (nested virt.) | Dsv3_8_32GB_128_P_SSD
139 | Classic Small GPU (compute) | NC_6_56GB_128_S_SSD
140 | Classic Small GPU (visualization) | NV_6_56GB_128_S_SSD
141 | Classic Medium GPU (visualization)| NVv3_12_112GB_128_S_SSD
142 |
143 | ## Troubleshooting
144 | To get more detailed logging to debug issues, we recommend that you follow the steps in this article: [Enable debug logging](https://learn.microsoft.com/powershell/azure/troubleshooting?view=azps-11.1.0#enable-debug-logging). Remember to use -Verbose flag when calling the module to see the verbose messages.
--------------------------------------------------------------------------------
/LabManagement/PowerShell/JITUserQuota/JITQuotaWithManagedId.ps1:
--------------------------------------------------------------------------------
1 | # Lets stop the script for any errors
2 | $ErrorActionPreference = "Stop"
3 |
4 | # ************************************************
5 | # ************ FIELDS TO UPDATE ******************
6 | # ************************************************
7 |
8 | # List of lab names that we should not include when updating quota)
9 | $excludeLabs = @('*test*','*demo*','*training*', '*how to*')
10 |
11 | # Number of available hours we reset the student to when running this script
12 | $usageQuota = 9
13 |
14 | # Segment of labs to update based on lab plans, regular expression to match
15 |
16 | # Match for all lab plans
17 | $labPlanNameRegex = "^.*"
18 |
19 | # ************************************************
20 |
21 | # create a temp file for host output
22 | $hostOutputFile = New-TemporaryFile
23 |
24 | Write-Output "Connecting service connection to Azure resources..."
25 | # Ensures you inherit azcontext in your Azure Automation runbook
26 | Enable-AzContextAutosave -Scope Process
27 |
28 | # Setup Azure Runbook Connection using System ManagedId
29 | try {
30 | $AzureContext = (Connect-AzAccount -Identity).context
31 | }
32 | catch{
33 | Write-Output "There is no system-assigned user identity. Aborting.";
34 | exit
35 | }
36 |
37 | # set and store context
38 | $AzureContext = Set-AzContext -SubscriptionName $AzureContext.Subscription `
39 | -DefaultProfile $AzureContext
40 |
41 | # Make sure we have the modules already imported via the automation account
42 | if (-not (Get-Command -Name "Get-AzLabServicesLabPlan" -ErrorAction SilentlyContinue)) {
43 | Write-Error "Unable to find the Az.LabServices.psm1 module, please add to the Azure Automation account"
44 | }
45 | if (-not (Get-Command -Name "Reset-AzLabUserQuotaBulk" -ErrorAction SilentlyContinue)) {
46 | Write-Error "Unable to find the Az.LabServices.BulkOperations.psm1 module, please add to the Azure Automation account"
47 | }
48 | if (-not (Get-Command -Name "Start-ThreadJob" -ErrorAction SilentlyContinue)) {
49 | Write-Error "Unable to find the ThreadJob Powershell module, please add to the Azure Automation account"
50 | }
51 |
52 | # -DefaultProfile $AzureContext
53 | $labPlans = Get-AzLabServicesLabPlan | Where-Object {
54 | $_.Name -match $labPlanNameRegex
55 | }
56 | Write-Output " Found .. $(($labPlans | Measure-Object).Count) lab plans)"
57 |
58 | Write-Output " Temp file location is: $($HostOutputFile.FullName)"
59 |
60 | try {
61 | $scriptstartTime = Get-Date
62 | Write-Output "Executing Bulk User Quota Script, starting at $scriptstartTime"
63 |
64 | $labPlans = Get-AzLabServicesLabPlan | Where-Object {
65 | $_.Name -match $labPlanNameRegex
66 | }
67 |
68 | $labs = $labPlans | Get-AzLabServicesLab 6>> $HostOutputFile.FullName
69 |
70 | # Filter the labs down to only the set that we should update
71 | Write-Output "Checking for labs to exclude..." 6>> $HostOutputFile.FullName
72 | $labsToUpdate = $labs | Where-Object {
73 | $toExclude = $null
74 | $labName = $_.Name
75 | $toExclude = $excludeLabs | ForEach-Object {
76 | if ($labName -like $_) {$true}
77 | }
78 | if ($toExclude)
79 | {
80 | # Can't write output to the hostfile because it breaks the filtering, so we'll just write to the terminal
81 | Write-Host "excluding $labName "
82 | $false
83 | }
84 | else {$true}
85 | }
86 |
87 | $labsToUpdate | ForEach-Object {
88 | Write-Output "Add member $($_.Name)"
89 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "UsageQuota" -Value $usageQuota -Force
90 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "LabName" -Value $_.Name -Force
91 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "ResourceGroupName" -Value $_.Id.Split("/")[4] -Force
92 | }
93 |
94 | # Now - let's call the bulk update function to update all the labs, piping 'host' messages to a file
95 | $labsToUpdate | Reset-AzLabUserQuotaBulk -ThrottleLimit 5 6>> $hostOutputFile.FullName
96 | }
97 | catch {
98 | # We just rethrow any errors with original context
99 | throw
100 | }
101 | finally {
102 | # Make sure we get the output back to Azure Automation, even if something breaks
103 |
104 | # Read in the 'host' messages and show them in the output
105 | Get-Content -Path $hostOutputFile.FullName
106 |
107 | # Remove the temp output file
108 | Remove-Item -Path $HostOutputFile.FullName -Force
109 | }
110 |
111 | Write-Output "Completed running Bulk User Quota script, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes"
--------------------------------------------------------------------------------
/LabManagement/PowerShell/JITUserQuota/JITQuotaWithRunAs.ps1:
--------------------------------------------------------------------------------
1 | # WARNING: The execution of RunAs accounts are retired as of September 30, 2023.
2 | # See https://learn.microsoft.com/azure/automation/migrate-run-as-accounts-managed-identity?tabs=sa-managed-identity for details.
3 |
4 | # Lets stop the script for any errors
5 | $ErrorActionPreference = "Stop"
6 |
7 | # ************************************************
8 | # ************ FIELDS TO UPDATE ******************
9 | # ************************************************
10 |
11 | # List of lab names that we should not include when updating quota)
12 | $excludeLabs = @('*test*','*demo*','*training*', '*how to*')
13 |
14 | # Number of available hours we reset the student to when running this script
15 | $usageQuota = 8
16 |
17 | # Segment of labs to update based on lab plans, regular expression to match
18 |
19 | # Match for all lab plans
20 | $labPlanNameRegex = "^.*"
21 |
22 | # ************************************************
23 |
24 | # create a temp file for host output
25 | $hostOutputFile = New-TemporaryFile
26 |
27 | Write-Output "Connecting service connection to Azure resources..."
28 | # Ensures you inherit azcontext in your Azure Automation runbook
29 | Enable-AzContextAutosave -Scope Process
30 |
31 | # Setup Azure Runbook Connection
32 | $Conn = Get-AutomationConnection -Name AzureRunAsConnection
33 | Connect-AzAccount -ServicePrincipal -Tenant $Conn.TenantID -ApplicationId $Conn.ApplicationID -CertificateThumbprint $Conn.CertificateThumbprint
34 | Select-AzSubscription -Subscription $Conn.SubscriptionId | Out-Null
35 |
36 | # Make sure we have the modules already imported via the automation account
37 | if (-not (Get-Command -Name "Get-AzLabServicesLabPlan" -ErrorAction SilentlyContinue)) {
38 | Write-Error "Unable to find the Az.LabServices.psm1 module, please add to the Azure Automation account"
39 | }
40 | if (-not (Get-Command -Name "Reset-AzLabUserQuotaBulk" -ErrorAction SilentlyContinue)) {
41 | Write-Error "Unable to find the Az.LabServices.BulkOperations.psm1 module, please add to the Azure Automation account"
42 | }
43 | if (-not (Get-Command -Name "Start-ThreadJob" -ErrorAction SilentlyContinue)) {
44 | Write-Error "Unable to find the ThreadJob Powershell module, please add to the Azure Automation account"
45 | }
46 |
47 | $labPlans = Get-AzLabServicesLabPlan | Where-Object {
48 | $_.Name -match $labPlanNameRegex
49 | }
50 | Write-Output " Found .. $(($labPlans | Measure-Object).Count) lab plans)"
51 |
52 | Write-Output " Temp file location is: $($HostOutputFile.FullName)"
53 |
54 | try {
55 | $scriptstartTime = Get-Date
56 | Write-Output "Executing Bulk User Quota Script, starting at $scriptstartTime"
57 |
58 | $labPlans = Get-AzLabServicesLabPlan | Where-Object {
59 | $_.Name -match $labPlanNameRegex
60 | }
61 |
62 | $labs = $labPlans | Get-AzLabServicesLab 6>> $HostOutputFile.FullName
63 |
64 | # Filter the labs down to only the set that we should update
65 | Write-Output "Checking for labs to exclude..." 6>> $HostOutputFile.FullName
66 | $labsToUpdate = $labs | Where-Object {
67 | $toExclude = $null
68 | $labName = $_.Name
69 | $toExclude = $excludeLabs | ForEach-Object {
70 | if ($labName -like $_) {$true}
71 | }
72 | if ($toExclude)
73 | {
74 | # Can't write output to the hostfile because it breaks the filtering, so we'll just write to the terminal
75 | Write-Host "excluding $labName "
76 | $false
77 | }
78 | else {$true}
79 | }
80 |
81 | $labsToUpdate | ForEach-Object {
82 | Write-Output "Add member $($_.Name)"
83 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "UsageQuota" -Value $usageQuota -Force
84 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "LabName" -Value $_.Name -Force
85 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "ResourceGroupName" -Value $_.Id.Split("/")[4] -Force
86 | }
87 |
88 | # Now - let's call the bulk update function to update all the labs, piping 'host' messages to a file
89 | $labsToUpdate | Reset-AzLabUserQuotaBulk -ThrottleLimit 5 6>> $hostOutputFile.FullName
90 | }
91 | catch {
92 | # We just rethrow any errors with original context
93 | throw
94 | }
95 | finally {
96 | # Make sure we get the output back to Azure Automation, even if something breaks
97 |
98 | # Read in the 'host' messages and show them in the output
99 | Get-Content -Path $hostOutputFile.FullName
100 |
101 | # Remove the temp output file
102 | Remove-Item -Path $HostOutputFile.FullName -Force
103 | }
104 |
105 | Write-Output "Completed running Bulk User Quota script, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes"
106 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/JITUserQuota/JITQuotaWithScheduledTask.ps1:
--------------------------------------------------------------------------------
1 | # Lets stop the script for any errors
2 | $ErrorActionPreference = "Stop"
3 |
4 | # ************************************************
5 | # ************ FIELDS TO UPDATE ******************
6 | # ************************************************
7 |
8 | # List of lab names that we should not include when updating quota)
9 | $excludeLabs = @('*test*','*demo*','*training*', '*how to*')
10 |
11 | # Number of available hours we reset the student to when running this script
12 | $usageQuota = 8
13 |
14 | # Segment of labs to update based on lab plans, regular expression to match
15 |
16 | # Match for all lab plans
17 | $labPlanNameRegex = "^.*"
18 |
19 | # Subscription ID
20 | $subId = "1111-1111-1111-11111-11111111"
21 |
22 | # ************************************************
23 |
24 | if ($PSVersionTable.PSEdition -eq 'Desktop' -and (Get-Module -Name AzureRM -ListAvailable)) {
25 | # Write-Warning -Message ('Az module not installed. Having both the AzureRM and ' +
26 | # 'Az modules installed at the same time is not supported.')
27 | } else {
28 | Install-Module -Name Az -AllowClobber -Scope CurrentUser -Force -Confirm:$false
29 | }
30 |
31 | # Install the Az.LabServices module if the command isn't available
32 | if (-not (Get-Command -Name "Get-AzLabServicesLab" -ErrorAction SilentlyContinue)) {
33 | Install-Module -Name Az.LabServices -Scope CurrentUser -Force
34 | }
35 |
36 | Import-Module Az
37 |
38 | $relativePath = "..\BulkOperations\Az.LabServices.BulkOperations.psm1"
39 | $currentDirPath = Join-Path -Path (Get-Location) -ChildPath "Az.LabServices.BulkOperations.psm1"
40 |
41 | if (Test-Path -Path $relativePath) {
42 | Import-Module $relativePath -Force
43 | } elseif (Test-Path -Path $currentDirPath) {
44 | Import-Module $currentDirPath -Force
45 | } else {
46 | Write-Error "BulkOperations.psm1 not found in the provided relative path or the current directory."
47 | return
48 | }
49 |
50 | Connect-AzAccount -Subscription $subId -Identity
51 | Write-Output "Lab quota update start at $(Get-Date)"
52 |
53 | # create a temp file for host output
54 | $dateString = Get-Date -Format "yyyyMMdd_HHmmss"
55 | $tempFilePath = Join-Path -Path $env:TEMP -ChildPath "AzureLabsUserQuota_$dateString.txt"
56 | $hostOutputFile = New-Item -Path $tempFilePath -ItemType File -Force
57 |
58 | $labPlans = Get-AzLabServicesLabPlan | Where-Object {
59 | $_.Name -match $labPlanNameRegex
60 | }
61 | Write-Output " Found .. $(($labPlans | Measure-Object).Count) lab plans)"
62 |
63 | Write-Output " Temp file location is: $($HostOutputFile.FullName)"
64 |
65 | try {
66 | $scriptstartTime = Get-Date
67 | Write-Output "Executing Bulk User Quota Script, starting at $scriptstartTime"
68 |
69 | $labPlans = Get-AzLabServicesLabPlan | Where-Object {
70 | $_.Name -match $labPlanNameRegex
71 | }
72 |
73 | $labs = $labPlans | Get-AzLabServicesLab 6>> $HostOutputFile.FullName
74 |
75 | # Filter the labs down to only the set that we should update
76 | Write-Output "Checking for labs to exclude..." 6>> $HostOutputFile.FullName
77 | $labsToUpdate = $labs | Where-Object {
78 | $toExclude = $null
79 | $labName = $_.Name
80 | $toExclude = $excludeLabs | ForEach-Object {
81 | if ($labName -like $_) {$true}
82 | }
83 | if ($toExclude)
84 | {
85 | # Can't write output to the hostfile because it breaks the filtering, so we'll just write to the terminal
86 | Write-Host "excluding $labName "
87 | $false
88 | }
89 | else {$true}
90 | }
91 |
92 | $labsToUpdate | ForEach-Object {
93 | Write-Output "Add member $($_.Name)"
94 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "UsageQuota" -Value $usageQuota -Force
95 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "LabName" -Value $_.Name -Force
96 | Add-Member -InputObject $_ -MemberType NoteProperty -Name "ResourceGroupName" -Value $_.Id.Split("/")[4] -Force
97 | }
98 |
99 | # Now - let's call the bulk update function to update all the labs, piping 'host' messages to a file
100 | $labsToUpdate | Reset-AzLabUserQuotaBulk -ThrottleLimit 5 6>> $hostOutputFile.FullName
101 | }
102 | catch {
103 | # We just rethrow any errors with original context
104 | throw
105 | }
106 | finally {
107 | # Read in the 'host' messages and show them in the output
108 | Get-Content -Path $hostOutputFile.FullName
109 | }
110 |
111 | Write-Output "Completed running Bulk User Quota script, total duration $([math]::Round(((Get-Date) - $scriptstartTime).TotalMinutes, 1)) minutes"
112 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/JITUserQuota/readme.md:
--------------------------------------------------------------------------------
1 |
2 | # Just-in-time User Quota
3 |
4 | Sample that creates a just-in-time system to update a lab user's quota as needed.
5 |
6 | ## Setup with Managed Identity and Runbook
7 |
8 | - **Setup Automation account with runbook using Managed identity**. See [Tutorial: Create Automation PowerShell runbook using managed identity](https://docs.microsoft.com/azure/automation/learn/powershell-runbook-managed-identity) for details
9 | - **Setup modules**. Add Az.LabServices.BulkOperations.psm1 as Az.LabServices.BulkOperations.zip
10 | - **Add Runbook PowerShell code**. Copy the JITQuotaWithManagedId.ps1 into the runbook editor
11 | - **Publish to Runbook**. See [managing runbooks](https://learn.microsoft.com/azure/automation/manage-runbooks#publish-a-runbook) for details.
12 | - **Set schedule**. See [managing runbooks](https://learn.microsoft.com/azure/automation/manage-runbooks#schedule-a-runbook-in-the-azure-portal) for details.
13 |
14 | ## Setup with Managed Identity and Windows Task Scheduler
15 | To run this script, it assumes that this repo has been cloned to the VM because it relies on the Az.LabServices.BulkOperations.psm1 script.
16 |
17 | - **Setup Managed Identity on VM**. The JITQuotaWithScheduledTask.psm1 script is designed to be run via Windows Task Scheduler on a dedicated Azure VM that has Managed Identity configured. See [configure managed identities](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm) to configure Managed Identity for the VM.
18 | - **Setup modules**. The script automatically installs and imports the AZ and Az.LabServicesBulkOperations.psm1 modules.
19 | - **Setup Windows Task Scheduler**. Use Windows Task Scheduler to schedule the script to run on regular cadence, such as the start of each week.
20 |
21 |
--------------------------------------------------------------------------------
/LabManagement/PowerShell/Quota/Measure-LabServicesNeededQuota.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | The MIT License (MIT)
3 | Copyright (c) Microsoft Corporation
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7 |
8 | .SYNOPSIS
9 | This script helps calculate number of cores needed when requesting more quota.
10 |
11 | .PARAMETER -PassThru
12 | If you would like to return the data on the pipeline, provide the -PassThru
13 | #>
14 |
15 | param
16 | (
17 | [Parameter(Mandatory=$false, HelpMessage="To return the quota data on the pipeline, pass the 'PassThru' switch")]
18 | [switch] $PassThru
19 | )
20 |
21 | #VM Sku families, vm sizes and number of cores need for each size.
22 | $labsSizes = [Ordered]@{
23 | Fsv2 = @{
24 | Name = "CPU Cores (Fsv2)"
25 | Sizes = [Ordered]@{
26 | "Small" = 2
27 | "Medium" = 4
28 | "Large" = 8
29 | }
30 | }
31 | Dsv4 = @{
32 | Name = "CPU Virtualization Cores (Dsv4)"
33 | Sizes = [Ordered]@{
34 | "Medium (Nested Virtualization)" = 4
35 | "Large (Nested Virtualization)" = 8
36 | }
37 | }
38 | NCv3T4 = @{
39 | Name = "GPU Compute (NCv3T4)"
40 | Sizes = [Ordered]@{
41 | "Small GPU (Compute)" = 6
42 | }
43 | }
44 | NVv4 = @{
45 | Name = "GPU Visualization (NVv4)"
46 | Sizes = [Ordered]@{
47 | "Small GPU (Visualization)" = 8
48 | "Medium GPU (Visualization)" = 12
49 | }
50 | }
51 | }
52 |
53 | $neededQuotaArray = [System.Collections.ArrayList]::new()
54 | foreach ($vmSkuFamily in $labsSizes.Values){
55 | $currentCores = 0
56 | $currentSizeName = ""
57 |
58 | #Get sizes for each sku family
59 | $currentVmSizes = $vmSkuFamily['Sizes']
60 | foreach ($currentVmSizeName in $currentVmSizes.Keys)
61 | {
62 | #Ask number of VMs needed for each size
63 | $numberOfVMs = [int](Read-Host -Prompt "How many '$($currentVmSizeName)' VMs do you need?" )
64 |
65 | #Calculate number of cores need for that size
66 | $currentCores += $numberOfVMs * $currentVmSizes[$currentVmSizeName]
67 | $currentSizeName += "$currentVmSizeName, "
68 | }
69 | $currentSizeName = $currentSizeName.Trim().TrimEnd(", ")
70 |
71 | #Log number of cores needed for VM Sku family
72 | if ($currentCores -gt 0){
73 | $neededQuotaArray.Add([PSCustomObject]@{
74 | "Sku Family" = "$($vmSkuFamily.Name)"
75 | "Size Names" = $currentSizeName
76 | "Cores Needed" = $currentCores
77 | }) |Out-Null
78 | }
79 | }
80 | Write-Host ""
81 |
82 | Write-Host "**************************" -ForegroundColor Green
83 | Write-Host "Notes:" -ForegroundColor Green
84 | Write-Host "- Additional cores quota requests are organized by the compute sku family."
85 | Write-Host "- To see current alloted quota for a subscription, run Get-LabServicesCapacityQuotas.ps1"
86 | Write-Host ""
87 | Write-Host "Results:" -ForegroundColor Green
88 | $neededQuotaArray | Format-Table
89 | Write-Host "**************************" -ForegroundColor Green
90 |
91 | if ($PassThru) {
92 | # return the quota data on the pipeline
93 | return $neededQuotaArray
94 | }
--------------------------------------------------------------------------------
/LabManagement/PowerShell/Quota/Readme.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | This script is used to calculate how many cores are needed in each family for the Lab Services virtual machine sizes. This is commonly used when requesting additional quota for Azure Lab Services. The results can be saved to a CSV file along with displayed to the console.
4 |
5 | ## Prerequisites
6 |
7 | - [Azure PowerShell module](https://docs.microsoft.com/powershell/azure)
8 | - [Azure Lab Services PowerShell module](https://www.powershellgallery.com/packages/Az.LabServices)
9 |
10 | ## Directions
11 |
12 | 1. Open a PowerShell window.
13 | 2. Run `Measure-LabServicesNeededQuota.ps1` . If you would like to return the results on the pipeline, please use the "PassThru" parameter.
14 |
15 | ``` Powershell
16 | # Calculate the cores needed for a Lab Services Virtual Machine Size:
17 | Measure-LabServicesNeededQuota.ps1
18 |
19 | # Calculate the cores needed and write the results to a CSV file
20 | Measure-LabServicesNeededQuota.ps1 -PassThru | Export-Csv -Path .\CoresNeeded.csv -NoTypeInformation
21 |
22 | ```
23 |
24 | For related information, refer to the following articles:
25 |
26 | - [About Azure Lab Services](https://docs.microsoft.com/azure/lab-services/lab-services-overview)
27 | - [Capacity Limits in Azure Lab Services](https://docs.microsoft.com/azure/lab-services/capacity-limits)
28 | - [Reference Guide for Azure Lab Services Powershell Module](https://docs.microsoft.com/powershell/module/az.labservices)
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Lab Services
2 |
3 | > [!IMPORTANT]
4 | > Azure Lab Services will be retired on June 28, 2027. [Click here](https://aka.ms/azlabs-retirementguide) for more information. We will not be taking any future requests or fixes.
5 |
6 | This repository contains samples extending the Azure Lab Services (post August 2022 update) experience.
7 |
8 | Contributions are welcome! If there is something that you want, feel free to create an issue or pull request.
9 |
10 | ## Overview
11 |
12 | Below are the different overarching areas. Samples are arranged by area and then technology used.
13 |
14 | ### Class Types
15 |
16 | This area contains scripts relating to [example classes](https://learn.microsoft.com/azure/lab-services/class-types) described in the Azure Lab Services documentation.
17 |
18 | - [Big Data Analytics](/ClassTypes/PowerShell/BigDataAnalytics/)
19 | - [Ethical Hacking](/ClassTypes/PowerShell/EthicalHacking/)
20 | - [Fedora Linux](/ClassTypes/Docker/FedoraDockerContainer/)
21 |
22 | Section also contains scripts to [enable nested virtualization](/ClassTypes/PowerShell/HyperV/), which is used by a couple example classes.
23 |
24 | ### General Scripts
25 |
26 | These are scripts to help either tangentially to Lab Services or in support of Lab Services.
27 |
28 | - [Creating a new VM image for Azure Compute Gallery from existing VM](/GeneralScripts/PowerShell/BringImageToSharedImageGallery/)
29 | - [Custom Policies](/GeneralScripts/PowerShell/CustomPolicies/)
30 |
31 | ### Lab Management
32 |
33 | This code is to help with management of labs:
34 |
35 | - Creating labs at scale.
36 | - [PowerShell example](/LabManagement/PowerShell/BulkOperations/)
37 | - [ARM example](/LabManagement/ARM/Bulk_CreateLab_ARM.ps1)
38 | - Customizing labs, templates, or VMs.
39 | - Managing students, schedules, or roles at scale.
40 |
41 | ### Template Management
42 |
43 | ## Earlier Versions
44 |
45 | If you are using the original version of Lab Services that uses lab accounts, code is in the [Azure-DevTestLab repository](https://github.com/Azure/azure-devtestlab/tree/master/samples/ClassroomLabs).
46 |
47 | ## Contributing
48 |
49 | This project welcomes contributions and suggestions. Most contributions require you to agree to a
50 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
51 | the rights to use your contribution. For details, visit [Microsoft Open Source Contributor License Agreements](https://cla.opensource.microsoft.com).
52 |
53 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide
54 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
55 | provided by the bot. You will only need to do this once across all repos using our CLA.
56 |
57 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
58 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
59 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
60 |
61 | ## Trademarks
62 |
63 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/legal/intellectualproperty/trademarks/usage/general).
64 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
65 | Any use of third-party trademarks or logos are subject to those third-party's policies.
66 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
40 |
41 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | # TODO: The maintainer of this repo has not yet edited this file
2 |
3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project?
4 |
5 | - **No CSS support:** Fill out this template with information about how to file issues and get help.
6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport).
7 | - **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide.
8 |
9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.*
10 |
11 | # Support
12 |
13 | ## How to file issues and get help
14 |
15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing
16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or
17 | feature request as a new Issue.
18 |
19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE
20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER
21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**.
22 |
23 | ## Microsoft Support Policy
24 |
25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above.
26 |
--------------------------------------------------------------------------------
/TemplateManagement/Bash/LinuxGraphicalDesktopSetup/GNOME_MATE/ReadMe.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | These scripts install GNOME/Xrdp and MATE/X2Go graphical desktop environments on Ubuntu. The scripts provide different configuration steps depending on the version, graphical desktop environment, and remote desktop server technology being used to optimize desktop connection performance:
4 | - Ubuntu 18.04 LTS => Installs GNOME/Xrdp and MATE/X2Go.
5 | - Ubuntu 20.04/21.04/22.04 LTS => Installs GNOME/Xrdp.
6 | - Ubuntu 20.04/22.04 LTS => Installs MATE/X2Go.
7 |
8 | > [!NOTE]
9 | > The Ubuntu 18.04 and 21.04 LTS images are *no* longer available in the Azure marketplace as a free image provided by Canonical. Azure Labs only supports using free marketplace images. The instructions/scripts included for Ubuntu 18.04/21.04 LTS are only applicable to custom lab images that were previously [saved to a Compute Gallery](https://learn.microsoft.com/azure/lab-services/approaches-for-custom-image-creation#save-a-custom-image-from-a-lab-template-virtual-machine), or to use custom images that are imported from a [physical lab environment](https://learn.microsoft.com/azure/lab-services/approaches-for-custom-image-creation#bring-a-custom-image-from-a-vhd-in-your-physical-lab-environment). Otherwise, we recommend using Ubuntu 20.04 or 22.04 LTS which are available as free marketplace images.
10 |
11 | If you are using a custom image with Ubuntu 18.04 LTS, the GNOME and MATE graphical desktop environments have a networking conflict with the Azure Linux Agent which is needed for the VMs to work properly in Azure Labs. For example, this networking conflict will cause lab creation to fail when attempting to provision the template VM. Likewise, it will cause publish to hang when attempting to provision the student VMs. To successfully use use GNOME or MATE on lab VMs, the below scripts include additional steps that are required to fix this networking conflict with Ubuntu 18.04 LTS. This issue is being tracked by the following Canonical bug: .
12 |
13 | Ubuntu 20.04/22.04 LTS do *not* have this networking conflict with the Azure Linux Agent when you install GNOME or MATE. As a result, when you run the below scripts to install 20.04/22.04, the steps for fixing the networking conflict are skipped.
14 |
15 | ## Ubuntu
16 |
17 | These scripts have been tested with:
18 |
19 | - GNOME/Xrdp:
20 | - Ubuntu 18.04/20.04/21.04/22.04 LTS
21 | - MATE/X2Go:
22 | - Ubuntu 18.04/20.04/22.04 LTS
23 |
24 | > [!NOTE]
25 | > X2Go isn't compatible with GNOME which is why Xrdp must be used. See X2Go's list of [compatible desktop environments](https://wiki.x2go.org/doku.php/doc:de-compat) for more information.
26 |
27 | ## Configuring X2Go and Xrdp
28 |
29 | Both [X2Go](https://wiki.x2go.org/doku.php/doc:newtox2go) and [Xrdp](https://en.wikipedia.org/wiki/Xrdp) are Remote Desktop solutions, which sometimes is referred to as Remote Control. A key difference between X2Go is that it uses the same port as SSH (port 22). Azure Labs enables the SSH port by default. Xrdp uses port 3389 which [you must enable](https://docs.microsoft.com/azure/lab-services/how-to-enable-remote-desktop-linux#enable-remote-desktop-connection-for-rdp) when you create a lab.
30 |
31 | X2Go typically provides better performance for students when they need to connect to a Linux VM. However, X2Go doesn't support all graphical desktops. As a result, these instructions show using X2Go with MATE and Xrdp with GNOME. The script also makes performance optimizations for GNOME because it is a more resource intensive desktop environment.
32 |
33 | There are two steps involved to set up either X2Go or Xrdp: *(Students only need to do step #2 below to connect to their assigned VM)*
34 |
35 | 1. [Install the X2Go\Xrdp server](#install-x2go-or-xrdp-server) on the lab's template VM using one of the scripts below.
36 | 2. [Install X2Go\RDP client and create a session](#install-x2go-or-rdp-client-and-create-a-session) to connect to your lab (remote) VM.
37 |
38 | ### Install X2Go or Xrdp Server
39 |
40 | The lab (remote) VM runs either the X2Go or Xrdp server. Graphical sessions are started on this remote VM and the server transfers the windows/desktops graphics to the client.
41 |
42 | The scripts below automatically install the X2Go/Xrdp server and the MATE\GNOME graphical desktop environment. To install using these scripts, SSH into the template VM and paste in one of the following scripts depending on which desktop environment you prefer:
43 |
44 | #### Install MATE Desktop and X2Go Server
45 |
46 | ```bash
47 | sudo sh -c "$(curl -fsSL https://raw.githubusercontent.com/Azure/LabServices/main/TemplateManagement/Bash/LinuxGraphicalDesktopSetup/GNOME_MATE/Ubuntu/x2go-mate.sh)"
48 | ```
49 |
50 | #### Install GNOME Desktop And Xrdp Server
51 |
52 | ```bash
53 | sudo sh -c "$(curl -fsSL https://raw.githubusercontent.com/Azure/LabServices/main/TemplateManagement/Bash/LinuxGraphicalDesktopSetup/GNOME_MATE/Ubuntu/xrdp-gnome.sh)"
54 | ```
55 |
56 | ### Install X2Go or RDP Client and Create a Session
57 |
58 | Once you have the X2Go/Xrdp server installed on your template VM (using the scripts above), you'll use the X2Go/RDP client to remotely connect to the VM. The X2Go/RDP Client is the application that allows you to connect to a remote server and display a graphical desktop on your local machine.
59 |
60 | Read the following articles:
61 |
62 | - [Connect to student VM using X2Go](https://docs.microsoft.com/azure/lab-services/how-to-use-remote-desktop-linux-student#connect-to-the-student-vm-using-x2go)
63 | - [Connect to student VM using RDP](https://docs.microsoft.com/azure/lab-services/how-to-use-remote-desktop-linux-student#connect-to-the-student-vm-using-microsoft-remote-desktop-rdp)
64 |
--------------------------------------------------------------------------------
/TemplateManagement/Bash/LinuxGraphicalDesktopSetup/GNOME_MATE/Ubuntu/x2go-mate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | error() {
6 | echo ${RED}"Error: $@"${RESET} >&2
7 | }
8 |
9 | setup_color() {
10 | # Only use colors if connected to a terminal
11 | if [ -t 1 ]; then
12 | RED=$(printf '\033[31m')
13 | GREEN=$(printf '\033[32m')
14 | YELLOW=$(printf '\033[33m')
15 | BLUE=$(printf '\033[34m')
16 | BOLD=$(printf '\033[1m')
17 | RESET=$(printf '\033[m')
18 | else
19 | RED=""
20 | GREEN=""
21 | YELLOW=""
22 | BLUE=""
23 | BOLD=""
24 | RESET=""
25 | fi
26 | }
27 |
28 | main() {
29 |
30 | setup_color
31 |
32 | echo "${BLUE}Adding x2go PPA repo...${RESET}"
33 |
34 | apt-get install -y software-properties-common
35 |
36 | add-apt-repository -y ppa:x2go/stable
37 |
38 | echo "${BLUE}Updating apt package repository...${RESET}"
39 |
40 | apt-get update
41 |
42 | sleep 2
43 |
44 | echo "${BLUE}Installing MATE desktop and x2go server...${RESET}"
45 |
46 | DEBIAN_FRONTEND=noninteractive apt-get install -y ubuntu-mate-desktop
47 |
48 | apt-get install -y x2goserver x2goserver-xsession
49 |
50 | currentversion=$(grep '^VERSION_ID' /etc/os-release)
51 |
52 | # The x2gomatebindings aren't available for 22.04/23.04. If you attempt to install them, you'll get this error:
53 | # Unable to locate package x2gomatebindings
54 | # For list of versions that x2gomatebindings are available for, see: https://launchpad.net/~x2go/+archive/ubuntu/stable
55 | # For more info about x2gomatebindings, see: https://wiki.x2go.org/doku.php/wiki:advanced:desktopbindings#:~:text=X2Go%20Session.-,Desktop%20Bindings%20for%20MATE%20(v1.x),-X2Go%20bindings%20for
56 | targetversion="20.04, 18.04"
57 |
58 | case "$currentversion" in
59 | *"$targetversion"*)
60 |
61 | echo "${BLUE}Installing x2go MATE bindings...${RESET}"
62 |
63 | apt-get install -y x2gomatebindings
64 | esac
65 |
66 | echo "${GREEN}MATE desktop and x2go successfully installed!${RESET}"
67 |
68 | # The following steps are needed to work around a conflict between MATE desktop and the Azure Linux VM agent. Specifically, the conflict occurs when MATE installs
69 | # ifupdown. Ifupdown conflicts with netplan/cloud-init which causes the VM to fail to get its IP address during first boot. As a result, the Azure Linux VM
70 | # agent remains in a "Not Ready" state when a new VM is provisioned with an image with MATE desktop installed.
71 | # Here is more info about how these tools interact with one another:
72 | # - ifupdown is used by the network management daemon, systemd networkd
73 | # - netplan uses the systemd networkd daemon to provide network information to cloud-init
74 | # - cloud-init runs during a VM's initial boot process to set up the VM
75 | # The workaround is to remove netplan and disable the systemd networkd daemon so that instead the Network Manager daemon and ifupdown are used for networking (Note: MATE
76 | # prefers using Network Manager). See the following bug: https://bugs.launchpad.net/ubuntu/+source/cloud-init/+bug/1832381.
77 | targetversion="18.04"
78 |
79 | case "$currentversion" in
80 | *"$targetversion"*)
81 | echo "${BLUE}Configuring networking workaround for MATE and Azure Linux VM agent...${RESET}"
82 |
83 | apt-get remove -y netplan.io
84 |
85 | systemctl disable systemd-networkd
86 |
87 | cat > /etc/network/interfaces <&2
7 | }
8 |
9 | setup_color() {
10 | # Only use colors if connected to a terminal
11 | if [ -t 1 ]; then
12 | RED=$(printf '\033[31m')
13 | GREEN=$(printf '\033[32m')
14 | YELLOW=$(printf '\033[33m')
15 | BLUE=$(printf '\033[34m')
16 | BOLD=$(printf '\033[1m')
17 | RESET=$(printf '\033[m')
18 | else
19 | RED=""
20 | GREEN=""
21 | YELLOW=""
22 | BLUE=""
23 | BOLD=""
24 | RESET=""
25 | fi
26 | }
27 |
28 | main() {
29 |
30 | setup_color
31 |
32 | echo "${BLUE}Updating apt package repository...${RESET}"
33 |
34 | apt-get update
35 |
36 | sleep 2
37 |
38 | echo "${BLUE}Installing GNOME desktop and Xrdp server...${RESET}"
39 |
40 | apt-get install -y ubuntu-gnome-desktop
41 |
42 | apt-get install -y xrdp
43 |
44 | cat >> /etc/polkit-1/localauthority.conf.d/02-allow-colord.conf << EOF
45 | polkit.addRule(function(action, subject) {
46 | if ((action.id == “org.freedesktop.color-manager.create-device” || action.id == “org.freedesktop.color-manager.create-profile” || action.id == “org.freedesktop.color-manager.delete-device” || action.id == “org.freedesktop.color-manager.delete-profile” || action.id == “org.freedesktop.color-manager.modify-device” || action.id == “org.freedesktop.color-manager.modify-profile”) && subject.isInGroup(“{group}”))
47 | {
48 | return polkit.Result.YES;
49 | }
50 | });
51 | EOF
52 |
53 | sed -i "s/allowed_users=console/allowed_users=anybody/" /etc/X11/Xwrapper.config
54 |
55 | # Optimizes GNOME desktop performance over RDP. See https://github.com/neutrinolabs/xrdp/issues/1483.
56 | echo "${BLUE}Optimize RDP settings for GNOME desktop...${RESET}"
57 | sed -i 's/#\s*tcp_send_buffer_bytes\s*=\s*32768/tcp_send_buffer_bytes=4194304/' /etc/xrdp/xrdp.ini
58 | sed -i 's/max_bpp=32/max_bpp=16/g' /etc/xrdp/xrdp.ini
59 | sysctl -w net.core.wmem_max=8388608
60 |
61 | /etc/init.d/xrdp restart
62 |
63 | sleep 2
64 |
65 | echo "${GREEN}GNOME desktop and Xrdp successfully installed!${RESET}"
66 |
67 | # The following steps are needed to work around a conflict between GNOME desktop and the Azure Linux VM agent. Specifically, the conflict occurs when GNOME installs
68 | # ifupdown. Ifupdown conflicts with netplan/cloud-init which causes the VM to fail to get its IP address during first boot. As a result, the Azure Linux VM
69 | # agent remains in a "Not Ready" state when a new VM is provisioned with an image that has GNOME desktop installed.
70 | # Here is more info about how these tools interact with one another:
71 | # - ifupdown is used by the network management daemon, systemd networkd
72 | # - netplan uses the systemd networkd daemon to provide network information to cloud-init
73 | # - cloud-init runs during a VM's initial boot process to set up the VM
74 | # The workaround is to remove netplan and disable the systemd networkd daemon so that instead the Network Manager daemon and ifupdown are used for networking (Note: GNOME
75 | # prefers using Network Manager). See the following bug: https://bugs.launchpad.net/ubuntu/+source/cloud-init/+bug/1832381.
76 |
77 | currentversion=$(grep '^VERSION_ID' /etc/os-release)
78 | targetversion="18.04"
79 |
80 | case "$currentversion" in
81 | *"$targetversion"*)
82 |
83 | echo "${BLUE}Configuring networking workaround for GNOME and Azure Linux VM agent...${RESET}"
84 |
85 | apt-get remove -y netplan.io
86 |
87 | systemctl disable systemd-networkd
88 |
89 | cat > /etc/network/interfaces < [!NOTE]
6 | > The Ubuntu 16.04, 18.04 and 21.04 LTS images are *no* longer available in the Azure marketplace as a free image provided by Canonical. Azure Labs only supports using free marketplace images. The instructions/scripts included for Ubuntu 16.04/18.04/21.04 LTS are only applicable to custom lab images that were previously [saved to a Compute Gallery](https://learn.microsoft.com/azure/lab-services/approaches-for-custom-image-creation#save-a-custom-image-from-a-lab-template-virtual-machine), or to custom images that are imported from a [physical lab environment](https://learn.microsoft.com/azure/lab-services/approaches-for-custom-image-creation#bring-a-custom-image-from-a-vhd-in-your-physical-lab-environment). Otherwise, we recommend using Ubuntu 20.04 or 22.04 LTS which are available as free marketplace images.
7 |
8 | ## Ubuntu
9 |
10 | These scripts have been tested with:
11 |
12 | - Ubuntu 16.04/18.04/20.04/21.04/22.04 LTS
13 |
14 | ## Configuring X2Go
15 |
16 | [X2Go](https://wiki.x2go.org/doku.php/doc:newtox2go) is a Remote Desktop solution, which sometimes is referred to as Remote Control. This is not to be confused with Microsoft Remote Desktop Connection that uses RDP - this is a competing Remote Desktop solution and protocol.
17 |
18 | Using X2Go requires two steps: _(Students only need to do step #2 below to connect to their assigned VM)_
19 |
20 | 1. [Install the X2Go server](#install-x2go-server) on the lab's template VM using one of the scripts below.
21 | 2. [Install the X2Go client and create a session](#install-x2go-client-and-create-a-session) to connect to your lab (remote) VM.
22 |
23 | ### Install X2Go Server
24 |
25 | The lab (remote) VM runs X2Go server. Graphical sessions are started on this remote VM and the server transfers the windows/desktops graphics to the client.
26 |
27 | The scripts below automatically install the X2Go server and the Linux desktop environment. To install using these scripts, SSH into the template VM and paste in one of the following scripts depending on which desktop environment you prefer:
28 |
29 | #### Install XFCE4 Desktop and X2Go Server
30 |
31 | ```bash
32 | sudo sh -c "$(curl -fsSL https://raw.githubusercontent.com/Azure/LabServices/main/TemplateManagement/Bash/LinuxGraphicalDesktopSetup/XFCE_Xubuntu/Ubuntu/x2go-xfce4.sh)"
33 | ```
34 |
35 | #### Install Xubuntu Desktop & X2Go Server
36 |
37 | ```bash
38 | sudo sh -c "$(curl -fsSL https://raw.githubusercontent.com/Azure/LabServices/main/TemplateManagement/Bash/LinuxGraphicalDesktopSetup/XFCE_Xubuntu/Ubuntu/x2go-xubuntu.sh)"
39 | ```
40 | ### Install X2Go Client and Create a Session
41 |
42 | Once you have the X2Go\Xrdp server installed on your template VM (using the scripts above), you'll use the X2Go\RDP client to remotely connect to the VM. The X2Go\RDP Client is the application that allows you to connect to a remote server and display a graphical desktop on your local machine.
43 |
44 | Read the following article:
45 |
46 | - [Connect to student VM using X2Go](https://docs.microsoft.com/azure/lab-services/how-to-use-remote-desktop-linux-student#connect-to-the-student-vm-using-x2go)
47 |
48 | After running the script, you may also want to disable compositing in xUbuntu desktop to optimize performance over a remote desktop connection. For example, use the below script to disable compositing. This script requires an active X11 display session, so you will need to run the script via a terminal within your xUbuntu graphical desktop environment by connecting to the VM using X2Go:
49 |
50 | ```bash
51 | xfconf-query -c xfwm4 -p /general/use_compositing -s false
52 | ```
53 |
54 | Once you've disabled compositing and restarted the VM, you should notice a significant performance improvement when using a remote desktop connection.
--------------------------------------------------------------------------------
/TemplateManagement/Bash/LinuxGraphicalDesktopSetup/XFCE_Xubuntu/Ubuntu/x2go-xfce4.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | error() {
6 | echo ${RED}"Error: $@"${RESET} >&2
7 | }
8 |
9 | setup_color() {
10 | # Only use colors if connected to a terminal
11 | if [ -t 1 ]; then
12 | RED=$(printf '\033[31m')
13 | GREEN=$(printf '\033[32m')
14 | YELLOW=$(printf '\033[33m')
15 | BLUE=$(printf '\033[34m')
16 | BOLD=$(printf '\033[1m')
17 | RESET=$(printf '\033[m')
18 | else
19 | RED=""
20 | GREEN=""
21 | YELLOW=""
22 | BLUE=""
23 | BOLD=""
24 | RESET=""
25 | fi
26 | }
27 |
28 |
29 | main() {
30 |
31 | setup_color
32 |
33 | echo "${BLUE}Adding x2go PPA repo...${RESET}"
34 |
35 | apt-get install -y software-properties-common
36 |
37 | add-apt-repository -y ppa:x2go/stable
38 |
39 | echo "${BLUE}Updating apt package repository...${RESET}"
40 |
41 | apt-get update
42 |
43 | sleep 2
44 |
45 | echo "${BLUE}Intalling XFCE4 desktop and x2go server...${RESET}"
46 |
47 | sudo DEBIAN_FRONTEND=noninteractive apt-get -y install xfce4
48 | sudo apt install xfce4-session
49 |
50 | # Install x2goserver and x2goserver-xsession packages
51 | apt-get install -y x2goserver x2goserver-xsession
52 |
53 | echo "${GREEN}XFCE4 desktop and x2go successfully installed!${RESET}"
54 | }
55 |
56 | main
--------------------------------------------------------------------------------
/TemplateManagement/Bash/LinuxGraphicalDesktopSetup/XFCE_Xubuntu/Ubuntu/x2go-xubuntu.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | error() {
6 | echo ${RED}"Error: $@"${RESET} >&2
7 | }
8 |
9 | setup_color() {
10 | # Only use colors if connected to a terminal
11 | if [ -t 1 ]; then
12 | RED=$(printf '\033[31m')
13 | GREEN=$(printf '\033[32m')
14 | YELLOW=$(printf '\033[33m')
15 | BLUE=$(printf '\033[34m')
16 | BOLD=$(printf '\033[1m')
17 | RESET=$(printf '\033[m')
18 | else
19 | RED=""
20 | GREEN=""
21 | YELLOW=""
22 | BLUE=""
23 | BOLD=""
24 | RESET=""
25 | fi
26 | }
27 |
28 |
29 | main() {
30 |
31 | setup_color
32 |
33 | echo "${BLUE}Adding x2go PPA repo...${RESET}"
34 |
35 | apt-get install -y software-properties-common
36 |
37 | add-apt-repository -y ppa:x2go/stable
38 |
39 | echo "${BLUE}Updating apt package repository...${RESET}"
40 |
41 | apt-get update
42 |
43 | sleep 2
44 |
45 | echo "${BLUE}Intalling xubuntu desktop and x2go server...${RESET}"
46 |
47 | DEBIAN_FRONTEND=noninteractive apt-get install -y xubuntu-desktop
48 |
49 | echo "${GREEN}Xubuntu desktop and x2go successfully installed!${RESET}"
50 |
51 | # Install x2goserver and x2goserver-xsession packages
52 | apt-get install -y x2goserver x2goserver-xsession
53 |
54 | echo "${GREEN}Xubuntu desktop and x2go successfully installed!${RESET}"
55 | }
56 |
57 | main
--------------------------------------------------------------------------------
/TemplateManagement/PowerShell/GenericPreparation/Prepare-MicrosoftStoreApplications.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | The MIT License (MIT)
3 | Copyright (c) Microsoft Corporation
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7 | .SYNOPSIS
8 | This script prepares computer for class by aiding in the deletion of unneeded Microsoft Store applications. Remaining Microsoft Store applications are updated.
9 | #>
10 |
11 | [CmdletBinding()]
12 | param( )
13 |
14 | ###################################################################################################
15 | #
16 | # PowerShell configurations
17 | #
18 |
19 | # NOTE: Because the $ErrorActionPreference is "Stop", this script will stop on first failure.
20 | # This is necessary to ensure we capture errors inside the try-catch-finally block.
21 | $ErrorActionPreference = "Stop"
22 |
23 | # Hide any progress bars, due to downloads and installs of remote components.
24 | $ProgressPreference = "SilentlyContinue"
25 |
26 | # Ensure we set the working directory to that of the script.
27 | Push-Location $PSScriptRoot
28 |
29 | # Discard any collected errors from a previous execution.
30 | $Error.Clear()
31 |
32 | # Configure strict debugging.
33 | Set-PSDebug -Strict
34 |
35 | ###################################################################################################
36 | #
37 | # Handle all errors in this script.
38 | #
39 |
40 | trap {
41 | # NOTE: This trap will handle all errors. There should be no need to use a catch below in this
42 | # script, unless you want to ignore a specific error.
43 | $message = $Error[0].Exception.Message
44 | if ($message) {
45 | Write-Host -Object "`nERROR: $message" -ForegroundColor Red
46 | }
47 |
48 | Write-Host "`nThe script failed to run.`n" -ForegroundColor Red
49 |
50 | # IMPORTANT NOTE: Throwing a terminating error (using $ErrorActionPreference = "Stop") still
51 | # returns exit code zero from the PowerShell script when using -File. The workaround is to
52 | # NOT use -File when calling this script and leverage the try-catch-finally block and return
53 | # a non-zero exit code from the catch block.
54 | exit -1
55 | }
56 |
57 | ###################################################################################################
58 | #
59 | # Functions used in this script.
60 | #
61 |
62 | <#
63 | .SYNOPSIS
64 | Returns true if script is running with administrator privileges and false otherwise.
65 | #>
66 | function Get-RunningAsAdministrator {
67 | [CmdletBinding()]
68 | param()
69 |
70 | $isAdministrator = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
71 | $null = @(
72 | Write-Verbose "Running with$(if(-not $isAdministrator) {"out"}) Administrator privileges."
73 | )
74 | return $isAdministrator
75 | }
76 |
77 |
78 | <#
79 | .SYNOPSIS
80 | Removes selected Microsoft Store applications. Uninstallable applications are not shown.
81 | #>
82 | function Remove-MicrosoftStoreApps{
83 |
84 | $options = $(New-Object -Type 'System.Management.Automation.Host.ChoiceDescription' -ArgumentList @('&Select specific applications', 'Select specific applications to delete from a grid.')), `
85 | $(New-Object -Type 'System.Management.Automation.Host.ChoiceDescription' -ArgumentList @('&None','Do not remove any Microsoft Store applications.'))
86 |
87 | $response = $Host.UI.PromptForChoice("Remove Microsoft Store applications", 'Which Microsoft Store applications should be removed?', $options, 0)
88 |
89 | if ($response -eq 0){
90 |
91 | $microsoftStoreAppsToRemove= Get-AppxPackage | Where {$_.NonRemovable -eq $false} | Sort-Object -Property Name | Out-Gridview -Title "Select which Microsoft Store applications to delete" -PassThru
92 | $appsThatAreDependencies = @(Get-AppxPackage | Select -expand Dependencies | select -expand PackageFullName)
93 | #Remove Microsoft Store application, as long as it is not a dependency of another application.
94 | $microsoftStoreAppsToRemove | `
95 | %{
96 | if ($appsThatAreDependencies -contains $_.PackageFullName){
97 | $currentPackageFullName = $_.PackageFullName
98 | $appsWithDependencyOnCurrentPackageList = @(Get-AppxPackage | where {$($_.Dependencies | select -expand PackageFullName) -eq $currentPackageFullName} | select -expand Name)
99 | Write-Host "Can't remove '$($_.Name)' as it is a dependency for $($appsWithDependencyOnCurrentPackageList -join ', ')."
100 | }else{
101 | %{Write-Host "Removing '$($_.Name)'."; $_ | Remove-AppxPackage }
102 | }
103 | }
104 | }else{
105 | Write-Host "No Microsoft Store applications removed."
106 | }
107 | }
108 |
109 | <#
110 | .SYNOPSIS
111 | Updates Microsoft Store applications.
112 | #>
113 | function Update-MicrosoftStoreApps{
114 | Write-Host "Updating Microsoft Store applications."
115 | (Get-WmiObject -Namespace "root\cimv2\mdm\dmmap" -Class "MDM_EnterpriseModernAppManagement_AppManagement01").UpdateScanMethod() | Out-Null
116 | }
117 |
118 | ###################################################################################################
119 | #
120 | # Main execution block.
121 | #
122 |
123 | try {
124 | Write-Host "Verifying running as administrator."
125 | if (-not (Get-RunningAsAdministrator)) {
126 | Write-Error "Please re-run this script as Administrator."
127 | }
128 |
129 | Remove-MicrosoftStoreApps
130 |
131 | Update-MicrosoftStoreApps
132 |
133 | Write-Host -Object "Script completed successfully." -ForegroundColor Green
134 | }
135 | finally {
136 | # Restore system to state prior to execution of this script.
137 | Pop-Location
138 | }
139 |
--------------------------------------------------------------------------------
/TemplateManagement/PowerShell/GenericPreparation/Prepare-OneDrive.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | This script prepares a the OneDrive install for a Windows 10 machine for a generic class. This includes installing the client, prompting to move known folders to OneDrive and automatically signed user into OneDrive, if possible.
4 | .PARAMETER TenantId
5 | The Tenant Id for your Office 365 subscription.
6 | .NOTES
7 | One way to get your TenantID is to run the following commands:
8 | Install-Module MSOnline
9 | Connect-MsolService
10 | Get-MSOLCompanyInformation | select -expand objectID | select -expand Guid
11 | If the above command fails, you can attempt to retrieve your Office Tenant ID from https://docs.microsoft.com/onedrive/find-your-office-365-tenant-id
12 | #>
13 |
14 | [CmdletBinding()]
15 | param(
16 | [string]$TenantId
17 | )
18 |
19 | ###################################################################################################
20 | #
21 | # PowerShell configurations
22 | #
23 |
24 | # NOTE: Because the $ErrorActionPreference is "Stop", this script will stop on first failure.
25 | # This is necessary to ensure we capture errors inside the try-catch-finally block.
26 | $ErrorActionPreference = "Stop"
27 |
28 | # Ensure we set the working directory to that of the script.
29 | Push-Location $PSScriptRoot
30 |
31 | # Discard any collected errors from a previous execution.
32 | $Error.Clear()
33 |
34 | # Configure strict debugging.
35 | Set-PSDebug -Strict
36 |
37 | ###################################################################################################
38 | #
39 | # Handle all errors in this script.
40 | #
41 |
42 | trap {
43 | # NOTE: This trap will handle all errors. There should be no need to use a catch below in this
44 | # script, unless you want to ignore a specific error.
45 | $message = $Error[0].Exception.Message
46 | if ($message) {
47 | Write-Host -Object "`nERROR: $message" -ForegroundColor Red
48 | }
49 |
50 | Write-Host "`nThe script failed to run.`n"
51 |
52 | exit -1
53 | }
54 |
55 | ###################################################################################################
56 | #
57 | # Generic Functions
58 | #
59 |
60 | <#
61 | .SYNOPSIS
62 | Returns true is script is running with administrator privileges and false otherwise.
63 | #>
64 | function Get-RunningAsAdministrator {
65 | [CmdletBinding()]
66 | param()
67 |
68 | $isAdministrator = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
69 | $null = @(
70 | Write-Verbose "Running with$(if(-not $isAdministrator) {"out"}) Administrator privileges."
71 | )
72 | return $isAdministrator
73 | }
74 |
75 | <#
76 | .SYNOPSIS
77 | Funtion will download file from specified url.
78 | .PARAMETER DownloadUrl
79 | Url where to get file.
80 | .PARAMETER TargetFilePath
81 | Path where download file will be saved.
82 | .PARAMETER SkipIfAlreadyExists
83 | Skip download if TargetFilePath already exists.
84 | #>
85 | function Get-WebFile {
86 | [CmdletBinding()]
87 | param(
88 | [Parameter(Mandatory = $true)][string]$DownloadUrl ,
89 | [Parameter(Mandatory = $true)][string]$TargetFilePath,
90 | [Parameter(Mandatory = $false)][bool]$SkipIfAlreadyExists = $true
91 | )
92 |
93 | Write-Verbose ("Downloading installation files from URL: $DownloadUrl to $TargetFilePath")
94 | $targetFolder = Split-Path $TargetFilePath
95 |
96 | #See if file already exists and skip download if told to do so
97 | if ($SkipIfAlreadyExists -and (Test-Path $TargetFilePath)) {
98 | Write-Verbose "File $TargetFilePath already exists. Skipping download."
99 | return $TargetFilePath
100 |
101 | }
102 |
103 | #Make destination folder, if it doesn't already exist
104 | if ((Test-Path -path $targetFolder) -eq $false) {
105 | Write-Verbose "Creating folder $targetFolder"
106 | New-Item -ItemType Directory -Force -Path $targetFolder | Out-Null
107 | }
108 |
109 | #Download the file
110 | for ($i = 1; $i -le 5; $i++) {
111 | try {
112 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; # Enable TLS 1.2 as Security Protocol
113 | $WebClient = New-Object System.Net.WebClient
114 | $WebClient.DownloadFile($DownloadUrl, $TargetFilePath)
115 | Write-Verbose "File $TargetFilePath download."
116 | return $TargetFilePath
117 | }
118 | catch [Exception] {
119 | Write-Verbose "Caught exception during download..."
120 | if ($_.Exception.InnerException) {
121 | $exceptionMessage = $_.InnerException.Message
122 | Write-Verbose "InnerException: $exceptionMessage"
123 | }
124 | else {
125 | $exceptionMessage = $_.Message
126 | Write-Verbose "Exception: $exceptionMessage"
127 | }
128 | }
129 | }
130 | Write-Error "Download of $DownloadUrl failed $i times. Aborting download."
131 | }
132 |
133 | <#
134 | .SYNOPSIS
135 | Invokes process and waits for process to exit.
136 | .PARAMETER FileName
137 | Name of executable file to run. This can be full path to file or file available through the system paths.
138 | .PARAMETER Arguments
139 | Arguments to pass to executable file.
140 | .PARAMETER ValidExitCodes
141 | List of valid exit code when process ends. By default 0 and 3010 (restart needed) are accepted.
142 | #>
143 | function Invoke-Process {
144 | [CmdletBinding()]
145 | param (
146 | [string] $FileName = $(throw 'The FileName must be provided'),
147 | [string] $Arguments = '',
148 | [Array] $ValidExitCodes = @()
149 | )
150 |
151 | Write-Host "Running command '$FileName $Arguments'"
152 |
153 | # Prepare specifics for starting the process that will install the component.
154 | $startInfo = New-Object System.Diagnostics.ProcessStartInfo -Property @{
155 | Arguments = $Arguments
156 | CreateNoWindow = $true
157 | ErrorDialog = $false
158 | FileName = $FileName
159 | RedirectStandardError = $true
160 | RedirectStandardInput = $true
161 | RedirectStandardOutput = $true
162 | UseShellExecute = $false
163 | Verb = 'runas'
164 | WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
165 | WorkingDirectory = $PSScriptRoot
166 | }
167 |
168 | # Initialize a new process.
169 | $process = New-Object System.Diagnostics.Process
170 | try {
171 | # Configure the process so we can capture all its output.
172 | $process.EnableRaisingEvents = $true
173 | # Hook into the standard output and error stream events
174 | $errEvent = Register-ObjectEvent -SourceIdentifier OnErrorDataReceived $process "ErrorDataReceived" `
175 | `
176 | {
177 | param
178 | (
179 | [System.Object] $sender,
180 | [System.Diagnostics.DataReceivedEventArgs] $e
181 | )
182 | foreach ($s in $e.Data) { if ($s) { Write-Host $err $s -ForegroundColor Red } }
183 | }
184 | $outEvent = Register-ObjectEvent -SourceIdentifier OnOutputDataReceived $process "OutputDataReceived" `
185 | `
186 | {
187 | param
188 | (
189 | [System.Object] $sender,
190 | [System.Diagnostics.DataReceivedEventArgs] $e
191 | )
192 | foreach ($s in $e.Data) { if ($s -and $s.Trim('. ').Length -gt 0) { Write-Host $s } }
193 | }
194 | $process.StartInfo = $startInfo;
195 | # Attempt to start the process.
196 | if ($process.Start()) {
197 | # Read from all redirected streams before waiting to prevent deadlock.
198 | $process.BeginErrorReadLine()
199 | $process.BeginOutputReadLine()
200 | # Wait for the application to exit for no more than 5 minutes.
201 | $process.WaitForExit(300000) | Out-Null
202 | }
203 | # Ensure we extract an exit code, if not from the process itself.
204 | $exitCode = $process.ExitCode
205 | # Determine if process requires a reboot.
206 | if ($exitCode -eq 3010) {
207 | Write-Host 'The recent changes indicate a reboot is necessary. Please reboot at your earliest convenience.'
208 | }
209 | elseif ($ValidExitCodes.Contains($exitCode)) {
210 | Write-Host "$FileName exited with expected valid exit code: $exitCode"
211 | # Override to ensure the overall script doesn't fail.
212 | $LASTEXITCODE = 0
213 | }
214 | # Determine if process failed to execute.
215 | elseif ($exitCode -gt 0) {
216 | # Throwing an exception at this point will stop any subsequent
217 | # attempts for deployment.
218 | throw "$FileName exited with code: $exitCode"
219 | }
220 | }
221 | finally {
222 | # Free all resources associated to the process.
223 | $process.Close();
224 | # Remove any previous event handlers.
225 | Unregister-Event OnErrorDataReceived -Force | Out-Null
226 | Unregister-Event OnOutputDataReceived -Force | Out-Null
227 | }
228 | }
229 |
230 | ###################################################################################################
231 | #
232 | # Script Specific Functions
233 | #
234 |
235 | <#
236 | .SYNOPSIS
237 | Download and install OneDrive for Business client application.
238 | #>
239 | function Install-OneDriveClient{
240 | #Disable the tutorial that shows at the end of the OneDrive Setup
241 | New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive" -Force | Out-Null
242 | New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive" -Name "DisableTutorial" -Value "00000001" -PropertyType DWORD -Force | Out-Null
243 |
244 | $onedriveSetupExePath = "$env:userprofile/Downloads/OneDriveSetup.exe"
245 | Get-WebFile -DownloadUrl "https://go.microsoft.com/fwlink/p/?LinkId=248256" -TargetFilePath $onedriveSetupExePath -SkipIfAlreadyExists $true
246 | Invoke-Process -FileName $onedriveSetupExePath -Arguments '/allUsers /silent'
247 | }
248 |
249 | <#
250 | .SYNOPSIS
251 | Turn on prompt that helps user move their known folders (Documents, Pictures, etc) to OneDrive.
252 | #>
253 | function Set-PromptToMoveKnownFoldersToOneDrive{
254 | param([Parameter(Mandatory=$true)][string] $tenantId)
255 |
256 | New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive" -Force | Out-Null
257 | New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive" -Name "KFMOptInWithWizard" -Value $tenantId -PropertyType String -Force | Out-Null
258 | }
259 |
260 | <#
261 | .SYNOPSIS
262 | Download and install OneDrive for Business client application.
263 | .PARAMETER TenantId
264 | Tenant Id for Office 365 subscription.
265 | .PARAMETER size
266 | Maximum size of file to allow to be uploaded to OneDrive.
267 | #>
268 | function Set-OneDriveFileMaximumSize{
269 | param(
270 | [Parameter(Mandatory=$true)][string] $tenantId,
271 | [string] $size = "0005000" #5 GB
272 | )
273 |
274 | New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive" -Force | Out-Null
275 | New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive\DiskSpaceCheckThresholdMB" -Force | Out-Null
276 | New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive\DiskSpaceCheckThresholdMB" -Name $tenantId -Value $size -PropertyType DWORD -Force | Out-Null
277 |
278 | }
279 |
280 | <#
281 | .SYNOPSIS
282 | Set OneDrive to download files on demand
283 | #>
284 | function Set-OneDriveDownloadFilesOnDemand{
285 | New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive" -Force | Out-Null
286 | New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive" -Name "FilesOnDemandEnabled" -Value "00000001" -PropertyType DWORD -Force | Out-Null
287 | }
288 |
289 | <#
290 | .SYNOPSIS
291 | Set OneDrive to sign in with windows credentials.
292 | #>
293 | function Set-SignIntoOneDriveWithDomainCreds{
294 | New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive" -Force | Out-Null
295 | New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive" -Name "SilentAccountConfig" -Value "00000001" -PropertyType DWORD -Force | Out-Null
296 | }
297 |
298 |
299 | ###################################################################################################
300 | #
301 | # Main execution block.
302 | #
303 |
304 | try {
305 | Write-Host "Verifying running as administrator."
306 | if (-not (Get-RunningAsAdministrator)) {
307 | Write-Error "Please re-run this script as Administrator."
308 | }
309 |
310 | Install-OneDriveClient
311 |
312 | Set-OneDriveDownloadFilesOnDemand
313 |
314 | if ((gwmi win32_computersystem).partofdomain -eq $true) {
315 | Set-SignIntoOneDriveWithWindowCreds
316 | } else {
317 | Write-Host "Computer is not joined to domain. OneDrive will not be set to use windows credentials for login."
318 | }
319 |
320 | if ([String]::IsNullOrEmpty($TenantId) -eq $false)
321 | {
322 | Set-PromptToMoveKnownFoldersToOneDrive -tenantId $TenantId
323 | Set-OneDriveFileMaximumSize -tenantId $TenantId
324 | }else {
325 | Write-Host 'Warning: Tenant Id not specified. User will not will not be prompted to move known folders to OneDrive.' -ForegroundColor 'Yellow'
326 | Write-Host 'Warning: Tenant Id not specified. Maximum size for synced files will not be set.' -ForegroundColor 'Yellow'
327 | }
328 |
329 | Write-Host -Object "Script completed successfully." -ForegroundColor Green
330 | }
331 | finally {
332 | # Restore system to state prior to execution of this script.
333 | Pop-Location
334 | }
335 |
--------------------------------------------------------------------------------
/TemplateManagement/PowerShell/GenericPreparation/Prepare-Updates.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | This script installs updates for a Windows 10 machine and turns off automatic updates to avoid class disruption.
4 | #>
5 |
6 | [CmdletBinding()]
7 | param( )
8 |
9 | ###################################################################################################
10 | #
11 | # PowerShell configurations
12 | #
13 |
14 | # NOTE: Because the $ErrorActionPreference is "Stop", this script will stop on first failure.
15 | $ErrorActionPreference = "Stop"
16 |
17 | # Ensure we set the working directory to that of the script.
18 | Push-Location $PSScriptRoot
19 |
20 | # Discard any collected errors from a previous execution.
21 | $Error.Clear()
22 |
23 | # Configure strict debugging.
24 | Set-PSDebug -Strict
25 |
26 | ###################################################################################################
27 | #
28 | # Handle all errors in this script.
29 | #
30 |
31 | trap {
32 | # NOTE: This trap will handle all errors. There should be no need to use a catch below in this
33 | # script, unless you want to ignore a specific error.
34 | $message = $Error[0].Exception.Message
35 | if ($message) {
36 | Write-Host -Object "`nERROR: $message" -ForegroundColor Red
37 | }
38 |
39 | Write-Host "`nThe script failed to run.`n" -ForegroundColor Red
40 |
41 | exit -1
42 | }
43 |
44 | ###################################################################################################
45 | #
46 | # Functions
47 | #
48 |
49 | <#
50 | .SYNOPSIS
51 | Returns true is script is running with administrator privileges and false otherwise.
52 | #>
53 | function Get-RunningAsAdministrator {
54 | [CmdletBinding()]
55 | param()
56 |
57 | $isAdministrator = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
58 | $null = @(
59 | Write-Verbose "Running with$(if(-not $isAdministrator) {"out"}) Administrator privileges."
60 | )
61 | return $isAdministrator
62 | }
63 |
64 | <#
65 | .SYNOPSIS
66 | Funtion will install a required updates. User will be notified if restart is needed.
67 | #>
68 | function Install-OsUpdates {
69 | Write-Host "Installing tools needed to update the operation system."
70 | Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null
71 | Install-Module PSWindowsUpdate -Force | Out-Null
72 |
73 | Write-Host "Installing required updates available from Microsoft Update."
74 | $updatesInfo = Get-WUInstall -MicrosoftUpdate -AcceptAll
75 | Write-Output $updatesInfo | Format-Table
76 |
77 | $updatesRequiringReboot = @($updatesInfo | where {$_.RebootRequired -eq $true})
78 | if ($updatesRequiringReboot.Count -gt 0)
79 | {
80 | Write-Host "Please restart the computer. Reboot required for updates to be fully installed." -ForegroundColor Yellow
81 | }
82 | }
83 |
84 | <#
85 | .SYNOPSIS
86 | Turn of automatic updates for the operating system to avoid interruptions during class hours.
87 | #>
88 | function Stop-AutoOsUpdates{
89 | Write-Host "Turning off auto-update for operating system."
90 | New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\AU" -Force | Out-Null
91 | New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\AU" -Name "NoAutoUpdate" -Value "1" -PropertyType DWORD -Force | Out-Null
92 | }
93 |
94 |
95 | ###################################################################################################
96 | #
97 | # Main execution block.
98 | #
99 | try {
100 | Write-Host "Verifying running as administrator."
101 | if (-not (Get-RunningAsAdministrator)) {
102 | Write-Error "Please re-run this script as Administrator."
103 | }
104 |
105 | Install-OsUpdates
106 |
107 | Stop-AutoOsUpdates
108 |
109 | Write-Host -Object "Script completed successfully." -ForegroundColor Green
110 | }
111 | finally {
112 | # Restore system to state prior to execution of this script.
113 | Pop-Location
114 | }
115 |
--------------------------------------------------------------------------------
/TemplateManagement/PowerShell/GenericPreparation/Readme.md:
--------------------------------------------------------------------------------
1 | # Azure Lab Services VM preparation
2 |
3 | This folder contains scripts useful for setting up an Azure Lab Services VM for the first time. They are fully described in the [How to prepare a windows template](https://docs.microsoft.com/azure/lab-services/classroom-labs/how-to-prepare-windows-template) article.
4 |
5 | 1. **Prepare-Updates.ps1**. This script installs updates for a Windows client machine and turns off automatic updates to avoid class disruption.
6 | 2. **Prepare-OneDrive**. This script prepares a the OneDrive install for a Windows client machine for a generic class.
7 | 3. **Prepare-MicrosoftStoreApplications.ps1**. This script prepares a computer for class by aiding in the deletion of unneeded Microsoft Store applications. The remaining Microsoft Store applications are updated.
8 |
--------------------------------------------------------------------------------