Need to run PS script twice before it works

I created a PS form with a MenuStrip and some abilities. In VS Code the first time I run it the MenuStrip works but nothing else. It’ll act the same way in a PS terminal. Only the MenuStrip works. When I run it a second time in VS Code everything works as it should, MenuStrip and the different set of abilities run as intended.

If I run that same form/app (whatever it’s called) from a PS terminal, it will run and the MenuStrip operations are doing what they are supposed to, just as when run from VS Code, but the actual tasks won’t work at all, no matter how often you start the form through the PS terminal. I’m wondering if the script has to load the assemblies into the session before those operations will work. So running in VS Code will work the second time because it’s loaded the assemblies from the first run, but each time you run it from a PS terminal it’s a different session so it can’t reference the assemblies? Similar to how .NET has to “prime” itself when starting up the first time. I don’t know if that’s accurate or not, which is why I’m here.

This is my first PS form, and I’m very sure there are many things with the code that could be better, but here’s a stripped down version and directions:

  1. From Within VS Code, click to run the script.
  2. Click on Home > Gather Information
  3. Click on Function > Last Boot Time
  4. Enter the name of the remote system you want to get the last boot time from
  5. click the button

Notice the first time you run the script from within VS Code it won’t work. Menu items do what they need to do, but it won’t fetch the last boot time.

When you run it a second time from within VS Code (following the same steps above) it will fetch the last boot time of the remote system and output the results.

If you run the ps1 from a PS terminal, the menu functions work, but it doesn’t matter how many times you run it the operation to get the last boot time doesn’t run.

What my end goal here is to package it up as an executable and have the whole thing run on the first load. Does anyone have a nugget of knowledge to drop on how to make this little guy work as intended?

# Load external assemblies
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()

#Get the screen resolution dimentions for dynamic main form sizing
$res = Get-CimInstance CIM_VideoController | Select CurrentHorizontalResolution, CurrentVerticalResolution

# ****************************************************
# **************Begin Menu Creation*******************
# ****************************************************

#Instantiate the menu strip object
$MS_Main = new-object System.Windows.Forms.MenuStrip

    #Create Home menu items
    $HomeMenu = new-object System.Windows.Forms.ToolStripMenuItem
    $HomeGatherMenu = new-object System.Windows.Forms.ToolStripMenuItem

        # Home menu buildout - This adds all the items to the HomeMenu menu
        $HomeMenu.DropDownItems.AddRange(@($HomeGatherMenu))
        $HomeMenu.Name = "HomeMenu"
        $HomeMenu.Size = new-object System.Drawing.Size(35, 20)
        $HomeMenu.Text = "&Home"

    #Create Function menu items
    $FunctionMenu = new-object System.Windows.Forms.ToolStripMenuItem
    
        # ******* Gather Menu Items
        $FunctionGLastBootMenu = new-object System.Windows.Forms.ToolStripMenuItem

            # Function Menu buildout - This adds all the items to the FunctionMenu menu
            $FunctionMenu.DropDownItems.AddRange(@($FunctionGLastBootMenu))
            $FunctionMenu.Name = "FunctionMenu"
            $FunctionMenu.Size = new-object System.Drawing.Size(75, 20)
            $FunctionMenu.Text = "&Function"

# Menu Strip Main (MS_Main) setup
# This will add the Home, Function and Help/About menu items to the menu bar
$MS_Main.Items.AddRange(@($HomeMenu,$FunctionMenu))
$MS_Main.Location = new-object System.Drawing.Point(0, 0)
$MS_Main.Name = "MS_Main"
$MS_Main.Size = new-object System.Drawing.Size(354, 24)
$MS_Main.TabIndex = 0
$MS_Main.Text = "menuStrip1"

# ****************************************************
# ************** End Menu Creation *******************
# ****************************************************

# ****************************************************
# **** Begin Menu Item Creation and Implementation ***
# ****************************************************

# *********** Home Menu Item Buildout ***********
# HomeGatherMenu
$HomeGatherMenu.Name = "HomeGatherMenu"
$HomeGatherMenu.Size = new-object System.Drawing.Size(152, 22)
$HomeGatherMenu.Text = "&Gather Information"
$HomeGatherMenu.Add_Click( { OnClick_HomeGatherMenu $HomeGatherMenu $EventArgs} )
function OnClick_HomeGatherMenu($Sender,$e){    
    # ******** Show Gather Menu
    $FunctionGLastBootMenu.Visible = $true
}

# ******   FUNCTION MENU   ***********
# Populate the Function menu with the contextual functions 
$FunctionGLastBootMenu.Name = "FunctionGLastBootMenu"
$FunctionGLastBootMenu.Size = New-Object System.Drawing.Size(152, 22)
$FunctionGLastBootMenu.Text = "&Last Boot Time"
$FunctionGLastBootMenu.Visible = $false
$FunctionGLastBootMenu.Add_Click({ OnClick_FunctionGLastBootMenu $FunctionGLastBootMenu $EventArgs})


# ****************************************************
# **** End Menu Item Creation and Implementation *****
# ****************************************************

# Create the main form for the app
$YAYMainForm = new-object System.Windows.Forms.form
$YAYMainForm.ClientSize = new-object System.Drawing.Size(($res.CurrentHorizontalResolution / 2), ($res.CurrentVerticalResolution / 1.75))
$YAYMainForm.Controls.Add($MS_Main)
$YAYMainForm.MainMenuStrip = $MS_Main
$YAYMainForm.BackgroundImage = $Image
$YAYMainForm.BackgroundImageLayout = "None"
$YAYMainForm.FormBorderStyle = 1
$YAYMainForm.MaximizeBox = $false
$YAYMainForm.Name = "YAYMainForm"
$YAYMainForm.BackColor = "#eeeeee"

#*******Create BTNGet (CONSTANT)*******
$BTNGet = New-Object system.Windows.Forms.Button
#$BTNGet.text = "Fetch"
$BTNGet.height=25
$BTNGet.width=75
$BTNGet.AutoSize = $true
$BTNGet.top= 73
$BTNGet.left=135
$BTNGet.BackColor = "#E6F3FF"
$BTNGet.Visible = $false
$BTNGet.add_click($BTNGetClick)
$YAYMainForm.controls.add($BTNGet)
#*******End button 1*******

#*******Status Label*****
# Placement needs to stay static
$LBLStatus = New-Object System.Windows.Forms.Label
$LBLStatus.Width = 40
$LBLStatus.Height = 20
$LBLStatus.top = 165
$LBLStatus.left = 26
$LBLStatus.visible = $true
$LBLStatus.text = "Status: "
$LBLStatus.BackColor = "#777777"
$LBLStatus.ForeColor = "#ffffff"
#*******End Status label********

#*******Status Output Label*****
# Placement needs to stay static
$LBLStatusOut = New-Object System.Windows.Forms.Label
#$LBLStatusOut.AutoSize = $true
$LBLStatusOut.Width = $res.CurrentHorizontalResolution / 2.212
$LBLStatusOut.Height = 20
$LBLStatusOut.top = 165
$LBLStatusOut.left = 67
$LBLStatusOut.visible = $true
$LBLStatusOut.BackColor = "#f6fcf5"
#*******End Status Output label********

#*******Host Name Label (CONSTANT)*****
$LBLHostName = New-Object System.Windows.Forms.Label
$LBLHostName.AutoSize = $true
$LBLHostName.top = 55
$LBLHostName.left = 26
$LBLHostName.Text = "Host Name:"
$LBLHostName.visible = $false
#*******End Host Name Label********

#*******Host Name Textbox*****
$TBHostName = New-object System.Windows.Forms.TextBox
$TBHostName.top = 75
$TBHostName.Left = 26
$TBHostName.BorderStyle = 1
$TBHostName.Visible = $false
$YAYMainForm.Controls.add($TBHostName)
#*******End Textbox 1********

# This next line adds the various labels to the form.
$YAYMainForm.Controls.AddRange(@($LBLStatusOut,$LBLHostName, $LBLStatus))

#********** Button Clicks **********
$BTNGetClick = {
    $MethodSeed = $GLOBAL:BTNGetText
    BTNGetRun -MethodSeed $MethodSeed
}

#********** END Buttn Clicks *******
#***********************************

#********** Button functions ***********
function BTNSeed ($ValueSeed) {
    Switch ($ValueSeed){
        "GLB" {$GLOBAL:BTNGetText = "Get Last Boot Time"}
    } #End Switch

} #End Function

function BTNGetRun ($MethodSeed){

Switch ($MethodSeed){
    "Get Last Boot Time" {
        try {
            $LBLStatusOut.Text = "Figuring out when this guy was last rebooted. Stay tuned..."
            $GLBT = gcim -ComputerName $TBHostName.Text Win32_OperatingSystem
            $TXTOutPut.text = ($TBHostName.text+" was last booted: "+$GLBT.LastBootUpTime)
            $LBLStatusOut.Text = "Complete. Last boot information is below."
        }
        catch {
            $LBLStatusOut.text =  "An error occurred: see the message below."
            $TXTOutPut.text = $_
        }
    } #END Last Boot Try/catch
}
}
#********** END Button functions ***********
#*******************************************
#******************************************* 


#************* Menu functions ***************
function OnClick_FunctionGLastBootMenu{ 
    #Set the seed for the button text value
    $ValueSeed = "GLB"    

    #Call the function to get the BTNGET text
    BTNSeed -ValueSeed $ValueSeed
    
    #Set the BTNGet text value
    $BTNGet.Text = $GLOBAL:BTNGetText

    $BTNGet.Visible = $true
    $TBHostName.Visible = $true
    $LBLHostName.Visible = $true
}


#*********************************************
#***********END Menu Functions****************
#*********************************************

#*******Informational Output TextArea*****
$TXTOutPut = New-Object System.Windows.Forms.TextBox
$TXTOutPut.AutoSize = $true
$TXTOutPut.Height = ($res.CurrentVerticalResolution / 2.6)
$TXTOutPut.Width = ($res.CurrentHorizontalResolution / 2.11)
$TXTOutPut.multiline = $True
$TXTOutPut.scrollbars = "Vertical, Horizontal"
$TXTOutPut.Font="Lucida Console"
$TXTOutPut.wordwrap = $false
$TXTOutPut.top = 190
$TXTOutPut.left = 25
$YAYMainForm.Controls.Add($TXTOutPut)
#*******EndInformational Output TextArea********

function OnFormClosing_YAYMainForm($Sender,$e){ 
    # $this represent sender (object)
    # $_ represent  e (eventarg)

    # Allow closing
    ($_).Cancel= $False
}
$YAYMainForm.Add_FormClosing( { OnFormClosing_YAYMainForm $YAYMainForm $EventArgs} )
$YAYMainForm.Add_Shown({$YAYMainForm.Activate()})
$YAYMainForm.ShowDialog()

#Free ressources
$YAYMainForm.Dispose()

<$.02>

I took a quick look and a couple of suggestions:

  1. Move your functions to the beginning of the script. They need to be declared before they are called.
  2. I would also update your functions to call them with Parameters

</$.02>

1 Like

Sounds like @tonyd is onto something. On the first boot, your functions aren’t loaded until the entire script is executed, so they don’t exist before they’re called. Once you’ve run it once, the functions persist in the session, which explains why they work on the second run.

I’m confused about the part where you say “If I run that same form/app (whatever it’s called) from a PS terminal, it will run”, but then you say “If you run the ps1 from a PS terminal, the menu functions work, but it doesn’t matter how many times you run it the operation to get the last boot time doesn’t run.” What’s different about the way you’re kicking it off? (I haven’t built a form in a long time, so it may just be something I’m missing…)

Try moving all of your function definitions right after you add the assemblies and before any other logic.

And he’s right about parameterizing them. You can define things a lot cleaner and with a lot more options by using Param blocks instead of what you’re doing, and things like [CmdletBinding()] only work with the Param block. I think it’s mainly a “best practice” recommendation, in your case, but it’s a good habit to cultivate.

Ahhh, understood. OK. I’ll give that a try tomorrow. Gotta head out for the day. I’ll report back what I find.

Thank you both so much! This was driving me crazy.

Just a confirmation of said behavior

Hi, Everyone.

Just letting you know that this has been resolved. It was a combination of things.

  1. moving the functions to the top of the script, and
  2. moving the button click action to the top as well.

After moving the functions to the top it was still not working. This morning I move the button click action and now it’s working as it should.

Lesson learned. Thanks for the help!