Search code examples
powershellcmdbatch-processing

PowerShell function not running as expected


I have a curious case that I cannot fathom the reason for...

Please know I am a novice to PowerShell.

I am working on a PowerShell menu system to help automate building out new computers in my environment. I have a PS1 file that holds the script for an app install. When I use the script to reference this I am able to run it and have no issue. However, when I try inserting this into a function and referencing it does not.

This works:

4       #   Microsoft Office 32-bit
            {
                Write-Host "`nMicrosoft Office 32-bit..." -ForegroundColor Yellow

                # {installMS32Bit}
                Invoke-Expression "cmd /c start powershell -NoExit -File '\\**SERVERPATH**\menuItems\ms_office\32-bit\install.ps1'"

                Start-Sleep -seconds 2
            }

This does not:

function installMS32Bit(){

Invoke-Expression "cmd /c start powershell -NoExit -File '\\**SERVERPATH**\menuItems\ms_office\32-bit\install.ps1'"
}

}

4       #   Microsoft Office 32-bit
            {
                Write-Host "`nMicrosoft Office 32-bit..." -ForegroundColor Yellow

                {installMS32Bit}

                Start-Sleep -seconds 2}

install.ps1 file:

    # Copy MS Office uninstall and setup to local then run and install 32-bit Office
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\setup.exe' -Destination 'C:\temp\' -Force
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\uninstall.xml' -Destination 'C:\temp\' -Force
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\32-bit\Setup.exe' -Destination 'C:\temp' -Force

Invoke-Expression ("cmd /c 'C:\temp\setup.exe' /configure 'C:\temp\uninstall.xml'")

Start-Process -FilePath 'C:\temp\Setup.exe'

Secondary question and a little explanation for Invoke-Expression...

I like to see progress and like to have secondary windows open to monitor the new process being run. I was unable to find a solution with a persistent window that worked for me to do this without Invoke-Expression.

If there is a better way to do this in PowerShell I am all ears!


Solution

  • {installMS32Bit}

    As Mathias points out in a comment on the question, this statement doesn't call your function, it wraps it in a script block ({ ... })[1], which is a piece of reusable code (like a function pointer, loosely speaking), for later execution via &, the call (execute) operator.

    To call your function, just use its name (by itself here, given that there are no arguments to pass): installMS32Bit

    Invoke-Expression should generally be avoided; definitely don't use it to invoke an external program, as in your attempts.

    Additionally, there's generally no need to call an external program via cmd.exe (cmd /c ...), just invoke it directly.

    For instance, replace the last Invoke-Epression call from your question with:

    # If the EXE path weren't quoted, you wouldn't need the &
    & 'C:\temp\setup.exe' /configure 'C:\temp\uninstall.xml'
    

    I like to see progress and like to have secondary windows open to monitor the new process being run. I was unable to find a solution with a persistent window that worked for me to do this without Invoke-Expression.

    (On Windows), Start-Process by default executes a console application in a new window (unless you specify -NoNewWindow), asynchronously (unless you specify -Wait).

    You cannot pass a .ps1 script directly to Start-Process (it will be treated like a document to open rather than an executable to call), but you can pass it to PowerShell's CLI via the -File parameter:

    Start-Process powershell.exe '-File install.ps1'
    

    The above is short for:

    Start-Process -FilePath powershell.exe -ArgumentList '-File install.ps1'
    

    That is, PowerShell will execute the following in a new window:
    powershell.exe -File install.ps1


    [1] Since you're not assigning the script block being created to a variable, it is implicitly output (printed to the display, in the absence of a redirection); a script block stringifies by its literal contents, excluding the enclosing { and }, so string installMS32Bit will print to the display.