Author Posts

March 16, 2018 at 2:38 pm

I have the below script that I need to run from a task schedule

in powershell if I type c:\form\multiscreen.ps1 it runs fine

In the cmd prompt if I type

C:\Users\sbrookma>powershell.exe -executionpolicy bypass -file c:\form\multiscreen.ps1

nothing happens

Is this because it is opening up in a different thread ?


$Global:syncHash = [hashtable]::Synchronized(@{})
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)


# Load WPF assembly if necessary
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')

$psCmd = [PowerShell]::Create().AddScript({
    [xml]$xaml = @"
    
   

   
   
   
    
     
      
          
           
           
        
                       
                     
        
        
    


"@


    $reader=(New-Object System.Xml.XmlNodeReader $xaml)
    
    $syncHash.Window=[Windows.Markup.XamlReader]::Load( $reader )

    [xml]$XAML = $xaml
        $xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | %{
        #Find all of the form types and add them as members to the synchash
        $syncHash.Add($_.Name,$syncHash.Window.FindName($_.Name) )

    }

    $Script:JobCleanup = [hashtable]::Synchronized(@{})
    $Script:Jobs = [system.collections.arraylist]::Synchronized((New-Object System.Collections.ArrayList))

    #region Background runspace to clean up jobs
    $jobCleanup.Flag = $True
    $newRunspace =[runspacefactory]::CreateRunspace()
    $newRunspace.ApartmentState = "STA"
    $newRunspace.ThreadOptions = "ReuseThread"          
    $newRunspace.Open()        
    $newRunspace.SessionStateProxy.SetVariable("jobCleanup",$jobCleanup)     
    $newRunspace.SessionStateProxy.SetVariable("jobs",$jobs) 
    $jobCleanup.PowerShell = [PowerShell]::Create().AddScript({
        #Routine to handle completed runspaces
        Do {    
            Foreach($runspace in $jobs) {            
                If ($runspace.Runspace.isCompleted) {
                    [void]$runspace.powershell.EndInvoke($runspace.Runspace)
                    $runspace.powershell.dispose()
                    $runspace.Runspace = $null
                    $runspace.powershell = $null               
                } 
            }
            #Clean out unused runspace jobs
            $temphash = $jobs.clone()
            $temphash | Where {
                $_.runspace -eq $Null
            } | ForEach {
                $jobs.remove($_)
            }        
            Start-Sleep -Seconds 1     
        } while ($jobCleanup.Flag)
    })
    $jobCleanup.PowerShell.Runspace = $newRunspace
    $jobCleanup.Thread = $jobCleanup.PowerShell.BeginInvoke()  
    #endregion Background runspace to clean up jobs
 Function Update-Display {
    Param (
            $Control,
            $Property,
            $Value,
            [switch]$AppendContent
        )

        # This is kind of a hack, there may be a better way to do this
        If ($Property -eq "Close") {
            $syncHash.Window.Dispatcher.invoke([action]{$syncHash.Window.Close()},"Normal")
            Return
        }

        # This updates the control based on the parameters passed to the function
        $syncHash.$Control.Dispatcher.Invoke([action]{
            # This bit is only really meaningful for the TextBox control, which might be useful for logging progress steps
            If ($PSBoundParameters['AppendContent']) {
                $syncHash.$Control.AppendText($Value)
            } Else {
                $syncHash.$Control.$Property = $Value
            }
        }, "Normal")
    } 
    
$DisplayText = "                      
This is a TextBlock control
with multiple lines of text.
3rd Line
4th Line
This is a TextBlock control This is a TextBlock control
with multiple lines of text. This is a TextBlock control
3rd Line
4th Line
This is a TextBlock control This is a TextBlock control
with multiple lines of text. This is a TextBlock control
3rd Line
4th Line
This is a TextBlock control
with multiple lines of text.
3rd Line
4th Line
This is a TextBlock control This is a TextBlock control
with multiple lines of text. This is a TextBlock control
3rd Line
4th Line
This is a TextBlock control This is a TextBlock control
with multiple lines of text. This is a TextBlock control
3rd Line
4th Line"                   

Update-Display -control TextDisplay -Property Text -Value $DisplayText

#region screenblocker
    Add-Type -AssemblyName System.Windows.Forms
    $screens = [System.Windows.Forms.Screen]::Allscreens | Where-Object Primary -eq $false
    $synchash.screens = New-Object System.Collections.Generic.List[System.Object]
    foreach($screen in $screens){ 
        $synchash.screens.Add($screen.devicename.replace('\','').replace('.','')[-1])
    }
    foreach ($screen in $screens){
        $synchash."$($screen.devicename.replace('\','').replace('.',''))" = $screen
        $newRunspace =[runspacefactory]::CreateRunspace()
        $newRunspace.ApartmentState = "STA"
        $newRunspace.ThreadOptions = "ReuseThread"
        $newRunspace.Open()
        $newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
        $PowerShell = [PowerShell]::Create().AddScript({
            function LoadXaml ($filename){
                $XamlLoader=(New-Object System.Xml.XmlDocument)
                $XamlLoader.Load($filename)
                return $XamlLoader
            }
            $screenNum = $synchash.screens[0]
            $display = "Display" + $screenNum
            $synchash.screens.RemoveAt(0)

            
            $XamlMainWindow = LoadXaml("\blankScreen.xaml")
            $reader = (New-Object System.Xml.XmlNodeReader $XamlMainWindow)
            $syncHash."Window$screenNum" = [Windows.Markup.XamlReader]::Load($reader)
        
        
        
            [xml]$XAML = $XamlMainWindow
            $XamlMainWindow.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach-Object{
            #Find all of the form types and add them as members to the synchash
            $syncHash.Add($("" + $_.Name + $screenNum),$syncHash."Window$screenNum".FindName($_.Name) )
            }

            $syncHash."Window$screenNum".Top = $synchash.$display.bounds.Top;
            $syncHash."Window$screenNum".Left = $synchash.$display.bounds.Left;
            $syncHash."Window$screenNum".Width = $synchash.$display.bounds.Width;
            $syncHash."Window$screenNum".Height = $synchash.$display.bounds.Height;
            $synchash."Window$screenNum".Show()

            $synchash."error$screenNum" = $error
        })
        $PowerShell.Runspace = $newRunspace
        [void]$Jobs.Add((
            [pscustomobject]@{
                PowerShell = $PowerShell
                Runspace = $PowerShell.BeginInvoke()
            }
        ))
    }
    #endregion screenblocker


    $syncHash.Install.Add_Click({
      
        $newRunspace =[runspacefactory]::CreateRunspace()
        $newRunspace.ApartmentState = "STA"
        $newRunspace.ThreadOptions = "ReuseThread"          
        $newRunspace.Open()
        $newRunspace.SessionStateProxy.SetVariable("SyncHash",$SyncHash) 
        $PowerShell = [PowerShell]::Create().AddScript({


        
Function Update-Window {
        Param (
            $Control,
            $Property,
            $Value,
            [switch]$AppendContent
        )

        # This is kind of a hack, there may be a better way to do this
        If ($Property -eq "Close") {
            $syncHash.Window.Dispatcher.invoke([action]{$syncHash.Window.Close()},"Normal")
            Return
        }

        # This updates the control based on the parameters passed to the function
        $syncHash.$Control.Dispatcher.Invoke([action]{
            # This bit is only really meaningful for the TextBox control, which might be useful for logging progress steps
            If ($PSBoundParameters['AppendContent']) {
                $syncHash.$Control.AppendText($Value)
            } Else {
                $syncHash.$Control.$Property = $Value
            }
        }, "Normal")
    }                        
                                                      
start-sleep -Milliseconds 850

$scripttorun = "c:\form\multiping.ps1"

update-window -Control Progress -Property Value -Value 25

                                                      
start-sleep -Milliseconds 850
update-window -Control Progress -Property Value -Value 50
Invoke-Expression $scripttorun
                                                      
start-sleep -Milliseconds 500
update-window -Control Progress -Property Value -Value 75

                                                     
start-sleep -Milliseconds 200
update-window -Control Progress -Property Value -Value 100
        })
        $PowerShell.Runspace = $newRunspace
        [void]$Jobs.Add((
            [pscustomobject]@{
                PowerShell = $PowerShell
                Runspace = $PowerShell.BeginInvoke()
            }
        ))
    })

    #region Window Close 
    $syncHash.Window.Add_Closed({
        Write-Verbose 'Halt runspace cleanup job processing'
        $jobCleanup.Flag = $False

        #Stop all runspaces
        $jobCleanup.PowerShell.Dispose()      
    })
    #endregion Window Close 
  
    $syncHash.Window.ShowDialog() | Out-Null
    $syncHash.Error = $Error
})



$psCmd.Runspace = $newRunspace
$data = $psCmd.BeginInvoke()

March 16, 2018 at 3:04 pm

Here's the code I use to get my scheduled task going. I've never tried running it from cmd to see what would happen, but I do know this works from task scheduler.

$taskSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
        $taskTrigger = New-ScheduledTaskTrigger -AtLogOn
        $taskPrincipal = New-ScheduledTaskPrincipal -GroupId "BUILTIN\Users"
        $TaskArgs = @'
        -ExecutionPolicy Bypass -WindowStyle Hidden -command "& 'C:\Staging\CC2_User_Configs.ps1'"
'@
        $taskAction = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $TaskArgs
        $task = New-ScheduledTask -Action $taskAction -Principal $taskPrincipal -Settings $taskSettings -Trigger $taskTrigger -Description Test
        Register-ScheduledTask -InputObject $task -TaskName CC2_User_Configs

March 16, 2018 at 4:57 pm

Hi Will, Thanks for that

Using your code created the task but for some reason the arguments did not populate in the action tab so I added them manually. Unfortunately all that happened was that Powershell briefly opened then closed and did not run the script. But if I take the arguments out and login powershell opens and if I run the script from there it works.

March 16, 2018 at 6:09 pm

I had to add this to the end of my script. It may not be the only way to accomplish this, but what is happening is your main runspace for the script is finished, so it closes, even though your secondary runspaces have not finished.

$psCmd.Runspace = $newRunspace
$data = $psCmd.BeginInvoke()
$loop = $true
Start-Sleep 20
while($loop)
{
    start-sleep 2
    if($syncHash.Window.isVisible -eq $false)
    {
        $loop=$false
    }
}

March 19, 2018 at 10:00 am

Thanks Will that worked a treat 🙂