Announcement

How to Place a Workstation Out of Order Remotely using PowerShell

 

If you work in an environment that has public computing sites the workstations deployed to those sites tend to have issues quickly because of high foot traffic. While many IT departments use some sort of signage to deter patrons from using problematic workstations, patrons often ignore those signs. Thus, it makes sense to put a workstation out of order, which ensures patrons are not using an unstable workstation.

In this tutorial, we'll show you how to put a workstation out of order remotely using Active Directory and PowerShell.

Launch the Active Directory MMC snap-in and navigate to the organization unit (OU) for your public workstations. Create a new OU called DFM (down for maintenance) and set your desired security permissions. If you have sub-OUs such as for buildings or floors, create a DFM OU for each building sub-OU to keep track of which workstations are down for maintenance in which building.



On your workstation, download and install Remote Server Administration Tools (RSAT) for Windows 10. Navigate to C:\Windows\System32\WindowsPowerShell\v1.0\Modules and copy the folder ActiveDirectory to a network share accessible by Group Policy. For this guide, we are going to create a folder on NETLOGON share called PowerShell and a subfolder called Modules. We will copy the folder there.


To use this module on a workstation without RSAT installed, we will need to copy the Active Directory module assemblies down to remote computers using Group Policy. On your workstation, navigate to C:\Windows\Microsoft.NET\assembly\GAC_64 and copy the folders Microsoft.ActiveDirectory.Management and Microsoft.ActiveDirectory.Management.Resources to a location accessible by Group Policy. We will be copying these two folders to the PowerShell folder created earlier in a subfolder called Assemblies.


Launch the Group Policy MMC snap-in and open the Group Policy Object that contains the computer settings for the workstations you wish to apply this guide to. At this point we need to specify the policy settings that are going to copy down the PowerShell Active Directory modules and assemblies. Since standard users cannot import modules or assemblies to the global assembly cache, we must copy the files down with Group Policy.

Navigate to Computer Configuration\Preferences\Files and add a new file. For the source file, enter the folder path to the Active Directory Management DLL assembly you saved on the network share accessible by Group Policy. To ensure all contents get copied over, append the text \*.* to the end of the folder path.

For the destination file, enter C:\Windows\Microsoft.NET\assembly\MSIL\Microsoft.ActiveDirectory.Management\v4.0_10.0.0.0__31bf3856ad364e35. Add another new file with the source file path to the Active Directory Management Resources DLL assembly you saved on the network share accessible by Group Policy. Again, to ensure all contents get copied over, append the text \*.* to the end of the folder path. For the destination folder, enter C:\Windows\Microsoft.NET\assembly\MSIL\Microsoft.ActiveDirectory.Management.Resources\v4.0_10.0.0.0_en_31bf3856ad364e35.

Add another new file with the source file path to the Active Directory module you saved on the network share accessible by Group Policy. To ensure all contents get copied over, append the text \*.* to the end of the folder path. For the destination folder, enter C:\Windows\System32\WindowsPowerShell\v1.0\Modules\ActiveDirectory.

Once more, add a new file and for the source file, enter the folder path to the Active Directory module sub-folder en-US you saved on the network share accessible by Group Policy. To ensure all contents get copied over, append the text \*.* to the end of the folder path. For the destination folder, enter C:\Windows\System32\WindowsPowerShell\v1.0\Modules\ActiveDirectory\en-US.

Rename both assembly files to something a little friendlier, such as Active Directory Management assembly and Active Directory Management Resources assembly, respectively. Likewise, rename both module files to something a little friendlier, such as Active Directory PowerShell module and Active Directory PowerShell module en-US.


Now we are going to create the PowerShell script that will check the workstation OU upon logon, determining whether the workstation is in or out of service.

Launch PowerShell ISE and create a new .PS1 file. First things first—we need to tell PowerShell to add the Windows Forms assemblies. Copy and paste the following into your PowerShell script:

# Add form objectAdd-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing


We also need to define the variables used throughout the script.

# Define variables
## Script
$Workstation = Get-ADObject -Filter "Name -like '$env:ComputerName'"
$StartTime = (Get-Date).AddSeconds(45)

## Form
$Form = New-Object System.Windows.Forms.Form
$PictureBox = New-Object System.Windows.Forms.PictureBox
$Label = New-Object System.Windows.Forms.Label
$Button = New-Object System.Windows.Forms.Button
$Timer = New-Object System.Windows.Forms.Timer
$InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState


Two actions are going to take place when the script runs: a timer ticking and possibly a button clicking. For the timer action, we need to tell the script to check its start time against the current time, and if 45 seconds have passed, close the form and sign out. I have put 45 seconds instead of 15 seconds to account for the time it will take to show the desktop environment.

Since we will not be running logon scripts synchronously, Windows will likely show the form behind the "We're getting things ready" screen before showing the desktop. For the button action, we need to tell the script to close the form and sign out if the OK button is clicked.

# Form actions
$Timer_onTick = {
    if($StartTime -LT (Get-Date))
    {
        $Timer.Stop();
        $Form.Close();
        $Form.Dispose();
        $Timer.Dispose();
        $Label.Dispose();
        $Button.Dispose();
        cmd.exe /c "logoff.exe"
    }
}

$Button_onClick = {
    $Timer.Stop();
    $Form.Close();
    $Form.Dispose();
    $Timer.Dispose();
    $Label.Dispose();
    $Button.Dispose();
    cmd.exe /c "logoff.exe"
}


The most important part of this script is the form that notifies users the workstation is out of service. With this form, you can change a few things depending on your environment.

# Form Content
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 261
$System_Drawing_Size.Width = 450
$Form.FormBorderStyle = 2
$Form.ClientSize = $System_Drawing_Size
$Form.ControlBox = $False
$Form.DataBindings.DefaultDataSourceUpdateMode = 0
$Form.Font = New-Object System.Drawing.Font("Microsoft Sans Serif",14.25,0,3,0)
$Form.Name = "form1"
$Form.ShowIcon = $False
$Form.ShowInTaskbar = $False
$Form.Text = "Information Technology Department" # Replace with your IT department name
$Form.TopMost = $True


$PictureBox.DataBindings.DefaultDataSourceUpdateMode = 0

$PictureBox.Image = [System.Drawing.Image]::FromFile('\\ad.home.net\NETLOGON\OOS_Banner.jpg')

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 0
$System_Drawing_Point.Y = 0
$PictureBox.Location = $System_Drawing_Point
$PictureBox.Name = "pictureBox1"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 99
$System_Drawing_Size.Width = 450
$PictureBox.Size = $System_Drawing_Size
$PictureBox.TabIndex = 2
$PictureBox.TabStop = $False

$Form.Controls.Add($PictureBox)

$Label.DataBindings.DefaultDataSourceUpdateMode = 0

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 12
$System_Drawing_Point.Y = 102
$Label.Location = $System_Drawing_Point
$Label.Name = "label1"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 98
$System_Drawing_Size.Width = 426
$Label.Size = $System_Drawing_Size
$Label.TabIndex = 1
$Label.Text = "This workstation is not in service. You will be automatically signed out in 15 seconds.

We apologize for any inconvenience. " # You can edit this message as needed
$Label.TextAlign = 32

$Form.Controls.Add($Label)

$Button.DataBindings.DefaultDataSourceUpdateMode = 0

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 95
$System_Drawing_Point.Y = 213
$Button.Location = $System_Drawing_Point
$Button.Name = "button1"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 36
$System_Drawing_Size.Width = 260
$Button.Size = $System_Drawing_Size
$Button.TabIndex = 0
$Button.Text = "OK"
$Button.UseVisualStyleBackColor = $True
$Button.add_Click($Button_onClick)

$Form.Controls.Add($Button)

$Timer = New-Object System.Windows.Forms.Timer
$Timer.Interval = 1000
$Timer.add_Tick($Timer_onTick)


The first thing you can change is the path to the $PictureBox.Image. For this guide, I am using a simple banner with the text "Out of Service." However, you can replace it with a banner of a corporate logo if you wish. You may also download the image below.


The second thing you can change is the $Form.Text from "Information Technology Department" to something less generic. If you wish to change the time, you can do that also by modifying the $StartTime variable. The last part of the script we need to add is the part that checks to see if the workstation is in the DFM OU, and if it is, start the 45-second timer and display the out-of-service banner.

$Workstation | ForEach-Object{

    # Check if workstation is out of service
    If($_.DistinguishedName -like "*DFM*"){

        # Save the initial state of the form
        $InitialFormWindowState = $Form.WindowState
        # Init the OnLoad event to correct the initial state of the form
        $Form.add_Load($OnLoadForm_StateCorrection)
        # Enable timer
        $Timer.Enabled = $True
        # Start timer
        $Timer.Start()
        # Show the Form
        $Form.ShowDialog() |Out-Null

    } else {

        # Do nothing; the workstation is not out of service.

    }
}


That's all the code we are going to need for this script; your final code should look like the following.

# Add Form Object
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Define Variables
## Script
$Workstation = Get-ADObject -Filter "Name -like '$env:ComputerName'"
$StartTime = (Get-Date).AddSeconds(45)

## Form
$Form = New-Object System.Windows.Forms.Form
$PictureBox = New-Object System.Windows.Forms.PictureBox
$Label = New-Object System.Windows.Forms.Label
$Button = New-Object System.Windows.Forms.Button
$Timer = New-Object System.Windows.Forms.Timer
$InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState

# Form Actions
$Timer_onTick = {
    if($StartTime -LT (Get-Date))
    {
        $Timer.Stop();
        $Form.Close();
        $Form.Dispose();
        $Timer.Dispose();
        $Label.Dispose();
        $Button.Dispose();
        cmd.exe /c "logoff.exe"
    }
}

$Button_onClick = {
    $Timer.Stop();
    $Form.Close();
    $Form.Dispose();
    $Timer.Dispose();
    $Label.Dispose();
    $Button.Dispose();
    cmd.exe /c "logoff.exe"
}

# Form Content
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 261
$System_Drawing_Size.Width = 450
$Form.FormBorderStyle = 2
$Form.ClientSize = $System_Drawing_Size
$Form.ControlBox = $False
$Form.DataBindings.DefaultDataSourceUpdateMode = 0
$Form.Font = New-Object System.Drawing.Font("Microsoft Sans Serif",14.25,0,3,0)
$Form.Name = "form1"
$Form.ShowIcon = $False
$Form.ShowInTaskbar = $False
$Form.Text = "Information Technology Department" # Replace with your IT department name
$Form.TopMost = $True


$PictureBox.DataBindings.DefaultDataSourceUpdateMode = 0

$PictureBox.Image = [System.Drawing.Image]::FromFile('\\ad.home.net\NETLOGON\OOS_Banner.jpg')

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 0
$System_Drawing_Point.Y = 0
$PictureBox.Location = $System_Drawing_Point
$PictureBox.Name = "pictureBox1"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 99
$System_Drawing_Size.Width = 450
$PictureBox.Size = $System_Drawing_Size
$PictureBox.TabIndex = 2
$PictureBox.TabStop = $False

$Form.Controls.Add($PictureBox)

$Label.DataBindings.DefaultDataSourceUpdateMode = 0

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 12
$System_Drawing_Point.Y = 102
$Label.Location = $System_Drawing_Point
$Label.Name = "label1"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 98
$System_Drawing_Size.Width = 426
$Label.Size = $System_Drawing_Size
$Label.TabIndex = 1
$Label.Text = "This workstation is not in service. You will be automatically signed out in 15 seconds.

We apologize for any inconvenience. " # You can edit this message as needed
$Label.TextAlign = 32

$Form.Controls.Add($Label)


$Button.DataBindings.DefaultDataSourceUpdateMode = 0

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 95
$System_Drawing_Point.Y = 213
$Button.Location = $System_Drawing_Point
$Button.Name = "button1"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 36
$System_Drawing_Size.Width = 260
$Button.Size = $System_Drawing_Size
$Button.TabIndex = 0
$Button.Text = "OK"
$Button.UseVisualStyleBackColor = $True
$Button.add_Click($Button_onClick)

$Form.Controls.Add($Button)

$Timer = New-Object System.Windows.Forms.Timer
$Timer.Interval = 1000
$Timer.add_Tick($Timer_onTick)

$Workstation | ForEach-Object{

    # Check if workstation is out of service
    If($_.DistinguishedName -like "*DFM*"){

        # Save the initial state of the form
        $InitialFormWindowState = $Form.WindowState
        # Init the OnLoad event to correct the initial state of the form
        $Form.add_Load($OnLoadForm_StateCorrection)
        # Enable timer
        $Timer.Enabled = $True
        # Start timer
        $Timer.Start()
        # Show the Form
        $Form.ShowDialog() |Out-Null
       
    } else {

        # Do nothing; the workstation is not out of service.

    }
      
}


Save your script to a network share accessible by Group Policy. For this guide, I am going to save my script as Is_Out_of_Service.ps1 to my NETLOGON share, as well as the JPEG for the banner. Finally, we are going to configure our group policy to run this script at logon.

Launch the Group Policy MMC snap-in and open the Group Policy Object that contains the logon scripts for the workstations you wish to apply this guide to. Navigate to User Configuration\Windows Settings\Scripts (Logon/Logoff) and add the script to the Logon item. If you have any other scripts to apply as well, apply this script last.


Lastly, navigate to Computer Configuration\Administrative Templates\System\Scripts and disable the setting Run logon scripts synchronously. This will allow Windows to display the form after loading the desktop.


When you move the computer object to the DFM OU, all users will receive the pop-up above notifying them the workstation is out of service and that the system will sign them out in 15 seconds.

No comments