Powershell Script to Enroll a Device to Intune

In this blog post, we’ll break down the PowerShell script provided, which is used to add a device to Microsoft Intune, a cloud-based service that manages mobile devices and computers. This script automates the process of configuring local machine registry settings and creating a scheduled task to facilitate device enrollment in Intune. Let’s go through the script step by step and understand its functionality.

Script Logic Overview

The script aims to achieve the following main objectives:

  1. Configure necessary registry settings for device enrollment.
  2. Create a scheduled task that triggers the device enrollment process.

Registry Settings Configuration

The script starts by defining various registry paths using variables. These paths represent the location where the required registry keys will be set. The keys are located under HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\MDM.

Registry Path Verification and Creation

The script then checks whether the specified registry paths exist and creates them if they don’t. If the paths do not exist, the script uses New-Item to create the necessary registry keys.

Setting Registry Properties

The script proceeds to set registry properties related to device enrollment. Specifically, it focuses on properties under the MDM key.

  • AutoEnrollMDM: This property is set to DWORD value 1, enabling automatic enrollment in Intune.
  • UseAADCredentialType: This property is set to DWORD value 1, indicating that Azure Active Directory (AAD) credentials will be used for enrollment.
  • MDMApplicationId: This property is set to an empty string, but later in the script, it is checked and updated if needed.

Scheduled Task Creation

The script configures a scheduled task that will execute the Intune enrollment process. Here’s how it’s done:

  • New-ScheduledTaskPrincipal: This cmdlet defines the principal under which the task will run. In this case, it specifies that the task will run under the NT AUTHORITY\SYSTEM account.
  • New-ScheduledTaskSettingsSet: This cmdlet defines various settings for the scheduled task, such as running only if network is available, not stopping when idle, and specifying a time limit for execution.
  • New-ScheduledTaskAction: This cmdlet defines the action that the scheduled task will perform. It specifies the executable (deviceenroller.exe) to run with the argument /c /AutoEnrollMDM, which likely triggers the device enrollment process.
  • New-ScheduledTaskTrigger: This cmdlet defines when the task will be triggered. In this script, the task is scheduled to run once, 5 minutes from the current time.

Modifying the Scheduled Task

The script then registers the scheduled task, retrieves the task, and modifies some of its properties:

  • The start and end boundaries of the trigger are adjusted to allow for a specific execution window.
  • The task’s setting to delete expired tasks after 0 seconds is configured to ensure that expired tasks are deleted immediately.
  • Finally, the modified task is saved using Set-ScheduledTask.

Error Handling

The script includes a try-catch block to handle exceptions. If an exception occurs, the script logs the error, exits gracefully, and returns an appropriate return code.

Conclusion

In summary, this PowerShell script automates the configuration of registry settings and creation of a scheduled task to facilitate device enrollment in Microsoft Intune. By running this script on target devices, administrators can ensure that devices are automatically enrolled in Intune and managed according to organization policies. It’s important to test the script thoroughly in a controlled environment before deploying it to production devices.

# Main Script Logic
try {

#### Local Machine Reg Section ####
#Reg Paths LM
$RegPoliciesLM = "HKLM:\SOFTWARE\Policies"
$RegMSLM = "$RegPoliciesLM\Microsoft"
$RegWINLM = "$RegMSLM\Windows"
$RegCVLM = "$RegWINLM\CurrentVersion"
$RegMDMLM = "$RegCVLM\MDM"


#Test Reg Locations and create if needed LM
$TestPoliciesLM = Test-Path $RegPoliciesLM
If ($TestPoliciesLM -eq $False){New-Item -Path "HKLM:\SOFTWARE" -Name "Policies" -Force}
$MSTestLM = Test-Path $RegMSLM
If ($MSTestLM -eq $False){New-Item -Path "$RegPoliciesLM" -Name "Microsoft" -Force}
$WINTestLM = Test-Path $RegWINLM
If ($WINTestLM -eq $False){New-Item -Path "$RegMSLM" -Name "Windows" -Force}
$CVTestLM = Test-Path $RegCVLM
If ($CVTestLM -eq $False){New-Item -Path "$RegWINLM" -Name "CurrentVersion" -Force}
$MDMTestLM = Test-Path $RegMDMLM
If ($MDMTestLM -eq $False){New-Item -Path "$RegCVLM" -Name "MDM" -Force}


#Test MDM LM
$MDMOldLM = Get-ItemProperty -Path "$RegMDMLM" -ErrorAction SilentlyContinue

If($MDMOldLM -eq $null) {
New-ItemProperty -Path "$RegMDMLM" -Name "AutoEnrollMDM" -PropertyType DWORD -Value "1" -Force
New-ItemProperty -Path "$RegMDMLM" -Name "UseAADCredentialType" -PropertyType DWORD -Value "1" -Force
New-ItemProperty -Path "$RegMDMLM" -Name "MDMApplicationId" -PropertyType String -Value "" -Force
}

$MDMAPPIDLM = Get-ItemProperty -Path "$RegMDMLM" -Name "MDMApplicationId" -ErrorAction SilentlyContinue

If (($MDMAPPIDLM -eq $Null) -or ($MDMAPPIDLM.'MDMApplicationId' -ne ""))
    {
        If ((Get-ItemProperty -Path $RegMDMLM -Name "MDMApplicationId" -ErrorAction SilentlyContinue) -eq $null)  {
            New-ItemProperty -Path "$RegMDMLM" -Name "MDMApplicationId" -PropertyType String -Value "" -Force}
        Else {Set-ItemProperty -Path "$RegMDMLM" -Name "MDMApplicationId" -Value "" -Force}

        If ((Get-ItemProperty -Path $RegMDMLM -Name "AutoEnrollMDM" -ErrorAction SilentlyContinue) -eq $null)  {
            New-ItemProperty -Path "$RegMDMLM" -Name "AutoEnrollMDM" -PropertyType DWORD -Value "1" -Force}
        Else {Set-ItemProperty -Path "$RegMDMLM" -Name "AutoEnrollMDM" -Value "1"  -Force}

        If ((Get-ItemProperty -Path $RegMDMLM -Name "UseAADCredentialType" -ErrorAction SilentlyContinue) -eq $null)  {
            New-ItemProperty -Path "$RegMDMLM" -Name "UseAADCredentialType" -PropertyType DWORD -Value "1" -Force}
        Else {Set-ItemProperty -Path "$RegMDMLM" -Name "UseAADCredentialType" -Value "1"  -Force}


        
    }

###Setup Scheduled Task
$RunTime = (Get-Date).AddMinutes(5)
$STPrin = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U -RunLevel Highest
$Stset = New-ScheduledTaskSettingsSet -RunOnlyIfNetworkAvailable -DontStopOnIdleEnd -ExecutionTimeLimit (New-TimeSpan -Hours 1)
$actionUpdate = New-ScheduledTaskAction -Execute %windir%\system32\deviceenroller.exe -Argument "/c /AutoEnrollMDM"
$triggerUpdate = New-ScheduledTaskTrigger -Once -At $RunTime


Register-ScheduledTask -Trigger $triggerUpdate -Action $actionUpdate -Settings $Stset -TaskName "MDMAutoEnroll" -Principal $STPrin -Force

$TargetTask = Get-ScheduledTask -TaskName "MDMAutoEnroll"
$TargetTask.Triggers[0].StartBoundary = $RunTime.ToString("yyyy-MM-dd'T'HH:mm:ss")
$TargetTask.Triggers[0].EndBoundary = $RunTime.AddMinutes(10).ToString("yyyy-MM-dd'T'HH:mm:ss")
$TargetTask.Settings.DeleteExpiredTaskAfter = "PT0S"
$TargetTask | Set-ScheduledTask



} catch [System.Exception] {
    Write-Log $_ -PrependText "FATAL: An unhandled exception was caught. The script will now exit as failed."
    Exit-Script -ReturnCode -999
    Return
}



Exit-Script -ReturnCode 0 # End successfully

PowerShell Desktop Information Script

The following Powershell script will display information such as the Hostname, IP Address, and Username of the computer hosting it. The script works on both Windows Server Desktop and Windows Server Core and is best implemented as a login script for an environment. Prior to enabling it as a login script, one would have to codesign the script, but that will be covered in another post.

add-type -name user32 -namespace win32 -memberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);'
[win32.user32]::showWindow((get-process -id $pid).mainWindowHandle, 0)

# Load the Winforms assembly
[reflection.assembly]::LoadWithPartialName( "System.Windows.Forms") | out-null

# Create the form
$form = New-Object Windows.Forms.Form

#Configure the form
$form.text = ""
$form.AutoScaleMode = 2
$form.FormBorderStyle = 0
$form.ControlBox = $false
$form.BackColor = [System.Drawing.Color]::Black
$form.TransparencyKey = [System.Drawing.Color]::Black
$form.AllowTransparency = $true
$form.Width = 400
$form.ShowInTaskbar = $false

#Systray Icon
$notifyIcon = New-Object Windows.Forms.NotifyIcon
$notifyIcon.Icon = [System.Drawing.SystemIcons]::Information
$notifyIcon.Text = "DesktopInfo POSH"
$notifyIcon.Visible = $true


#define a context menu
$contextMenu = New-Object Windows.Forms.ContextMenu
$contextMenuExit = New-Object Windows.Forms.MenuItem
$contextMenuExit.Text = "E&xit"
$contextMenuExit.Add_Click({
    $form.Close()
})
#Out-Null is added to suppress output of index
$contextMenu.MenuItems.Add($contextMenuExit) | Out-Null
$notifyIcon.ContextMenu = $contextMenu


$point = New-Object System.Drawing.Point
$point.Y = 0
$point.X = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea.Right - $form.Width
$form.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual
$form.DesktopLocation = $point


# Create the hostName label control and set text, size and location
$hostName = New-Object Windows.Forms.Label
$hostName.Location = New-Object System.Drawing.Point(10,10)
$hostName.Size = New-Object System.Drawing.Size(100,25)
$hostName.Width = 300
$hostName.Font = New-Object System.Drawing.Font($hostName.font.Name,16,[System.Drawing.FontStyle]::Bold)
$hostName.ForeColor = [System.Drawing.Color]::White
$hostName.text = "Host Name: $($env:COMPUTERNAME)"

# Create the userName label control and set text, size and location
$userName = New-Object Windows.Forms.Label
$userName.Location = New-Object System.Drawing.Point(10,40)
$userName.Size = New-Object System.Drawing.Size(100,25)
$userName.Width = 300
$userName.Font = New-Object System.Drawing.Font($hostName.font.Name,16,[System.Drawing.FontStyle]::Bold)
$userName.ForeColor = [System.Drawing.Color]::White
$userName.text = "Username: $($env:USERNAME)"


$NetAdapters = Get-NetAdapter
$IPLabel = @()
$i = 70

foreach($na in $NetAdapters){
        $IP = New-Object Windows.Forms.Label
        $IP.Location = New-Object System.Drawing.Point(10,$i)
        $IP.Size = New-Object System.Drawing.Size(100,25)
        $IP.Width = 500
        $IP.Font = New-Object System.Drawing.Font($hostName.font.Name,16,[System.Drawing.FontStyle]::Bold)
        $IP.ForeColor = [System.Drawing.Color]::White
        $IP | Add-Member -NotePropertyName ifIndex -NotePropertyValue $na.ifIndex
        $AdapterAlias = $na.ifAlias
        if($adapterAlias.length -gt 15){
            $adapterAlias = "$($na.ifAlias)".Substring(0,15)
        }
        $IP.text = "$($AdapterAlias): $((Get-NetIPAddress -InterfaceIndex $na.ifIndex).IPv4Address)"
        $IPLabel+=$IP
        $i+=30
}


#refresh form every 2 seconds
$Timer = New-Object System.Windows.Forms.Timer
$Timer.Interval = 2000
$Timer.Add_Tick({
    $hostName.text = "Host Name: $($env:COMPUTERNAME)"
    $userName.text = "Username: $($env:USERNAME)"
    
    #cycle through known interfaces by index
    foreach($l in $IPLabel){
        $na = Get-NetAdapter -InterfaceIndex $l.ifIndex
        $AdapterAlias = $na.ifAlias
        if($adapterAlias.length -gt 15){
            $adapterAlias = "$($na.ifAlias)".Substring(0,15)
        }
        $l.text = "$($AdapterAlias): $((Get-NetIPAddress -InterfaceIndex $na.ifIndex).IPv4Address)"
    }
    $form.SendToBack()
})
$Timer.Enabled = $true


# Add the controls to the Form
$form.controls.add($hostName)
$form.controls.add($userName)
foreach($l in $IPLabel){
    $form.controls.add($l)
}

# Display the dialog
$form.SendToBack()
$form.ShowDialog()
$timer.Stop()
$notifyIcon.Dispose()

The script starts by adding the user32 namespace from the win32 DLL to access the ShowWindow function, which hides the script’s console window. Then, it loads the System.Windows.Forms assembly to create a form for displaying the desktop information.

The form is configured with various settings such as size, background color, transparency, and positioning on the desktop. It also includes a system tray icon with a context menu that allows users to exit the script.

The script retrieves the host name and username from environment variables and displays them as labels on the form. It also fetches network adapter information and dynamically creates labels to display the IP addresses of each adapter.

The form is refreshed every 2 seconds to update the information displayed. It is a work in progress, and more will be added, as well as a configuration file to allow customization of the information displayed. This is only the beginning.

The script can be viewed and downloaded from my github: https://github.com/Thurdi/POSH-DesktopInfo

Creating an OpenVPN-AS container

Previously:

 

I installed Docker CE on Ubuntu 16.04, but the method described would work on most versions of Linux.

Creating an OpenVPN-AS container:

For this part, I’m going to be posting the scripts and dockerfile that I wrote to create my OpenVPN-AS container.

The dockerfile is a text document that instructs Docker how to build the image.  I’ll be starting from a Ubuntu 16.04 build and will install the required packages, download the OpenVPN-AS package and install it, then change the openvpn user’s password.  After that,  I’ll open communication over the required ports, copy over my entrypoint.sh script, set it as executable, and run it.

The entrypoint.sh script is essentially a startup script for the instance that will launch OpenVPN-AS and, if required, removes files from a previously running instance of the OpenVPN services.  If OpenVPN crashes, I’ll need to stop twistd.pid and then remove the file.

dockerfile:
[shell]
 FROM ubuntu:16.04
 MAINTAINER Thurdi "https://github.com/thurdi";
 USER root
 WORKDIR /
 RUN apt-get update && apt-get install -y wget \
     iptables \
     net-tools \
     psmisc && \
     wget https://swupdate.openvpn.org/as/openvpn-as-2.1.12-
     ubuntu16.amd_64.deb && \
     dpkg -i openvpn-as-2.1.12-Ubuntu16.amd_64.deb && \
     echo "openvpn:password1234" | chpasswd && \
     rm -rf openvpn-as-2.1.12-Ubuntu16.amd_64.deb
 COPY Build/entrypoint.sh /
 RUN chmod +x /entrypoint.sh
 EXPOSE 443/tcp 1194/udp 943/tcp
 VOLUME ["/usr/local/openvpn_as"]
 CMD ["/entrypoint.sh"]
 [/shell]
entrypoint.sh:
[shell]
 #!/bin/bash
 # remove twisted pid
 if ps -p $(cat twistd.pid) > /dev/null 2>&1
 then
     TMP=$(cat twistd.pid)
     kill $TMP
     exit 1
 else
     echo "no twistd.pid found"
 fi
 if [ -e "/twistd.pid" ]; then
    rm -rf twistd.pid &>/dev/null
 fi
 # remove pid file if it exists
 if [ -e "/var/run/openvpnas.pid" ]; then
     rm -f "/var/run/openvpnas.pid" &>/dev/null
 fi
 #start openvpn
 /usr/local/openvpn_as/scripts/openvpnas -n
 [/shell]

Installing Docker

Previously:

I setup SSH on a freshly installed Ubuntu 16.04 server.

Creating the Docker Server:

Next, I’m going to install Docker and get it ready to run the VPN and Plex containers.  For this part, I’m going to cheat a bit and use the script that Docker provides.  The code is as follows:

[shell]

wget https://raw.githubusercontent.com/docker/docker-install/master/install.sh

sudo chmod +x install.sh

sudo ./install.sh

[/shell]

This downloads the [shell]install.sh[/shell] script, adds execute permissions to it, then runs the script.  The Docker script installs the correct version of Docker CE depending on your flavor of Linux, in my case Ubuntu 16.04.

After running the script, verify that Docker is installed by running:

[shell]

sudo docker run hello-world

[/shell]

And with that, Docker has successfully been installed, and I’m ready to move on to installing OpenVPN-AS!

Setting Up SSH

So the first thing I need to do is setup a Linux VM.  I’m choosing to use Ubuntu 16.04 as my host VM.  From a base install of Ubuntu, I’m going to configure SSH by removing password authentication and requiring RSA key based authentication.  

 

Login to the Ubuntu Server and type in:

[shell]sudo nano /etc/ssh/sshd_config[/shell]

Remove the comment (#) from the line that has:

[shell]#PasswordAuthentication yes[/shell]

And change the “yes” to a “no”.  Do not restart the server!

 

Next, I’ll need to copy the public key to the server, but first I’ll need to create a private and public key combination.  I’ll start by downloading PuTTYGen.  I select Generate and follow the instructions to move the mouse to generate a random sequence.  Once the RSA keys have been generated, I’ll save them using the “Save public key” and “Save private key” buttons.

Copy the id_rsa.pub file to the server as ~/.ssh/authorized_keys.

 

Once I have the public key copied over, I’m good to go! I’ll restart the service using:

[shell]sudo service ssh restart[/shell], and I’ll be required to use my private key to connect over SSH.  Easy peasy.

New Project

The Project

I’ve recently started a project where I’m consolidating my VMs into docker containers.  Docker containers are another layer of abstraction for servers and their OS is provided by the host docker server. The applications run inside virtual environments where only the bare minimum required software is running.

 

In my case, I decided to create a docker VM instead of installing docker on the bare metal.  My fileserver is still going to run as its own VM alongside the docker VM, but I’m going to move my Plex server and VPN server to run inside docker.

 

Why Docker?

Docker allows for rapid application deployment. Although it is not the best place to host a VPN server or a Plex server, it does have some appeal in having portability for those servers. Creating a dockerfile with a strict definition for what you want the server to be allows the server to build with a single command.  For my purposes, in my home test environment, this allows me to blow up and rebuild my servers on a whim and makes it a quick process.  Sounds like enough of a win to learn a new technology with great applications for the applications I’m going to write later down the road.  Haha, see what I did there?

 

The Blog

In my blog, I intend on documenting my move to Docker as both notes for myself and notes for you, the reader.  The upcoming posts will detail the steps to recreate an OpenVPN-AS and a Plex server in a series of Docker containers running on a single Docker host VM.  I’ll include everything from setting up the Docker host VM all the way through adding users for the VPN and mapping symlinks for Plex.