There is a task called “Get Maintenance Consent From Logged In User” that will pop up a pop-up for the user to accept or decline a maintenance session to run on their computer. You can customize the title, message, timeout, and default answer to the maintenance. However, I’m not aware of a way to customize it beyond that. I would like to be able to change the icon to our company’s logo since the question mark and the system icon at the top both just make it look like a virus.
Yeah, that’s just a standard msgbox, I don’t think you can modify the title bar icon. However, if you would like to get a little more invested in the pop up Windows, a few of us in the community have already done the work to convert it into a WPF windows. Here’s my code. You will need the create a function called Get-ConsentFromUser before the task script will function.
Get-ConsentFromUser
<#
.SYNOPSIS
Displays a consent window on the users desktop.
.DESCRIPTION
Displays a consent window and prompts the user to provide input to allow maintenance to continue.
.PARAMETER Title
Specifies the title in the title bar. There is a default value of "System Maintenance".
.PARAMETER Timeout
Specifies the timeout before the program runs the default choice.
.PARAMETER DefaultAnswer
Specifies the default answer that is chosen if the program times out.
.EXAMPLE
Get-ConsentFromUser -title "System Maintenance" -timeout 90 -DefaultAnswer "No"
.OUTPUTS
[Boolean]
#>
[CmdletBinding()]
param(
[string]$title = "System Maintenance",
[int]$timeout = 60,
[ValidateSet('Yes','No')]
[string]$DefaultAnswer = "Yes"
)
$isUserLoggedIn = Invoke-ImmyCommand {
return $true
} -Context User -ErrorAction SilentlyContinue
if(!$isUserLoggedIn) {
return $DefaultAnswer
}
$answer = Invoke-ImmyCommand {
Write-Host "Running command as logged in user, waiting up to $($Using:Timeout) second(s) for consent."
$syncHash = [hashtable]::Synchronized(@{})
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("Title",$Using:Title)
$newRunspace.SessionStateProxy.SetVariable("Timeout",$Using:Timeout)
$newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
$psCmd = [PowerShell]::Create().AddScript({
Add-Type -AssemblyName PresentationFramework,PresentationCore
$Icon = "<Add URL to logo image here>" # <---------------------------NOTE
$Notice = @"
<TextBlock FontSize="16">
Your MSP is trying to run maintenance on your computer.<LineBreak/>
This may trigger several restarts. If you do not wish to proceed now, <LineBreak/>
maintenance will continue unprompted after business hours. <LineBreak/>
</TextBlock>
"@
# Define XAML for the window
[xml]$xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="$Title"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterScreen"
WindowState="Normal"
ShowInTaskbar="False"
Icon="$Icon"
Topmost="True"
ResizeMode="NoResize">
<Window.Resources>
<SolidColorBrush x:Key="BackgroundBrush" Color="#1E1E1E"/>
<SolidColorBrush x:Key="ForegroundBrush" Color="#F1F1F1"/>
<SolidColorBrush x:Key="BorderBrush" Color="#2E2E2E"/>
<Style TargetType="Button">
<Setter Property="Background" Value="{StaticResource BackgroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="Margin" Value="5"/>
</Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}"/>
<Setter Property="Margin" Value="5"/>
</Style>
</Window.Resources>
<Grid Background="{StaticResource BackgroundBrush}">
<StackPanel Margin="10">
$Notice
<TextBlock FontSize="14" Foreground="Yellow" Text="Do you wish to proceed at this time?"/>
<StackPanel Orientation="Horizontal" Margin="0,10,0,0" HorizontalAlignment="Right">
<Button Name="Yes" Content="Yes" FontSize="18" Background="LightYellow" Foreground="Black" FontWeight="Bold" />
<Button Name="No" Content="No" FontSize="18" Background="LightYellow" Foreground="Black" FontWeight="Bold" />
</StackPanel>
</StackPanel>
</Grid>
</Window>
"@
$reader = New-Object System.Xml.XmlNodeReader $xaml
try{
$window = [Windows.Markup.XamlReader]::Load($reader)
}catch{
$_.Exception.InnerException.Message
throw
}
# Define the event handlers for the buttons
$yesHandler = {
$window.DialogResult = $true
}
$noHandler = {
$window.DialogResult = $false
}
# Add event handlers to the buttons
$window.FindName("Yes").Add_Click($yesHandler)
$window.FindName("No").Add_Click($noHandler)
# Show the window and wait for the user to make a choice
$result = $window.ShowDialog()
return $result
})
$null = $psCmd.Runspace = $newRunspace
$RunSpace = $psCmd.BeginInvoke()
while (-not $RunSpace.IsCompleted) {
Start-Sleep -Milliseconds 100
}
$RunspaceResult = $psCmd.EndInvoke($RunSpace)
return $RunspaceResult
} -Context User -Timeout ($Timeout + 10) #Extra 10 seconds to account for any delay in showing the popup
switch($answer) {
$true {return 'Yes'}
$false {return 'No'}
}
And here’s the task combined script:
$ConsentDialogParams = @{}
if($DialogTitle) {
$ConsentDialogParams.Add("Title",$DialogTitle)
}
if($DialogTimeout) {
$ConsentDialogParams.Add("Timeout",$DialogTimeout)
}
if($DialogDefaultAnswer) {
$ConsentDialogParams.Add("DefaultAnswer",$DialogDefaultAnswer)
}
$answer = Get-ConsentFromUser @ConsentDialogParams
if($answer -ne "Yes") {
Stop-ImmySession -FailureMessage "Maintenance was aborted by the logged in user."
}
Do whatever you like with it. You could make a $message parameter, however for me I had trouble getting the text lined up right, so I just put my message directly in the XAML with line breaks. I also have a note in the function script to add your icon, but you could turn that into a parameter as well following the example of the other parameters.
I would love to give this a try! I have never created my own task though. Do you think you could give more instructions or screenshots on how it is set up?
I can provide you with some screenshots of the same task to help a bit. Here’s how I set the parameters:
If you notice under the name that it says DialogTitle will be usable in your script as $DialogTitle, and the same occurs for the other parameters. In the task script I posted earlier, if $DialogTitle is present, then it becomes $Title and passed to the function. Again, it’s the same for the other parameters.
Next is the script section:
I blurred my script since it has my company name, but this is where you would add the task script. For most scripts, you would use the combined script which utilizes a switch statement with the automatic $method variable ($method will only ever be “test”, “get”, or “set”). In this case, the script only ever needs to be in “set” mode since it requires user interaction.
Hope that helps!
I got it figured out. Thank you for the help on that! But how were you able to have the maintenance run at a later time if they say no?
The task itself doesn’t do that, your schedule does. This is based off of the msgbox option that the Immy team has in global. They both do the same thing in the case of a user saying “no”. They will run the Stop-ImmyMaintenanceSession function, which does what it says. The session itself is just stopped, it is not postponed. Your schedule will run the next maintenance session.
Is there a way to have the new task you helped me make, “Get-ConsentFromUser”, to run after the detection? Something like:
Detection > Get consent > Pending for execution > Execution
That is how it was setup before with the original “Get Maintenance Consent From Logged In User” task. I kind of liked it setup that way, but now I can’t recreate it again. Any suggestions?
Not entirely sure what you are asking here. You should place it first on the ordering page, and that should make sure it’s the first thing that runs after detection.
That being said, it is worth noting (and mentioned at the top of the ordering page) that onboarding tasks always occur before regular maintenance items, regardless of the ordering. So you might need to make two cross tenant deployments for this task: one that is set to “onboarding only”, and another that uses the filterscript “Not During Onboarding”
What I meant is that I want it to ask for consent before the maintenance detection or immediately after finishing the detection.
Right now, I have it set to do the detection during the day, then run the maintenance after work hours. So ideally the user can consent to maintenance while working during the day for it to run in the afternoon. It would be nice to have the pop-up say something along the lines of, “Can we do maintenance on your device tonight at 9 p.m.?” That way, the user knows when to expect maintenance.
you could add it first to the ordering, enable execute serially and then add it to test, that way it runs during detection.
unsure how to get it to abort maintenance if the click no
Pretty sure there’s a built-in Stop-ImmySession function. So you would just do something like:
if ($no -eq $true) {
Stop-ImmySession
}