How can I check if the PowerShell profile script is running from an SSH session? - powershell

I'm trying to work around a bug in Win32-OpenSSH, where -NoProfile -NoLogo is not respected when using pwsh.exe (Core) and logging in remotely via SSH/SCP. One way (of several) I tried, was to add the following in the very beginning of my Microsoft.PowerShell_profile.ps1 profile.
function IsInteractive {
$non_interactive = '-command', '-c', '-encodedcommand', '-e', '-ec', '-file', '-f'
-not ([Environment]::GetCommandLineArgs() | Where-Object -FilterScript {$PSItem -in $non_interactive})
}
# No point of running this script if not interactive
if (-not (IsInteractive)) {
exit
}
...
However, this didn't work with a remote SSH, because when using [Environment]::GetCommandLineArgs() with pwsh.exe, all you get back is:
C:\Program Files\PowerShell\6\pwsh.dll
regardless whether or not you are in an interactive session.
Another way I tried, was to scan through the process tree and look for the sshd parent, but that was also inconclusive, since it may run in another thread where sshd is not found as a parent.
So then I tried looking for other things. For example conhost. But on one machine conhost starts before pwsh, whereas on another machine, it starts after...then you need to scan up the tree and maybe find an explorer instance, in which case it is just a positive that the previous process is interactive, but not a definite non-interactive current process session.
function showit() {
$isInter = 'conhost','explorer','wininit','Idle',
$noInter = 'sshd','pwsh','powershell'
$CPID = ((Get-Process -Id $PID).Id)
for (;;) {
$PNAME = ((Get-Process -Id $CPID).Name)
Write-Host ("Process: {0,6} {1} " -f $CPID, $PNAME) -fore Red -NoNewline
$CPID = try { ((gwmi win32_process -Filter "processid='$CPID'").ParentProcessId) } catch { ((Get-Process -Id $CPID).Parent.Id) }
if ($PNAME -eq "conhost") {
Write-Host ": interactive" -fore Cyan
break;
}
if ( ($PNAME -eq "explorer") -or ($PNAME -eq "init") -or ($PNAME -eq "sshd") ) {
# Write-Host ": non-interactive" -fore Cyan
break;
}
""
}
}
How can I check if the profile script is running from within a remote SSH session?
Why am I doing this? Because I want to disable the script from running automatically through SSH/SCP/SFTP, while still being able to run it manually (still over SSH.) In Bash this is a trivial one-liner.
Some related (but unhelpful) answers:
Powershell test for noninteractive mode
How to check if a Powershell script is running remotely

Related

Error in PowerShell one liner for browser process management

I'm trying to run a code in PowerShell in one line. This code is a loop that's used for surveillance. If Microsoft Edge is opened the process has to close Chrome.
My code it works well if Edge is not opened, it goes right by the if condition, but if Edge is opened it returns me an error in the else condition.
System is Windows 10 with PowerShell ISE.
$a = 1 ;DO { 'Starting Loop' ; $vischk = get-process | where-object {$_.mainwindowhandle -ne 0 -and $_.MainWindowTitle -eq 'Start - Microsoft Edge'} | select-object name, mainwindowtitle ; if (!($vischk)) {Write-Warning 'Microsoft Edge is off'}else{Write-Warning 'Closing Chrome' Stop-Process -name chrome} ; Write-Warning 'Active surveillance' ; Start-Sleep -s 15} While ($a -le 2)
I need to run the surveillance window and when Microsoft Edge is opened, close another browser like chrome or another process name.
This is also not a One-Liner, it's a long script all put on one line. ;-},
hence your use of the semicolon. Semicolon means what is before it and after it
are independent code blocks.
I get that use case in an interactive consolehost thing, but in a script, well,
that's an entirely different thing.
Yet, doing this. the way you have it is a choice. Dev in the ISE, save the file,
run from the console directly or shell out to it from the ISE.
Also the way you are checking for the MS Edge instance is not correct. The
MainWindowTitle is only 'Microsoft Edge'
Get-Process -Name MicrosoftEdge | select *
<#
Name : MicrosoftEdge
Id : 6388
PriorityClass : Normal
...
ProductVersion : 11.00.17763.529
Description : Microsoft Edge
Product : Microsoft Edge
__NounName : Process
...
SafeHandle : Microsoft.Win32.SafeHandles.SafeProcessHandle
MachineName : .
MainWindowHandle : 132100
MainWindowTitle : Microsoft Edge
MainModule : System.Diagnostics.ProcessModule (MicrosoftEdge.exe)
MaxWorkingSet : 1413120
MinWorkingSet : 204800
Modules : {System....
#>
$Code = #'
$a = 1
DO {
'Starting Loop'
$vischk = get-process |
where-object {
$_.mainwindowhandle -ne 0 -and
$_.MainWindowTitle -eq 'Microsoft Edge'
} | select-object name, mainwindowtitle
if (!($vischk))
{Write-Warning 'Microsoft Edge is off'}
else{
Write-Warning 'Closing Chrome'
Stop-Process -name chrome
}
Write-Warning 'Active surveillance'
Start-Sleep -s 3}
While ($a -le 2)
'#
You can stay in the ISE to test code, but as Olaf points out, your user may not be the same. So, you need to validate both environments. You can stay in the ISE and test your code there as well as in the consolehost.
So, to test code using a consolehost instance from the ISE/VSCode without typing in the console.
Start-Process powershell -ArgumentList "-NoExit","-Command &{ $Code }" -Wait
Or
Start-Process pwsh -ArgumentList "-NoExit","-Command &{ $Code }" -Wait
Or just open the consolehost and run the script
The above works, as designed based on your defined case.
I agree with Olaf here as well, you need to add more error checking for what is and is not running for this to be more operationally sound. Don't run code you don't need to run if a target does not exist.

redirect stdout, stderr from powershell script as admin through start-process

Inside a powershell script, I'm running a command which starts a new powershell as admin (if I'm not and if needed, depending on $arg) and then runs the script.
I'm trying to redirect stdout and stderr to the first terminal.
Not trying to make things easier, there are arguments too.
param([string]$arg="help")
if($arg -eq "start" -Or $arg -eq "stop")
{
if(![bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544"))
{
Start-Process powershell -Verb runas -ArgumentList " -file servicemssql.ps1 $arg"
exit
}
}
$Services = "MSSQLSERVER", "SQLSERVERAGENT", "MSSQLServerOLAPService", "SSASTELEMETRY", "SQLBrowser", `
"SQLTELEMETRY", "MSSQLLaunchpad", "SQLWriter", "MSSQLFDLauncher"
function startsql {
"starting SQL services"
Foreach ($s in $Services) {
"starting $s"
Start-Service -Name "$s"
}
}
function stopsql {
"stopping SQL services"
Foreach ($s in $Services) {
"stopping $s"
Stop-Service -Force -Name "$s"
}
}
function statussql {
"getting SQL services status"
Foreach ($s in $Services) {
Get-Service -Name "$s"
}
}
function help {
"usage: StartMssql [status|start|stop]"
}
Switch ($arg) {
"start" { startsql }
"stop" { stopsql }
"status" { statussql }
"help" { help }
"h" { help }
}
Using the following answers on SO doesn't work:
Capturing standard out and error with Start-Process
Powershell: Capturing standard out and error with Process object
How to deal with the double quote inside double quote while preserving the variable ($arg) expansion ?
PowerShell's Start-Process cmdlet:
does have -RedirectStandardOut and -RedirectStandardError parameters,
but syntactically they cannot be combined with -Verb Runas, the argument required to start a process elevated (with administrative privileges).
This constraint is also reflected in the underlying .NET API, where setting the .UseShellExecute property on a System.Diagnostics.ProcessStartInfo instance to true - the prerequisite for being able to use .Verb = "RunAs" in order to run elevated - means that you cannot use the .RedirectStandardOutput and .RedirectStandardError properties.
Overall, this suggests that you cannot directly capture an elevated process' output streams from a non-elevated process.
A pure PowerShell workaround is not trivial:
param([string] $arg='help')
if ($arg -in 'start', 'stop') {
if (-not (([System.Security.Principal.WindowsPrincipal] [System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('Administrators'))) {
# Invoke the script via -Command rather than -File, so that
# a redirection can be specified.
$passThruArgs = '-command', '&', 'servicemssql.ps1', $arg, '*>', "`"$PSScriptRoot\out.txt`""
Start-Process powershell -Wait -Verb RunAs -ArgumentList $passThruArgs
# Retrieve the captured output streams here:
Get-Content "$PSScriptRoot\out.txt"
exit
}
}
# ...
Instead of -File, -Command is used to invoke the script, because that allows appending a redirection to the command: *> redirects all output streams.
#soleil suggests using Tee-Object as an alternative so that the output produced by the elevated process is not only captured, but also printed to the (invariably new window's) console as it is being produced:
..., $arg, '|', 'Tee-Object', '-FilePath', "`"$PSScriptRoot\out.txt`""
Caveat: While it doesn't make a difference in this simple case, it's important to know that arguments are parsed differently between -File and -Command modes; in a nutshell, with -File, the arguments following the script name are treated as literals, whereas the arguments following -Command form a command that is evaluated according to normal PowerShell rules in the target session, which has implications for escaping, for instance; notably, values with embedded spaces must be surrounded with quotes as part of the value.
The $PSScriptRoot\ path component in output-capture file $PSScriptRoot\out.txt ensures that the file is created in the same folder as the calling script (elevated processes default to $env:SystemRoot\System32 as the working dir.)
Similarly, this means that script file servicemssql.ps1, if it is invoked without a path component, must be in one of the directories listed in $env:PATH in order for the elevated PowerShell instance to find it; otherwise, a full path is also required, such as $PSScriptRoot\servicemssql.ps1.
-Wait ensures that control doesn't return until the elevated process has exited, at which point file $PSScriptRoot\out.txt can be examined.
As for the follow-up question:
To go even further, could we have a way to have the admin shell running non visible, and read the file as we go with the Unix equivalent of tail -f from the non -privileged shell ?
It is possible to run the elevated process itself invisibly, but note that you'll still get the UAC confirmation prompt. (If you were to turn UAC off (not recommended), you could use Start-Process -NoNewWindow to run the process in the same window.)
To also monitor output as it is being produced, tail -f-style, a PowerShell-only solution is both nontrivial and not the most efficient; to wit:
param([string]$arg='help')
if ($arg -in 'start', 'stop') {
if (-not (([System.Security.Principal.WindowsPrincipal] [System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('Administrators'))) {
# Delete any old capture file.
$captureFile = "$PSScriptRoot\out.txt"
Remove-Item -ErrorAction Ignore $captureFile
# Start the elevated process *hidden and asynchronously*, passing
# a [System.Diagnostics.Process] instance representing the new process out, which can be used
# to monitor the process
$passThruArgs = '-noprofile', '-command', '&', "servicemssql.ps1", $arg, '*>', $captureFile
$ps = Start-Process powershell -WindowStyle Hidden -PassThru -Verb RunAs -ArgumentList $passThruArgs
# Wait for the capture file to appear, so we can start
# "tailing" it.
While (-not $ps.HasExited -and -not (Test-Path -LiteralPath $captureFile)) {
Start-Sleep -Milliseconds 100
}
# Start an aux. background that removes the capture file when the elevated
# process exits. This will make Get-Content -Wait below stop waiting.
$jb = Start-Job {
# Wait for the process to exit.
# Note: $using:ps cannot be used directly, because, due to
# serialization/deserialization, it is not a live object.
$ps = (Get-Process -Id $using:ps.Id)
while (-not $ps.HasExited) { Start-Sleep -Milliseconds 100 }
# Get-Content -Wait only checks once every second, so we must make
# sure that it has seen the latest content before we delete the file.
Start-Sleep -Milliseconds 1100
# Delete the file, which will make Get-Content -Wait exit (with an error).
Remove-Item -LiteralPath $using:captureFile
}
# Output the content of $captureFile and wait for new content to appear
# (-Wait), similar to tail -f.
# `-OutVariable capturedLines` collects all output in
# variable $capturedLines for later inspection.
Get-Content -ErrorAction SilentlyContinue -Wait -OutVariable capturedLines -LiteralPath $captureFile
Remove-Job -Force $jb # Remove the aux. job
Write-Verbose -Verbose "$($capturedLines.Count) line(s) captured."
exit
}
}
# ...

IF Statement to Verify VLAN Exists in PowerCLI Script

I am writing a PowerCLI script to automate the creation of VMs based on the data within a CSV file and I would like to know how to format an IF statement to check if the VLANs specified already exist to avoid cluttering up the screen with errors.
The section of the script dealing with the VLAN creation in its current format:
New-VM -Name $_.Name -VMHost ($esx | Get-Random) -NumCPU $_.NumCPU -Location $Folder
$list = Get-Cluster $_.Cluster | Get-VMHost
foreach ($esxhost in $list)
{ Get-VirtualSwitch -Name $switch -VMHost $esxhost |
New-VirtualPortgroup -Name "VLAN $($_.VLAN)" -VLANID $($_.VLAN)
}
Write-Host "Wait - propagating VLAN $($_.VLAN) to all hosts" -foreground yellow
Start-Sleep 10
I would like to determine a way to have the script do something like:
IF $_.VLAN exists
Write-host "$_.VLAN already present, proceeding to next step"
ELSE DO{ Get-VirtualSwitch -Name $switch -VMHost $esxhost |
New-VirtualPortgroup -Name "VLAN $($_.VLAN)" -VLANID $($_.VLAN)
}
I don't have much experience in writing these so I was hoping for some assistance on how to
Check whether the VLAN already exists in vSphere on the switch
How to format the IF/ELSE statement properly to avoid cluttering up the PowerCLI window with errors when the script is run
Thank you for any assistance you may provide
EDIT to work for vlan rather than vswitch
You could use get-virtualportgroup for this and check if the names returned contain your vlanid. This won't work for distributed switches as that's a different set of cmdlets.
$host = 'YourHost'
$vlanid = 'YourVlanId'
if ((Get-VirtualPortGroup -host $host).VLanId -contains $vlanid )
{
Write-Output 'vlan present'
}
else
{
Write-Output 'vlan missing'
#your code to create vlan here
}

How to run interactive commands in another application window from powershell

I have another command line program which I invoke from my powershell script and would like to run some interactive commands in that window once it is opened from power shell.
In other words - I do a Invoke-Item $link_to_app which opens up the interactive command line for that application and now I would like to use the application specific commands from within powershell scripts.
e.g. app.exe -help to invoke the help command of the app.exe.
Any pointers would help. Thanks!
Try this:
$app = 'app.exe -help'
Invoke-Expression $app
Tested with this and it worked as expected:
$pingTest = 'ping -n 8 127.0.0.1'
Invoke-Expression $pingTest
From your expanded explanation you appear to want to run 2 commands within the same command prompt. This is possible, however, I'm not sure it will work in your scenario. For example:
test1.bat:
echo "hello!"
test2.bat: echo "goodbye!"
$batchTest = "test1.bat && test2.bat"
cmd /c $batchTest
output:
D:\Test>echo "hello!"
"hello!"
D:\Test>echo "goodbye!"
"goodbye!"
Hope this helps.
I'm not sure, but I think what you want is the ability to have a script send input to and receive output from another program, where the other program has "state" that your script needs to be able to interact with. Below is an example of a script that drives CMD.EXE. CMD has state, such as current working directory and environment variables.
Note, that you could do what the other answerer suggested and just start the program, give all the input on the command line, and then do what you need to with the output. However for CMD if you need to make decisions based on the output, and then give CMD more input based on the previous output, you'd have to save and restore the environment and current working directories between each time you executed CMD. The approach below doesn't require that.
However the approach below does have several caveats. First it is dependent on the PS "host". It works (for me) on the command line PS, but not in ISE. This dependency is due to using the Raw host interface to determine if a key is available. Second it is timing dependent, based on the behavior of CMD (or whatever you use instead). You'll see a few sleep commands in the script. I had to experiment a whole lot to get this script to show CMD's output for a particular sub-command when that command was entered, versus CMD giving the output of previous commands after another command was entered. Comment out the sleeps to see what I mean. Third it is easy to hang Powershell. Killing CMD in task manager gets you out of the hung state, which I had to do many times.
You'll see that I added a couple of commands that the script deals with specially. This is to demonstrate that input to command can come from a PS script (versus input from the keyboard).
$global:ver++
if ($ExecutionContext.Host.name -match "ISE Host$") {
write-warning "This script relies on RawUI functionality not implemented in ISE"
return
}
$in = $null
$flExiting = $false
$doDebug = $false
function dot-debug {param($color)
if ($doDebug) {
write-host "." -NoNewline -ForegroundColor $color
}
}
#function dot-debug {param($color) }
$procInfo = new diagnostics.processstartinfo
$procInfo.RedirectStandardOutput=1
$procInfo.RedirectStandardInput=1
$procInfo.RedirectStandardError=1
$procInfo.FileName="cmd.exe"
$procInfo.UseShellExecute=0
$p=[diagnostics.process]::start($procInfo)
$outBuf = new char[] 4096
write-host "Version $ver"
sleep -Milliseconds 300
do {
dot-debug red
# This while loop determines whether input is available from either
# CMD's standard output or from the user typing. You don't want to
# get stuck waiting for input from either one if it doesn't really have input.
:WaitIO while ($true) {
if (-1 -ne $p.StandardOutput.peek()) {
dot-debug yellow
$cnt = $p.StandardOutput.read( $outBuf, 0, 4096)
} else {
dot-debug Gray
if ($host.ui.rawui.KeyAvailable -or $flExiting) {break}
}
$str = $outBuf[0..($cnt-1)] -join ""
write-host "$str" -NoNewline
while (-1 -eq ($rc =$p.StandardOutput.peek())) {
if ($host.ui.rawui.KeyAvailable -or $flExiting) {
break WaitIO
}
dot-debug DarkGray
sleep -milli 200
}
dot-debug cyan
}
dot-debug green
# read-host echoes input, so commands get echoed twice (cmd also echoes)
#
# $host.ui.rawui.ReadKey("NoEcho, IncludeKeyDown") doesn't work on ISE,
# but does work in the PS cli shell
if ($in -ne "exit") {$in = read-host}
if ($in -eq "td") { # toggle debug
$doDebug = -not $doDebug
$p.StandardInput.WriteLine( "echo debug toggled")
sleep -milli 300
continue
}
if ($in -eq "xxx") {
# Example of script driven output being sent to CMD
$p.StandardInput.WriteLine( "echo This is a very long command that I do not want to have to type in everytime I want to use it")
# You have to give CMD enough time to process command before you read stdout,
# otherwise stdout gets "stuck" until the next time you write to stdin
sleep -milli 1
continue
}
if ($in -eq "exit") {
$flExiting = $true
$p.StandardInput.WriteLine($in)
continue
}
foreach ($char in [char[]]$in) {
$p.StandardInput.Write($char)
}
$p.StandardInput.Write("`n")
sleep -milli 1
} until ($p.StandardOutput.EndOfStream)

Powershell test for noninteractive mode

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!
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 Assert-IsNonInteractiveShell {
# Test each Arg for match of abbreviated '-NonInteractive' command.
$NonInteractive = [Environment]::GetCommandLineArgs() | Where-Object{ $_ -like '-NonI*' }
if ([Environment]::UserInteractive -and -not $NonInteractive) {
# We are in an interactive shell.
return $false
}
return $true
}
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"
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
}
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.
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
This will return a Boolean when the -Noninteractive switch is used to launch the PowerShell prompt.
[Environment]::GetCommandLineArgs().Contains('-NonInteractive')
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
}
C:\> powershell -NoProfile -NoLogo -NonInteractive -Command "[Environment]::GetCommandLineArgs()"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
-NoProfile
-NoLogo
-NonInteractive
-Command
[Environment]::GetCommandLineArgs()
Implement two scripts, one core.ps1 to be manually launched, and one scheduled.ps1 that launches core.ps1 with a parameter.
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.
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