Error in PowerShell one liner for browser process management - powershell

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.

Related

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

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

"echo on" in powershell or how do I make Powershell output the command lines of all the commands, INCLUDING the native ones invoked by the script?

My question may seem duplicate of PowerShell "echo on", but it is not.
I am not interested in capturing the command output, but in the command line itself of every command executed by the script, including the native commands.
This is what "echo on" in cmd does and this is what I am looking for. Set-PSDebug -Trace 1 does not do it and neither passing the -Verbose flag.
So far I have not see a way except outputing them myself, which is a huge pain in itself.
So, can Powershell do what "echo on" does in cmd?
EDIT 1
Not ideal, but I would accept an answer suggesting to use a wrapper function which would receive a command (native or powershell) with parameters and run the command while faithfully logging the respective command line. Of course, the wrapper function code should be part of the answer.
EDIT 2
The following trivial example demonstrates why Set-PSDebug -Trace 1 does not do it:
tasklist `
/fi "status eq running" | Select-Object -First 4
Please, observe:
C:\> cat C:\temp\1.ps1
tasklist `
/fi "status eq running" | Select-Object -First 4
C:\> Set-PSDebug -Trace 1
C:\> C:\temp\1.ps1
DEBUG: 1+ >>>> C:\temp\1.ps1
DEBUG: 1+ >>>> tasklist `
Image Name PID Session Name Session# Mem Usage
========================= ======== ================ =========== ============
csrss.exe 756 Console 1 2,816 K
C:\>
EDIT 3
For comparison, observe an equivalent script in cmd with echo on:
C:\>type c:\temp\1.cmd
#echo on
tasklist ^
/fi "status eq running" |findstr/n ^^|findstr "^[1-4]:"
C:\>c:\temp\1.cmd
C:\>tasklist /fi "status eq running" | findstr/n ^ | findstr "^[1-4]:"
1:
2:Image Name PID Session Name Session# Mem Usage
3:========================= ======== ================ =========== ============
4:csrss.exe 756 Console 1 2,328 K
C:\>
EDIT 4
start-transcript does not do it either:
C:\WINDOWS\system32> cat c:\temp\1.ps1
tasklist `
/fi "status eq running" | Select-Object -First 4 | Out-Default
C:\WINDOWS\system32> Start-Transcript
Transcript started, output file is ~\Documents\PowerShell_transcript.L-PF0TBKV7.Sr1ntThx.20190611143800.txt
C:\WINDOWS\system32> c:\temp\1.ps1
Image Name PID Session Name Session# Mem Usage
========================= ======== ================ =========== ============
csrss.exe 756 Console 1 2,936 K
C:\WINDOWS\system32> Stop-Transcript
Transcript stopped, output file is ~\Documents\PowerShell_transcript.L-PF0TBKV7.Sr1ntThx.20190611143800.txt
C:\WINDOWS\system32> cat ~\Documents\PowerShell_transcript.L-PF0TBKV7.Sr1ntThx.20190611143800.txt
**********************
Windows PowerShell transcript start
Start time: 20190611143800
Username: xyz\me
RunAs User: xyz\me
Configuration Name:
Machine: L-PF0TBKV7 (Microsoft Windows NT 10.0.16299.0)
Host Application: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
Process ID: 25508
PSVersion: 5.1.16299.1004
PSEdition: Desktop
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.16299.1004
BuildVersion: 10.0.16299.1004
CLRVersion: 4.0.30319.42000
WSManStackVersion: 3.0
PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1
**********************
Transcript started, output file is ~\Documents\PowerShell_transcript.L-PF0TBKV7.Sr1ntThx.20190611143800.txt
C:\WINDOWS\system32
>
PS>c:\temp\1.ps1
Image Name PID Session Name Session# Mem Usage
========================= ======== ================ =========== ============
csrss.exe 756 Console 1 2,936 K
C:\WINDOWS\system32
>
PS>Stop-Transcript
**********************
Windows PowerShell transcript end
End time: 20190611143810
**********************
C:\WINDOWS\system32>
As you can see it does not contain the command line.
Firstly, the reason you're dissatisfied with the built-in options is because you're going against the grain; your requirement is like asking how to put sacks of gravel in the back of a Porsche. Powershell comes with Verbose and Debug output streams and a fantastic debugger.
If you have any ability to influence coding standards, look at splatting as an alternative to backtick-continuations.
If you can count on versions of Windows that are not years past EoL, consider Get-ScheduledTask | Where-Object State -eq 'Ready' instead of tasklist.
That said, yes, what you want is possible. Here's a script that will echo across line continuations:
# Echo.ps1
function Disable-Echo
{
param
(
[Parameter(Mandatory)]
[string]$Path
)
$Path = ($Path | Resolve-Path -ErrorAction Stop).Path
Get-PSBreakpoint -Script $Path | Remove-PSBreakpoint
}
function Enable-Echo
{
param
(
[Parameter(Mandatory)]
[string]$Path
)
$Path = ($Path | Resolve-Path -ErrorAction Stop).Path
Disable-Echo $Path
$Ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$null, [ref]$null)
$Statements = $Ast.BeginBlock, $Ast.ProcessBlock, $Ast.EndBlock |
Select-Object -ExpandProperty Statements |
Write-Output |
Where-Object {$_.Extent}
foreach ($Statement in $Statements)
{
$Action = {
$Text = $Statement.Extent.Text
$Text = $Text -replace '`\r?\n' # Concatenate lines that are escaped with backtick
# Alternative to remove superfluous whitespace:
# $Text = $Text -replace '\s+`\r?\n\s*', ' '
Write-Host "ECHO: $Text" -ForegroundColor Yellow
continue # or 'break' to stop on the line
}.GetNewClosure() # Create a closure, to capture the value of $Statement
$Params = #{
Script = $Path
Line = $Statement.Extent.StartLineNumber
Column = $Statement.Extent.StartColumnNumber
Action = $Action
}
$null = Set-PSBreakpoint #Params
}
}
Sample script:
# foo.ps1
gci `
-Name `
-File `
-Filter Victor.*
gci -File -Name *.md; gci -File -Name *.psd1
Usage:
# Without echo
❯ .\foo.ps1
Victor.build.ps1
Victor.psd1
Victor.psm1
README.md
Victor.psd1
❯ . .\Echo.ps1
❯ Enable-Echo .\foo.ps1
❯ .\foo.ps1
ECHO: gci -Name -File -Filter Victor.*
Victor.build.ps1
Victor.psd1
Victor.psm1
ECHO: gci -File -Name *.md
README.md
ECHO: gci -File -Name *.psd1
Victor.psd1
Tested on PSv5 and PSv7. Should work on PSv2, although the sample foo.ps1 is PSv3+ (IIRC).
This will not echo calls to other scripts. For that, you'd probably want to do more AST inspection, identify CommandAsts that call scripts, and recursively enable echo on those scripts too. Alternatively, there might be joy in Set-PSBreakpoint -Variable - the $$ variable might be a good place to start - but this would likely be a PITA to work with as it would invoke while you're trying to debug the echo function. You could inspect Get-PSCallStack to skip the action while you're at the command prompt.
I expect four answers and you have already mentioned three that do not work for you (Set-PSDebug, Start-Transaction, -Verbose). As much as they may be viable but not in the format you are looking for, I will not talk more of them.
For the third option, try using Get-History. Now, this will not print out each command as you execute it (only when you call it) like I assume you want. It will also likely not print out each of the lines inside another script (you would want a trace but you did not like that because it prints out more than just the execution).
You can try asking around the PowerShell repository but I do not expect you to find what you are seeking.
If Event logs is an option, start tracing by enabling this Group Policy.
Administrative Templates -> Windows Components -> Windows PowerShell
See Microsoft Docs - Script Tracing and Logging
Then you would of course need to parse the Event logs accordingly...

PowerShell Start-Job with a scriptblock calling another script with parameters

I'm using the Nmap-Scan.ps1 script and calling it using another script. The nmap-scan script is just a wrapper for the Nmap.exe program.
What I am required to do is provide a list of devices (by IP or DNS), run a NMAP scan, and have the results of each dropped into their own respective files.
Running the nmap-scan.ps1 directly works as expected:
.\Nmap-Scan.PS1 "localhost" -Arguments "-sS -sU -Pn -v" -OutDir "C:\Scripts\PowerShell\Get-NMAPData\Test" -Location "C:\Program Files (x86)\nmap\nmap.exe"
And I get the 'localhost.xml' file in the expected OutDir.
However, when I run the following code PowerShell shows that it is starting the jobs but I don't get any resulting XML files. So I must be doing something wrong or maybe the 'Start-Job' doesn't have the permissions to write the file out? I'm not sure either way and I'm stuck at this point. I would appreciate any help.
$MaxThreads = 2
$ServerList = Get-Content -Path "serverlist.txt"
foreach($server in $ServerList) {
#Create hashtable with parameters and their values
$Arg = #{
InputObject=$server;
Arguments="-sS -sU -Pn -v";
OutDir="C:\Scripts\PowerShell\Get-NMAPData\Test"
}
Start-Job -ScriptBlock{PARAM($Arg); & C:\Scripts\PowerShell\Get-NMAPData\Nmap-Scan.PS1 #Arg} -ArgumentList $Arg
while (#(Get-Job | Where-Object {$_.State -eq "Running"}).Count -ge $MaxThreads){
Write-Verbose "Waiting for open thread...($MaxThreads Maximum)"
Start-Sleep 3
}
}
foreach($job in Get-Job){
Receive-Job -Job $job -OutVariable temp
Write-Host($temp)
}
Job Output example:
StartTime : 9/7/2018 2:33:31 PM
OutFile : C:\Scripts\PowerShell\Get-NMAPData\Test\localhost.xml
Arguments : "-sS -sU -Pn -v"
Duration : 00:00:01.0990367
Hash :
Target : localhost
FinishTime : 9/7/2018 2:33:33 PM
RunspaceId : c68a80f5-198a-4af5-ac0d-c28667b3305c
#{StartTime=9/7/2018 2:33:31 PM; OutFile=C:\Scripts\PowerShell\Get-NMAPData\Test\localhost.xml; Arguments="-sS -sU -Pn -v"; Duration=00:00:01.0990367; Hash=; Target=localhost; FinishTime=9/7/2018 2:33:33 PM; PSComputerName=localhost; RunspaceId=c68a80f5-198a-4af5-ac0d-c28667b3305c; PSShowComputerName=False}

How do I clear $error and $LASTEXITCODE set by an external cmdlet or executable?

I have a custom module wrapping an external command (csrun.exe), and parses the output so I can use it in PowerShell.
Everything just about works except if the external command writes to stderror, and clearing the error in my cmdlet doesn't seem to fully work. It will clear (i.e. $error.count is 0 and $lasterrorcode is 0, but once I return to the script that is calling my cmdlet, $error and $lasterrorcode are no longer clear and the error in $error references the underlying exception for the external command
System.Management.Automation.RemoteException: The compute emulator is not running.
I've attempted, try-catches, clearing the mentioned variables. Regardless, the calling script retains a reference to the error.
CustomModule.psm1
$__azureEmulatorPath = "C:\Program Files\Microsoft SDKs\Azure\Emulator\"SDKs\Azure\Emulator\"
$__azureEmulator = __azureEmulatorPath + "csrun.exe"
function Get-EmulatorStatus() {
[OutputType([ComputeEmulatorStatus])]
[cmdletbinding()]
param()
$output = (& $__azureEmulator /status | Out-String)
if ($error.Count -gt 0 -or $LASTEXITCODE -ne 0) {
Write-Host ($Error | Format-List -Force | Out-String)
Write-Host Clearing Error and Continuing
$error.Clear()
$LASTEXITCODE = 0
}
#error from command cleared here
return $output
}
export-modulemember -function *
Test.ps1
import-module "CustomModule.psm1" # definew cmdlet Get-EmulatorStatus
$status = Get-EmulatorStatus
# even though error cleared in cmdlet, still here
Write-Host Write-Host Error $LASTEXITCODE, $Error.Count
Write-Host ($Error | Format-List -Force | Out-String)
Try using one of two options:
use exit from your cmdlet, e.g. exit 0 (preferred).
use a global scope when setting the codes explicitly, E.g.
$global:LASTEXITCODE
I ran into this calling robocopy that sets non-zero exit codes even on success, and interfered with Jenkin's automation.

Powershell script cannot get applications list data from windows 7 machine

Recently, I made a script to list all the installed applications in local & remote machine & give the output in a structured manner in an excelsheet.
It looks like this:
$a = Read-Host "Enter machine name" | Out-File -filepath C:\machine.txt
$computerName = Get-Content C:\machine.txt
$a = New-Object -comobject Excel.Application
$a.visible = $True
$b = $a.Workbooks.Add()
$c = $b.Worksheets.Item(1)
$c.Cells.Item(1,1) = "Name"
$c.Cells.Item(1,2) = "Publisher"
$c.Cells.Item(1,3) = "InstalledDate"
$c.Cells.Item(1,4) = "Version"
$c.Cells.Item(1,5) = "UninstallString"
$d = $c.UsedRange
$d.Interior.ColorIndex = 19
$d.Font.ColorIndex = 11
$d.Font.Bold = $True
$i = 2
function Get-InstalledAppReg ([string]$ComputerName) {
$RegPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
$BaseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine", $ComputerName)
$OpenSubKey = $BaseKey.OpenSubKey($RegPath)
$i =2
$OpenSubKey.GetSubKeyNames() | ForEach {
$Path = "$RegPath\$_"
$BaseKey.OpenSubKey($Path).GetValue("DisplayName")
$BaseKey.OpenSubKey($Path).GetValue("Publisher")
$BaseKey.OpenSubKey($Path).GetValue("InstalledDate")
$BaseKey.OpenSubKey($Path).GetValue("Version")
$BaseKey.OpenSubKey($Path).GetValue("UninstallString")
$c.Cells.Item($i,1) = $BaseKey.OpenSubKey($Path).GetValue("DisplayName")
$c.Cells.Item($i,2) = $BaseKey.OpenSubKey($Path).GetValue("Publisher")
$c.Cells.Item($i,3) = $BaseKey.OpenSubKey($Path).GetValue("InstalledDate")
$c.Cells.Item($i,4) = $BaseKey.OpenSubKey($Path).GetValue("Version")
$c.Cells.Item($i,5) = $BaseKey.OpenSubKey($Path).GetValue("UninstallString")
$i ++
}
}
Get-InstalledAppReg($computerName)
$d.EntireColumn.AutoFit()
$b.SaveAs("c:\softhive.xlsx")
$b.Close()
$a.Quit()
Get-Process | Where { $_.Name -Eq "Excel" } | Kill
This script ran perfectly for all remote machines which has XP as a OS.
Problem started when I started running it in windows & machines remotely.
Initially it gave wrong path error, when I realized that for windows 7, I probably have to use
"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" instead of
"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall".
With this different path, when I run the same script again, I get an error:
Exception calling "OpenRemoteBaseKey" with "2" argument(s): "The network path was not found.
"
At :line:24 char:62
$BaseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey( <<<< "LocalMachine", $ComputerName)
Probably, I need to change other things too in the script?
My machine, from where I run the script, is a windows XP SP3 machine.
Unfortunately the WMI Win32_Product class does not report all apps found in Control Panel's "Add or Remove Programs"...
The registry walk seems to be unavoidable, see:
http://powergui.org/thread.jspa?threadID=17068
http://learningpcs.blogspot.fr/2011/10/powershell-get-installed-software.html
Rather than comb the registry, I would use WMI for this. See Win32_Product and friends e.g.:
Get-WmiObject Win32_Product
Note that if I run this on my Windows 7 x64 system in a 64bit PowerShell prompt it shows all installed apps (32-bit and 64-bit):
Get-WmiObject Win32_Product| sort Vendor | Format-Table Name,InstallDate,Vendor
To see all the properties available execute:
Get-WmiObject Win32_Product | Select -First 1 | Format-List *
I remember a while back I did something like this at an IT firm and we simply searched the C: directory for the names of all programs ending in .exe, in order to optimize we would hone in on specific apps that we were looking for. We set up a batch that would pass or fail based on if what we wanted. Keep in mind this is a batch file, however the idea is similar.
echo ================= >>Software_Scan.txt
echo Below is a list of all wireless networks. Saved networks will be found in the Wireless Profiles folder
set filePath=
for /R "C:\Program Files (x86)" /D %%a in (*) do if exist "%%a\YahooMessenger.exe" set filePath=%%a& goto continue
:continue
if defined filePath echo %COMPUTERNAME% FAIL Yahoo Messenger >> Software_Scan.txt
if NOT defined filePath echo %COMPUTERNAME% PASS Yahoo Messenger >> Software_Scan.txt