How to automate service desk tasks with a PowerShell portal
Windows PowerShell is widely recognized as a powerful command-line tool for managing Windows systems. Its deep integration with the Windows OS enables administrators to perform tasks at scale, often replacing or complementing GUI-based management. However, PowerShell's capabilities extend beyond system administration. It can also be used to create custom applications, such as a user self-service portal to automate service desk functions.
This tutorial demonstrates how to build a proof-of-concept self-service portal using PowerShell. You will learn the following:
- The underlying code structure.
- Implementation techniques.
- Examples of common user issues that can be automated.
- How to deploy the portal to users.
Why build a self-service portal to automate service desk tasks?
In many organizations, certain problems recur frequently, leading to a high volume of help desk calls. Examples include password resets, lost network drive mappings and low disk space. By automating fixes to these common issues, you can empower users to resolve them independently, reducing the burden on IT staff and improving user satisfaction.
PowerShell's ability to interact deeply with Windows makes it an ideal tool for creating a portal to automate service desk workflows. While the example provided here is a proof of concept, it can be customized to fit your organization's needs.
Features of the self-service portal
The self-service portal is a simple GUI built using PowerShell. It includes the following options:
- Reset Password. Enables users to reset their passwords.
- Map Network Drive. Restores lost network drive mappings.
- Reclaim Disk Space. Launches a disk cleanup utility.
- Request Support. Sends a support request -- e.g., via email.
- Exit. Closes the portal.
Each feature includes error handling to ensure the portal does not become a source of frustration.
Building the portal: Key components
1. Load required assemblies
PowerShell supports GUI development through Windows Forms or Windows Presentation Foundation (WPF). This example uses Windows Forms for simplicity and ease of use. The necessary assemblies are loaded at the beginning of the script.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
2. Create the form
The form serves as the main window of the portal. It is created as an object of type System-Windows.Forms.Form:
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "Self-Service Portal"
$Form.Size = New-Object System.Drawing.Size(400, 350)
$Form.BackColor = "White"
$Form.StartPosition = "CenterScreen"
3. Add GUI elements
The portal includes labels, text boxes and buttons. Each element is created as an object, its attributes are defined and it is added to the form. For example, the Username label and text box are created as follows.
# Create the label and textbox for username
$UserLabel = New-Object System.Windows.Forms.Label
$UserLabel.Text = "UserName:"
$UserLabel.Location = New-Object System.Drawing.Point(20, 20)
$UserLabel.Size = New-Object System.Drawing.Size(100, 20)
$Form.Controls.Add($UserLabel)
$UserBox = New-Object System.Windows.Forms.TextBox
$UserBox.Location = New-Object System.Drawing.Point(120, 18)
$UserBox.Size = New-Object System.Drawing.Size(200, 20)
$Form.Controls.Add($UserBox)
4. Add action buttons and error handling
Buttons are created similarly, with click actions defined to execute specific tasks. For example, the Reset Password button includes error handling to ensure a username is entered.
$ResetPasswordButton = New-Object System.Windows.Forms.Button
$ResetPasswordButton.Text = "Reset Password"
$ResetPasswordButton.Location = New-Object System.Drawing.Point(20, 60)
$ResetPasswordButton.Size = New-Object System.Drawing.Size(140, 30)
$ResetPasswordButton.BackColor = "LightGray"
$ResetPasswordButton.Add_Click({
$username = $UserBox.Text.Trim()
if (-not $username) {
[System.Windows.Forms.MessageBox]::Show("Please enter a username.")
return
}
Try {
# AD password reset (requires RSAT or AD module)
# Set-ADAccountPassword -Identity $username -Reset -NewPassword (ConvertTo- SecureString -AsPlainText "Password123!" -Force)
$StatusLabel.Text = "Password reset for $username."
} Catch {
$StatusLabel.Text = "Error resetting password: $_"
}
})
$Form.Controls.Add($ResetPasswordButton)
Example features in action to automate service desk operations
Here's how each button in the self-service portal functions to handle common support requests.
Reset Password
When a user clicks the Reset Password button, the script checks if a username has been entered. If not, it displays an error message. If a username is provided, the script simulates a password reset.
Map Network Drive
This feature uses the New-PSDrive cmdlet to restore a network drive mapping. The example uses a placeholder path.
New-PSDrive -Name "Z" -Root "\\<server>\<Share>" -PSProvider FileSystem
Reclaim Disk Space
This option launches the Windows Disk Cleanup utility, enabling users to free up space on their drives.
Request Support
The Request Support button sends an email using the Send-MailMessage cmdlet. However, Microsoft considers this cmdlet obsolete due to security concerns. While it is included for demonstration purposes, it is not recommended for production.
Exit button
The Exit button closes the portal:
$ExitButton.Add_Click({
$Form.Close()
})
Convert the script to an executable
To make the portal user-friendly, you can convert the PowerShell script into an executable using the PS2EXE utility. This lets you create a desktop shortcut for users, enabling them to launch the portal with a simple double-click.
Transform IT support with PowerShell
PowerShell's versatility makes it an excellent tool for creating a self-service portal, empowering users to resolve common issues independently. The proof-of-concept script discussed in the video demonstrates how to build a customizable portal with features like password resets, network drive mapping and disk cleanup. With some customization, this portal can become an asset for any organization, reducing help desk workloads and improving UX.
Brien Posey is a former 22-time Microsoft MVP and a commercial astronaut candidate. In his more than 30 years in IT, he has served as a lead network engineer for the U.S. Department of Defense and a network administrator for some of the largest insurance companies in America.
Build a PowerShell Self-Service Portal for IT Support // Scripting Tutorial
Editor's note: The following transcript has been lightly edited for length and clarity.
Brien Posey: Windows PowerShell is probably best known as a command-line tool you can use to manage Windows. After all, PowerShell has a number of commands that provide deep integration into the Windows operating system. With a couple of minor exceptions, anything you can do from the GUI can also be done through PowerShell, and PowerShell allows you to manage Windows at scale.
You can also leverage that deep integration with the Windows operating system for other purposes. For example, you could build a self-service user portal in almost any organization.
Specific problems come up repeatedly, and oftentimes those problems result in the user making a call to the help desk. But what if you could keep the user from having to call the help desk? What if you could automate the solution to those problems that the user is having? And what if you knew which issues occur most often in your organization and came up with automated solutions to those problems? Well, you can do exactly that through PowerShell.
I've created a self-service portal to show you how this works. However, the portal is only intended as a proof of concept. If you want to use it in your organization, you'll have to adapt the code to your organization's infrastructure.
Self-service portal features
I've created a PowerShell script called Portal.ps1. I will run that script to show you what my self-service portal looks like. Once again, this is just a proof of concept. If you have specific problems that come up repeatedly in your organization, you can build an automated solution to those problems and add those to the dashboard I've created. You could even replace some of the buttons that I've made with some buttons of your own. This is very customizable.
Let's look at what we've got going on here. This self-service portal is meant for users to open whenever they have technical problems. The portal has a few different options. The user can reset their password, map a network drive, reclaim disk space if they're starting to run low on hard drive space, or request support. There's also an Exit button.
An important thing I've done in creating the script is to bake in some error handling. We don't want the self-service portal, which is supposed to solve users' problems, to become another problem.
Buttons on the portal's dashboard
As a demonstration of the script's error handling, let's suppose the user wants to reset their password. When the user clicks Reset Password, a pop-up tells them, "Please enter a username."
You'll notice that the PowerShell console did not display an error. That's because it handled the error before it occurred. I'll click OK. If I enter my username and click Reset Password, I get a message saying that Brien's password was reset.
You'll notice that the script didn't prompt me for a new password, and the reason for that is straightforward. I'm not actually performing a password reset. I didn't want to reset my password. A couple of lines of code are commented out. You'll see what that looks like in a few minutes.
Next, we have the Map Network Drive button. The idea here is that users often have network drives mapped to their data or applications, and sometimes Windows loses that drive mapping. Rather than contacting the help desk, the user could click a Map Drive button to restore that drive mapping automatically.
If I click Map Network Drive, I will likely get an error message. Yeah, you can see the error message. The reason for that is that I'm not mapping to a real path. You can see the command right here:
New-PSDrive -Name "Z" -Root "\\<server>\<Share>" -PSProvider …
New-PSDrive is the command that you use within PowerShell to map a network drive. I'm mapping the "Z" drive, -Root, and then I have a fake path: "\\<server>\<Share>. Then, -PSProvider would be FileSystem, indicating that I want a file system drive.
The portal also has a Reclaim Disk Space button. If the user clicks on this button, it will launch a Windows utility allowing them to choose the disk they want to clean up and then perform some basic cleanup options to free up some disk space.
Finally, the portal has Request Support. I will probably get an error because I don't have a mail client installed on this machine.
Building the PowerShell portal
That being the case, let's look at the code associated with the self-service portal.
Load assemblies
The very first line of code loads one of the necessary assemblies, and that assembly is called System.Windows.Forms:
Add-Type -AssemblyName System.Windows.Forms
This brings up an important point. PowerShell is a command-line interface, but as my self-service portal demonstrates, it can do graphical user interfaces. When it comes to building a graphical user interface, you have two main choices. You can use Windows Forms, or you can use the Windows Presentation Foundation. In this case, I've chosen to use Windows Forms because building a GUI from Windows Forms tends to be a little bit easier. There's more documentation, and the process is a bit more straightforward. I find it easier. That's what I chose to use.
Windows Presentation Foundation is a viable option, but you would typically want to use WPF to build a more modern GUI or include advanced GUI functionality, such as animations.
Let's look at how the rest of the code works. Here, I'm adding another assembly:
Add-Type -AssemblyName System.Drawing
Create the form
Next, we create a form. In Windows Forms, a form is just a window. If I were to go back to my application, you'd notice that all these buttons exist within a window, and that window is the form.
$Form = New-Object System.Windows.Forms.Form
You can see that we're creating a form as a new object of type System.Windows.Forms.Form. I'm calling the form "Self-Service Portal." Looking at the very top of the portal's interface, you can see that we have the words "Self-Service Portal." That's where that came from.
Then, we're defining the size of the form -- in this case, 400 pixels by 350 pixels -- setting the background to white, and placing it at the center of the screen. That's how we create a form.
Create the GUI elements
Once our form has been created, we will set up the various GUI elements we will use. In this case, we have a few different GUI elements.
We have some labels. A label is a mechanism that lets you put a text string on the screen. We have a label for the word "Username." We have a "Status" label. You can see the phrase "Service Request Sent" done via a $StatusLabel.
Then, we have some buttons. I have a Password Reset, Map Drive, Request Support, Reclaim Disk Space and Exit button.
We also have a textbox next to the username field.
Those are the GUI elements.
In the script, we define each GUI element, set attributes on the GUI element, and then add the GUI element to the form.
Let's look at how this works:
$UserLabel = New-Object System.Windows.Forms.Label
$UserLabel.Text = "UserName:"
$UserLabel.Location = New-Object System.Drawing.Point(20, 20)
$UserLabel.Size = New-Object System.Drawing.Size(100, 20)
$Form.Controls.Add($UserLabel)
Here, we create a label object called $UserLabel following these steps:
- We create a new object of type System.Windows.Forms.Label.
- We set the text to "Username:".
- We define the location where it appears on the screen. It is 20 pixels to the right and 20 pixels from the top of the screen.
- We set the label size. The label is 100 pixels wide by 20 pixels high. We're probably not using all that space, but we could.
- We add the $UserLabel label object to the form. The variable $ Form is associated with the form we created. The $Form variable is followed by Controls.Add.
Creating a label object works similarly for all the other graphical elements, whether we're adding a button, a textbox or something else. You make the associated object, define the attributes you want to use, and then add the object to the form.
You can see that we're doing the same thing for the textbox. The only difference is that this time, instead of creating a label object, we're creating a textbox object. Once again, we're setting the location and size and adding the textbox to the form.
We do the same thing with all the other GUI objects we create.
Button click actions
Now that I've shown you how the GUI works, I want to show you how the button click actions and some error handling work.
In this block of code, we set up the Reset Password button:
$ResetPasswordButton = New-Object System.Windows.Forms.Button
$ResetPasswordButton.Text = "Reset Password"
$ResetPasswordButton.Location = New-Object System.Drawing.Point(20, 60)
$ResetPasswordButton.Size = New-Object System.Drawing.Size(140, 30)
$ResetPasswordButton.BackColor = "LightGray"
$ResetPasswordButton.Add_Click({
$username = $UserBox.Text.Trim()
if (-not $username) {
[System.Windows.Forms.MessageBox]::Show("Please enter a username.")
return
}
Try {
# AD password reset (requires RSAT or AD module)
# Set-ADAccountPassword -Identity $username -Reset -NewPassword (ConvertTo- SecureString -AsPlainText "Password123!" -Force)
$StatusLabel.Text = "Password reset for $username."
} Catch {
$StatusLabel.Text = "Error resetting password: $_"
}
})
$Form.Controls.Add($ResetPasswordButton)
Creating a button is exactly like building any other GUI element.
- We begin by creating an object. In this case, that object is of type System.Windows.Forms.Button.
- We set the attributes for the object. We're setting the text on the button; defining the button's location, size and color; and adding a button click action. We have $ResetPasswordButton.Add_Click. Anything that appears between the parentheses will be instructions that will be executed if someone clicks on this button.
As you'll recall, I noted that my Reset Password button wasn't configured to perform a password reset. I had a couple of commands commented out. Let's look at what's going on.
The first thing that we have is:
if (-not $username)
We're checking to see if the username field has been populated. If the user has not entered a username into the username field, then we're going to execute this block of code:
[System.Windows.Forms.MessageBox]::Show("Please enter a username.")
return
This code block will cause PowerShell to display a message box with the phrase, "Please enter a username." If I removed my name from the username field and clicked Reset Password again, I would get the "Please enter a username" message box.
Assuming that a username has been entered, we skip all of this and go to this block of code:
Try {
# AD password reset (requires RSAT or AD module)
# Set-ADAccountPassword -Identity $username -Reset -NewPassword (ConvertTo- SecureString -AsPlainText "Password123!" -Force)
$StatusLabel.Text = "Password reset for $username."
In this code block, a comment says an Active Directory password reset requires RSAT (Remote Server Administration Tools) or the Active Directory module. The PowerShell cmdlet used to reset the password is Set-ADAccountPassword. However, we're not resetting the password because this entire command has been commented out. The pound sign (#) indicates a comment. Anything appearing after a pound sign is treated as a comment.
What we're doing then is updating the $StatusLabel.Text to display the phrase, "Password reset for $username."
If an error occurred, we would execute the Catch and display a different string of text: "Error resetting password: $_".
Two things are going on here that I want to show you. The first is the Try-Catch. In PowerShell, Try allows you to try a code block and see if it will produce an error. We're trying all this to see if an error occurs. If an error doesn't happen, the code can run. If an error does occur, we're going to execute the Catch. The catch is everything between these two brackets:
} Catch {
$StatusLabel.Text = "Error resetting password: $_"
}
We're updating the Status label.
How to change a GUI object's attributes within the script
This leads to the other thing that I wanted to explain. Let's return to where I was defining my GUI objects. We have a GUI object for $StatusLabel, which is exactly like the $Username label I showed you earlier:
$StatusLabel = New-Object System.Windows.Forms.Label
$StatusLabel.Text = ""
$StatusLabel.Location = New-Object System.Drawing.Point(20, 250)
$StatusLabel.Autosize = $True
$Form.Controls.Add($StatusLabel)
We set some attributes for the label:
- We set the attribute .Text equal to nothing (""). There's not initially going to be any text associated with that label.
- We set the location.
- We use an attribute called AutoSize and set it to $True. This lets PowerShell automatically adjust the label size so I don't have to guess how big it needs to be.
Then, we're adding the label object to the form.
I wanted to show you that this label object doesn't initially have any associated text. Still, if I return to the Password Reset button, you'll notice we're updating $StatusLabel.Text, and we're providing a message to appear as the status label. I am showing you this to underscore that you can modify the attributes associated with a GUI object anytime within your script. You're not stuck with those attributes after you define them. You can change them anytime you want.
If you want to change what a label says, call that object -- in this case, $StatusLabel -- and add the text attribute .Text, then set that equal to whatever you want it to say.
Map Drive button configuration
We've got a few other buttons. We have the Map Drive button:
$MapNetworkDriveButton = New-Object System.Windows.Form.Button
$MapNetworkDriveButton.Text = "Map Drive"
$MapNetworkDriveButton.Location = New-Object System.Drawing.Point(200, 60)
$MapNetworkDriveButton.Size = New-Object System.Drawing.Size(140, 30)
$MapNetworkDriveButton.BackColor = "LightGray"
$MapNetworkDriveButton.Add_Click({
Try {
#Example drive mapping
New-PSDrive -Name "Z" -Root "\\<server>\<Share>" -PSProvider "FileSystem"
$StatusLabel.Text = "Mapped Z: to Server \ Share"
} Catch {
$StatusLabel.Text = "Error mapping drive"
}
})
$Form.Controls.Add($MapNetworkDriveButton)
I mentioned earlier that the Map Drive button wasn't pointing to a real network share. The command I'm using for drive mapping is New-PSDrive -Name. Then, I'm calling it "Z." That will be the Z drive. Next, we have -Root and the file path: "\\<server>\<Share>. Finally, -PSProvider is FileSystem.
If this succeeds, we'll get a message in the $StatusLabel that says, "Mapped Z to Server \ Share." If the command fails, the $StatusLabel will be updated to "Error mapping network drive."
Reclaim Disk Space button configuration
We have the button that's used to reclaim some disk space:
# System Cleanup
$CleanupButton = New-Object System.Windows.Form.Button
$CleanupButton.Text = "Reclaim Disk Space"
$CleanupButton.Location = New-Object System.Drawing.Point(20, 110)
$CleanupButton.Size = New-Object System.Drawing.Size(140, 30)
$CleanupButton.BackColor = "LightGray"
$CleanupButton.Add_Click({
Try {
Start-Process "cleanmgr.exe" -ArgumentList "/LowDisk"
$StatusLabel.Text = "System cleanup started."
} Catch {
$StatusLabel.Text = "Cleanup failed: $_"
}
})
$Form.Controls.Add($CleanupButton)
The text on the button is "Reclaim Disk Space." With the click action, our Try is going to be Start-Process. I'm starting a new process, and then cleanmgr.exe -- a built-in Windows utility -- -ArgumentList "/LowDisk". I will run the cleanmgr.exe in low disk space mode and set the $StatusLabel.Text equal to "System cleanup started." If an error occurred, I would change that $StatusLabel to say, "Cleanup failed."
Request Support button configuration
The next thing that we have is the button used to request support:
$SupportButton = New-Object System.Windows.Forms.Button
$SupportButton.Text = "Request Support"
$SupportButton.Location = New-Object System.Drawing.Point(200, 110)
$SupportButton.Size = New-Object System.Drawing.Size(140, 30)
$SupportVPNButton.BackColor = "LightSalmon"
$SupportButton.Add_Click({
Try {
$To = "[email protected]"
$Subject = "Technical Support Request"
$Body = "User $($UserBox.Text) has requested technical support."
Send-MailMessage -To $To -From "$env:[email protected]" -Subject $Subject -Body $Body -SMTPServer smtp.yourdomain.com
$StatusLabel.Text = "Service request sent."
} Catch {
$StatusLabel.Text = "Service request failed to send."
}
})
$Form.Controls.Add($SupportButton)
This button is constructed the same way as the other buttons, though we're doing something a little different. We're setting a variable for $To, $Subject and $Body and constructing an email message using those variables. Then, I'm using the cmdlet Send-MailMessage to send that message.
As I mentioned earlier, this button won't work because I don't have direct access to a mail server. The other problem with this technique is that Microsoft no longer supports Send-MailMessage. A warning message on the Microsoft support page for the Send-MailMessage cmdlet says: "The Send-MailMessage cmdlet is obsolete. This cmdlet doesn't guarantee secure connections to SMTP servers. While no immediate replacement is available in PowerShell, we recommend that you do not use Send-MailMessage." I am pointing this out because you probably shouldn't use this cmdlet in production. Even so, Microsoft's not saying that you can't use it. It's just that there are risks associated with using it.
I only included Send-MailMessage in my script to illustrate some diversity. Each button that I've created does something a little bit different:
- The Reset Password button calls an Active Directory-related cmdlet: Set-ADAccountPassword.
- The Map Drive button maps a network drive via a native PowerShell cmdlet: New-PSDrive.
- The Reclaim Disk Space button calls a native Windows tool. We're starting a process called cleanmgr.exe and providing some arguments.
- The Request Support button initiates the Send-MailMessage cmdlet.
Each of our buttons does something a little different as part of its click action.
Exit button and displaying the form
Then, finally, we have the Exit button:
$ExitButton = New-Object System.Windows.Forms.Button
$ExitButton.Text = "Exit"
$ExitButton.Location = New-Object System.Drawing.Point(280, 200)
$ExitButton.Size = New-Object System.Drawing.Size(60, 30)
$ExitButton.BackColor = "LightBlue"
$ExitButton.Add_Click({
$Form.Close()
})
$Form.Controls.Add($ExitButton)
If the Exit button is clicked, we close the form.
The very last line of code in the script is:
$Form.ShowDialog() Out-Null
This line causes the form to be displayed on the screen.
Convert the self-service portal into an executable
There's one more thing that I want to mention before I wrap up this video.
To launch the self-service portal, I had to open PowerShell, navigate to my scripts folder, and launch the Portal.ps1 script. Expecting your end users to do this is a bit much.
Instead, I recommend going out to GitHub and downloading PS2EXE. PS2EXE is a utility that compiles a PowerShell script into an executable. That way, you can create a desktop icon for your users. They double-click on that icon, launching the executable you've made and putting them directly into the self-service portal you've built.
That's how you build a self-service portal in a PowerShell environment. I'm Brien Posey. Thanks for watching.