Powershell test for noninteractive mode

…衆ロ難τιáo~ 提交于 2021-02-06 14:54:07

问题


I have a script that may be run manually or may be run by a scheduled task. I need to programmatically determine if I'm running in -noninteractive mode (which is set when run via scheduled task) or normal mode. I've googled around and the best I can find is to add a command line parameter, but I don't have any feasible way of doing that with the scheduled tasks nor can I reasonably expect the users to add the parameter when they run it manually. Does noninteractive mode set some kind of variable or something I could check for in my script?

Edit: I actually inadvertently answered my own question but I'm leaving it here for posterity.

I stuck a read-host in the script to ask the user for something and when it ran in noninteractive mode, boom, terminating error. Stuck it in a try/catch block and do stuff based on what mode I'm in.

Not the prettiest code structure, but it works. If anyone else has a better way please add it!


回答1:


You can check how powershell was called using Get-WmiObject for WMI objects:

(gwmi win32_process | ? { $_.processname -eq "powershell.exe" }) | select commandline

#commandline
#-----------
#"C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe" -noprofile -NonInteractive

UPDATE: 2020-10-08

Starting in PowerShell 3.0, this cmdlet has been superseded by Get-CimInstance

(Get-CimInstance win32_process -Filter "ProcessID=$PID" | ? { $_.processname -eq "pwsh.exe" }) | select commandline

#commandline
#-----------
#"C:\Program Files\PowerShell\6\pwsh.exe"



回答2:


I didn't like any of the other answers as a complete solution. [Environment]::UserInteractive reports whether the user is interactive, not specifically if the process is interactive. The api is useful for detecting if you are running inside a service. Here's my solution to handle both cases:

function Test-IsNonInteractiveShell {
    if ([Environment]::UserInteractive) {
        foreach ($arg in [Environment]::GetCommandLineArgs()) {
            # Test each Arg for match of abbreviated '-NonInteractive' command.
            if ($arg -like '-NonI*') {
                return $true
            }
        }
    }

    return $false
}

Note: This code is needlessly verbose and takes 3.5 ms to run (on average), but works well to illustrate functionality. I have a more terse solution that runs in 2.7 ms (on average) on my GitHub. Execution time evaluated with Measure-Command; performance will vary depending on system performance.




回答3:


I wanted to put an updated answer here because it seems that [Environment]::UserInteractive doesn't behave the same between a .NET Core (container running microsoft/nanoserver) and .NET Full (container running microsoft/windowsservercore).

While [Environment]::UserInteractive will return True or False in 'regular' Windows, it will return $null in 'nanoserver'.

If you want a way to check interactive mode regardless of the value, add this check to your script:

($null -eq [Environment]::UserInteractive -or [Environment]::UserInteractive)

EDIT: To answer the comment of why not just check the truthiness, consider the following truth table that assumes such:

left  | right  | result 
=======================
$null | $true  | $false
$null | $false | $true (!) <--- not what you intended



回答4:


I came up with a posh port of existing and proven C# code that uses a fair bit of P/Invoke to determine all the corner cases. This code is used in my PowerShell Build Script that coordinates several build tasks around Visual Studio projects.

# Some code can be better expressed in C#...
#
Add-Type @'
using System;
using System.Runtime.InteropServices;

public class Utils
{
    [DllImport("kernel32.dll")]
    private static extern uint GetFileType(IntPtr hFile);

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetStdHandle(int nStdHandle);

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetConsoleWindow();

    [DllImport("user32.dll")]
    private static extern bool IsWindowVisible(IntPtr hWnd);

    public static bool IsInteractiveAndVisible
    {
        get
        {
            return Environment.UserInteractive &&
                GetConsoleWindow() != IntPtr.Zero &&
                IsWindowVisible(GetConsoleWindow()) &&
                GetFileType(GetStdHandle(-10)) == 2 &&   // STD_INPUT_HANDLE is FILE_TYPE_CHAR
                GetFileType(GetStdHandle(-11)) == 2 &&   // STD_OUTPUT_HANDLE
                GetFileType(GetStdHandle(-12)) == 2;     // STD_ERROR_HANDLE
        }
    }
}
'@

# Use the interactivity check somewhere:
if (![Utils]::IsInteractiveAndVisible)
{
    return
}



回答5:


Testing for interactivity should probably take both the process and the user into account. Looking for the -NonInteractive (minimally -noni) powershell switch to determine process interactivity (very similar to @VertigoRay's script) can be done using a simple filter with a lightweight -like condition:

function Test-Interactive
{
    <#
    .Synopsis
        Determines whether both the user and process are interactive.
    #>

    [CmdletBinding()] Param()
    [Environment]::UserInteractive -and
        !([Environment]::GetCommandLineArgs() |? {$_ -ilike '-NonI*'})
}

This avoids the overhead of WMI, process exploration, imperative clutter, double negative naming, and even a full regex.




回答6:


This will return a Boolean when the -Noninteractive switch is used to launch the PowerShell prompt.

[Environment]::GetCommandLineArgs().Contains('-NonInteractive')



回答7:


I think the question needs a more thorough evaluation.

  • "interactive" means the shell is running as REPL - a continuous read-execute-print loop.

  • "non-interactive" means the shell is executing a script, command, or script block and terminates after execution.

If PowerShell is run with any of the options -Command, -EncodedCommand, or -File, it is non-interactive. Unfortunately, you can also run a script without options (pwsh script.ps1), so there is no bullet-proof way of detecting whether the shell is interactive.

So are we out of luck then? No, fortunately PowerShell does automatically add options that we can test if PowerShell runs a script block or is run via ssh to execute commands (ssh user@host command).

function IsInteractive {
    # not including `-NonInteractive` since it apparently does nothing
    # "Does not present an interactive prompt to the user" - no, it does present!
    $non_interactive = '-command', '-c', '-encodedcommand', '-e', '-ec', '-file', '-f'

    # alternatively `$non_interactive [-contains|-eq] $PSItem`
    -not ([Environment]::GetCommandLineArgs() | Where-Object -FilterScript {$PSItem -in $non_interactive})
}

Now test in your PowerShell profile whether this is in interactive mode, so the profile is not run when you execute a script, command or script block (you still have to remember to run pwsh -f script.ps1 - not pwsh script.ps1)

if (-not (IsInteractive)) {
    exit
}



回答8:


C:\> powershell -NoProfile -NoLogo -NonInteractive -Command "[Environment]::GetCommandLineArgs()"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
-NoProfile
-NoLogo
-NonInteractive
-Command
[Environment]::GetCommandLineArgs()



回答9:


Implement two scripts, one core.ps1 to be manually launched, and one scheduled.ps1 that launches core.ps1 with a parameter.




回答10:


powerShell -NonInteractive { Get-WmiObject Win32_Process -Filter "Name like '%powershell%'" | select-Object CommandLine }

powershell -Command { Get-WmiObject Win32_Process -Filter "Name like '%powershell%'" | select-Object CommandLine }

In the first case, you'll get the "-NonInteractive" param. In the latter you won't.




回答11:


Script: IsNonInteractive.ps1

function Test-IsNonInteractive()
{
    #ref: http://www.powershellmagazine.com/2013/05/13/pstip-detecting-if-the-console-is-in-interactive-mode/
    #powershell -NoProfile -NoLogo -NonInteractive -File .\IsNonInteractive.ps1
    return [bool]([Environment]::GetCommandLineArgs() -Contains '-NonInteractive')
}

Test-IsNonInteractive

Example Usage (from command prompt)

pushd c:\My\Powershell\Scripts\Directory
::run in non-interactive mode
powershell -NoProfile -NoLogo -NonInteractive -File .\IsNonInteractive.ps1
::run in interactive mode
powershell -File .\IsNonInteractive.ps1
popd

More Involved Example Powershell Script

#script options
$promptForCredentialsInInteractive = $true

#script starts here

function Test-IsNonInteractive()
{
    #ref: http://www.powershellmagazine.com/2013/05/13/pstip-detecting-if-the-console-is-in-interactive-mode/
    #powershell -NoProfile -NoLogo -NonInteractive -File .\IsNonInteractive.ps1
    return [bool]([Environment]::GetCommandLineArgs() -Contains '-NonInteractive')
}

function Get-CurrentUserCredentials()
{
    return [System.Net.CredentialCache]::DefaultCredentials
}
function Get-CurrentUserName()
{
    return ("{0}\{1}" -f $env:USERDOMAIN,$env:USERNAME)
}

$cred = $null
$user = Get-CurrentUserName

if (Test-IsNonInteractive) 
{
    $msg = 'non interactive'
    $cred = Get-CurrentUserCredentials
} 
else 
{
    $msg = 'interactive'
    if ($promptForCredentialsInInteractive) 
    {
        $cred = (get-credential -UserName $user -Message "Please enter the credentials you wish this script to use when accessing network resources")
        $user = $cred.UserName
    } 
    else 
    {
        $cred = Get-CurrentUserCredentials
    }
}

$msg = ("Running as user '{0}' in '{1}' mode" -f $user,$msg)
write-output $msg


来源:https://stackoverflow.com/questions/9738535/powershell-test-for-noninteractive-mode

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!