4.1.0 And ServiceUI Questions - Using PSADT to prompt user for input during OOBE

I've been developing a package using v4.0.6 to be used with Intune AutoPilot where I'm prompting the tech running the installer for information. With PSADT not having any sort of prompt to receive input from a user I'm building System.Windows.Forms to handle these requests. The script is running under System context but requires user input/interaction, so I've been relying on ServiceUI. I haven't had time to test the new version yet to see how it will handle prompts like this without the use of ServiceUI, but I figured I would at least start a discussion to see if anyone has done something similar and can answer and potentially save me a bit of time. I love the idea of no longer needing to rely on ServiceUI so if this is possible, I would love to change how I'm developing this.

Or in times like this will ServiceUI still be required?

I would love if PSADT was able to give me a customizable prompt option to request input. In my specific case I'm prompting the user for the computer name to rename the computer to and do a bunch of other functions with.

It's not the prettiest but here's the form I'm building and how it's used if anyone is interested or could use something similar for themselves.

    function Get-ComputerName {
        ## Form to prompt for Computer Name
        Add-Type -AssemblyName System.Windows.Forms
        [System.Windows.Forms.Application]::EnableVisualStyles()
        Add-Type -Assembly System.Drawing 

        [int32]$height = 150
        [int32]$width = 350

        $Line1 = "Enter the desired Computer Name"
        $line2 = "Attempt $count of 3"

        $FormIcon =  New-Object system.drawing.icon "$($adtSession.DirFiles)\AppIcon.ico"

        $objImage = [system.drawing.image]::FromFile("$($adtSession.DirFiles)\AppIcon.png")

        $pictureBox = New-Object System.Windows.Forms.PictureBox
        $pictureBox.Width = 60
        $pictureBox.Height = 60
        $pictureBox.Location = New-Object System.Drawing.Point(10,10)
        $pictureBox.Image = $objImage
        $pictureBox.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::Zoom

        $textBox = New-Object System.Windows.Forms.TextBox
        $textBox.Location = New-Object System.Drawing.Point(80,65)
        $textBox.Size = New-Object System.Drawing.Size(190,20)
        
        $label = New-Object System.Windows.Forms.Label
        $label.Font = 'Microsoft Sans Serif,10'
        $label.width = 400
        $label.height = 25
        $label.Location = New-Object System.Drawing.Point(80,10)
        $label.Text = $Line1

        $label2 = New-Object System.Windows.Forms.Label
        $label2.Font = 'Microsoft Sans Serif,10'
        $label2.width = 400
        $label2.height = 25
        $label2.Location = New-Object System.Drawing.Point(80,35)
        $label2.Text = $Line2

        $OKButton = New-Object System.Windows.Forms.Button
        $OKButton.Location = New-Object System.Drawing.Point(30,100)
        $OKButton.Size = New-Object System.Drawing.Size(75,23)
        $OKButton.Text = 'OK'
        $OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
        
        $cancelButton = New-Object System.Windows.Forms.Button
        $cancelButton.Location = New-Object System.Drawing.Point(245,100)
        $cancelButton.Size = New-Object System.Drawing.Size(75,23)
        $cancelButton.Text = 'Cancel'
        $cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel

        $Form = [System.Windows.Forms.Form]::new()
        $form.SuspendLayout()
        $form.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen
        $Form.ClientSize = "$width,$height"
        $Form.text = "Rename Computer"
        $Form.Icon = $FormIcon
        $form.Controls.Add($pictureBox)
        $form.Controls.Add($textBox)
        $form.AcceptButton = $OKButton
        $form.CancelButton = $cancelButton
        $form.Controls.Add($label)
        $form.Controls.Add($label2)
        $Form.controls.Add($OKButton)
        $Form.controls.Add($cancelButton)
        $form.Add_Shown({$textBox.Select()})
        $Form.BringToFront()
        $Form.TopMost = $true
        $Form.ResumeLayout()
        $Result = $Form.ShowDialog()

        if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
            If (($textBox.Text -eq "") -or ($null -eq $textBox.Text)) {
                $ComputerName = $Null
                Write-ADTLogEntry -Message "User entered no data. Attempt $Count of 3." -Source 'Computer Name GUI Prompt'
                Write-Output $ComputerName
            }
            Else {
                $ComputerName = $textBox.Text
                Write-ADTLogEntry -Message "User entered computer name: $ComputerName" -Source 'Computer Name GUI Prompt'
                Write-Output $ComputerName
            }
        }
        Else {
            $ComputerName = $Null
            Write-ADTLogEntry -Message "User cancelled the prompt. Attempt $Count of 3." -Source 'Computer Name GUI Prompt'
            Write-Output $ComputerName
        }
    }


## Gather computer name and ensure it valid contains data. Give 3 attempts before existing script
$Count = 0
    do {
        $Count = $Count+1
        $Name = Get-ComputerName
        If ($Name -match "\s") {
            Write-ADTLogEntry -Message "Computer name entered contained spaces, please try again without spaces. Name entered: $Name" -Severity 2 -Source 'Computer Name GUI Prompt'
            Show-ADTDialogBox "Computer name entered contained spaces, please try again without spaces.`n`nName entered: $Name" -Title "Rename Computer" -Icon Stop
            $Name = $null
        }
        If ($Name.Length -gt "15") {
            Write-ADTLogEntry -Message "Computer name entered is longer than 15 characters, please try again with a shorter name. Name entered: $Name" -Severity 2 -Source 'Computer Name GUI Prompt'
            Show-ADTDialogBox "Computer name entered is longer than 15 characters, please try again with a shorter name.`n`nName entered: $Name" -Title "Rename Computer" -Icon Stop
            $Name = $null
        }
    } until (($null -ne $Name) -or ($count -ge "3"))
    
    If ($null -eq $Name) {
            Write-ADTLogEntry -Message "Computer Name prompt cancelled after $count attempts, exiting deployment." -Severity 3 -Source 'Computer Name GUI Prompt'
            Show-ADTDialogBox "Computer name prompt cancelled after $count attempts, exiting deployment."  -Title "Rename Computer" -Icon Stop
            Close-ADTSession -ExitCode 1603
    }

This is already available in 4.1.0: Show-ADTInstallationPrompt · PSAppDeployToolkit

Ohhh that's exactly what I was hoping for! I just hadn't had time to dive into all the changes yet. I'm working on testing that now! Thank you!

Not a worry my good man! Apologies also, but I just noticed that you want to do this in the OOBE. As you probably know, the ESP sits on top of everything all it's hard to get our UI to display. We're looking to address that in 4.2 via [Feature] Explore UIAccess for PSADT.ClientServer.Client · Issue #1591 · PSAppDeployToolkit/PSAppDeployToolkit · GitHub though.

I've been very successful in getting focus during OOBE using the below script block. However, I would LOVE for PSADT to have something integrated into it! I've been using 4.0.6 and my script is working perfectly, but I'm trying to pivot to 4.1 because the UI is much prettier and no ServiceUI dependency.

Unfortunately I've run into a roadblock that may require another post. I'm trying to authenticate to Azure/Graph during the script which will bring up a browser window. But without ServiceUI that window doesn't appear. I'm playing with Start-ADTProcessAsUser and running PowerShell with piped arguments and it is launching and authenticating but I'm having trouble capturing the output of the token so I can pass it along to the rest of the script.

    Set-ADTRegistryKey -Key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -Name 'ConsentPromptBehaviorUser' -Type 'DWord' -Value '0'
    ## Bring focus to the script and allow for display of Computer Name prompt
    $appId = (Get-Process -Name WWAHost -ErrorAction SilentlyContinue | Select-Object -First 1).Id
    If($null -ne $appId) {
        Write-ADTLogEntry "Shifting focus to deployment script" -Source 'Focus Change'
        $wshell = New-Object -ComObject wscript.shell;
        $wshell.AppActivate($appId)
        Start-Sleep -Seconds 1
        $wshell.SendKeys("+{F10}")
        Start-Sleep -Seconds 10
        $wshell.SendKeys("{ESC}")
    }

Yeah, you can do things like that and I have in the past as well, but that's not without its own issues, etc. We'd like to do something a bit more "first class".

To get around your ServiceUI-less issue in sending the keystrokes, use Send-ADTKeys in our toolkit to get it done as it'll leverage the client/server process to do it as defaultuser0.

I just saw that today as I was going through the documents and was going to look into incorporating that into my deployment tomorrow. I'm happy to hear yall are already working on a much cleaner solution. I'm on a deadline to get this package done so hopefully this works well enough for now and then when 4.2 comes out I'll update it.

I think I figured out how to connect to Azure and handle the pop up for authentication and passing the auth token through. Just using export-clixml inside Start-ADTProcessAsUser to store the secure string and delete the file once authentication is complete. Will be testing it all through Intune tomorrow!

So I've been unsuccessful in getting the Send-ADTKeys to launch from OOBE under the user's context. I see where it's mentioned that it has to use psexec in interactive mode to make it happen. I've tried to call it within the script but it doesn't seem to be working. It works fine on my PC when I run it but just errors when I try on the target machine. What's the best way to call Send-ADTKeys in this scenario? Or do I need to call the entire deployment using PSADT? It just shows exit with code 1.

Copy-ADTFile -Path "$($adtSession.DirFiles)\PSAppDeployToolkit" -Destination "$Env:ProgramFiles\WindowsPowerShell\Modules" -Recurse
$FocusCommands = @(
    "`$WWAHostHandle = (Get-Process -Name WWAHost).MainWindowHandle;
    If(`$null -ne `$WWAHostHandle) {;
        Send-ADTKeys -WindowHandle ([IntPtr]`$WWAHostHandle) -Keys '+{F10}' -WaitDuration (New-TimeSpan -Seconds 10);
        Send-ADTKeys -WindowHandle ([IntPtr]`$WWAHostHandle) -Keys '{ESC}';
    }"
)
Start-ADTProcess -FilePath "$($adtSession.DirFiles)\psexec.exe" -ArgumentList "/accepteula /nobanner /s /i powershell -noexit $FocusCommands" -WaitForChildProcesses -PassThru -CreateNoWindow

You're going about it the wrong way and probably making everything harder. Literally just use Send-ADTKeys while running as system as it'll leverage our client/server process to send the keys to the nominated window handle running in the logged on user's session (defaultuser0, the OOBE user).

I was trying that at first and it would just report in the logs that it couldn't find the window to send keys to. Then I read the tidbit about needing psexec to run interactively and thought that was needed for it to work. I'll have to give it another shot next week and see what happens.

You'll need to give Send-ADTKeys the appropriate handle. You should be able to that from Get-Process -Name wwahost. The property should be MainWindowHandle, I believe

That's what I was doing. I also see there's a Get-ADTWindowHandle command and maybe I can use that too. For sure have more testing next week.