WARNING: Mode LastWriteTime Length Name
WARNING: Mode LastWriteTime Length Name
Running command as logged in user, waiting up to 60 second(s) for consent.
ParentContainsErrorRecordException:
Line |
94 | $window = [Windows.Markup.XamlReader]::Load($reader)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Multiple ambiguous overloads found for "Load" and the argument count: "1".
hmmm… I haven’t used this task in a while. I’ll have to take a look at it later. It was pretty much built as a proof of concept because I didn’t like the msgbox. I’ll check it out over the weekend and if I have any reports, I’ll post here
Currently not yet. It kind of slipped my mind, so I’ll check on that in a bit. I’ve started working on a splash screen side project to run during onboarding lol.
The tricky part of that is going to be to somehow have it start and end around the session properly, and persist on reboots. That’s out of scope of this conversation, but somewhat related
@Calvin_Thain According to the Microsoft documentation for the Load method, there are 3 types of arguments it can take (that only take 1 argument): Stream, XamlReader, and XmlReader.
Seeing that the xaml is being passed as an [xml] in powershell, I assumed I would need the XmlReader. I just tested changing the following line:
And it appeared to still function correctly for me. This should help clear the ambiguity in your case, since it explicitly states which type of argument should be used.
I updated the function with the line you suggested and it does get further. What happens is it immediately cancels the session without prompting the user.
$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."
}
Updated “Get-ConsentFromUser” Function:
<#
.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([System.Xml.XmlReader]$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'}
}
@Calvin_Thain Ah, I see. I don’t think powershell can parse the xml correctly, since the $Icon is not being set. You need to fill in the icon with an image url of your choosing (company logo maybe?). If you do not want to set the icon, you can comment out this line:
$Icon = "<Add URL to logo image here>" # <---------------------------NOTE
and remove this line from the xml:
Icon="$Icon"
Alternatively, you can add $newRunspace.SessionStateProxy.SetVariable("Icon",$Using:IconURL)
and remove $Icon = "<Add URL to logo image here>"
This way, you can create a config task text parameter for “IconURL”
Has anyone figured out a “postpone by an hour” button for this? Would be super slick if someone could say “not now” but in an hour (or a user defined timeframe with a max delay)
I don’t have time to make the necessary adjustments, but you could probably add a third button in the xaml, and then in the code-behind logic just hide the pop-up for an hour when it’s clicked (using start-sleep and setting the window to inactive/hidden). Might have to mess around with Immy’s timeout options, though, so it doesn’t cancel after like 5 minutes.
FYI, An issue I had was if the user did not select anything the prompt would stay up even if immy.bot had moved on. I was worried a user select “no” after the timeout had happened and reboot there PC anyway frustrating the user. See the updated version below with the “$dispatcherTimer” section. This script does remove the logo lines so you would have to re-add.
<#
.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
$Notice = @"
<TextBlock FontSize="16">
IntegriCom 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 be skipped and your PC will not be updated. <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"
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="#F1F1F1" Foreground="Black" FontWeight="Bold" />
<Button Name="No" Content="No" FontSize="18" Background="#F1F1F1" Foreground="Black" FontWeight="Bold" />
</StackPanel>
</StackPanel>
</Grid>
</Window>
"@
$reader = New-Object System.Xml.XmlNodeReader $xaml
try{
$window = [Windows.Markup.XamlReader]::Load([System.Xml.XmlReader]$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)
# Set up a dispatcher timer for the timeout
$dispatcherTimer = New-Object Windows.Threading.DispatcherTimer
$dispatcherTimer.Interval = [TimeSpan]::FromSeconds($Timeout)
$dispatcherTimer.IsEnabled = $true
$dispatcherTimer.add_Tick({
$window.DialogResult = $true
$window.Close()
})
# Show the window and wait for the user to make a choice
$result = $window.ShowDialog()
# Stop the timer if the user makes a choice before the timeout
$dispatcherTimer.Stop()
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'}
}
I am reviewing the above and am sorry if I missed anything. Has anyone solved the issue of this being a deployment and therefore running into problems where the computer may still reboot before the deployment runs…
Global Task
Maintenance Consent From Logged In User