Change title of another window through PowerShell - powershell

I have a VB application, which starts several instances of a third party non-GUI application. To keep track of these multiple instances, I update their title, using the SetWindowText() function. This application however has the nasty habit of continuously updating the title, so each SetWindowText works only temporary. As soon as you click anywhere in the screen, the tile is changed back.
I found a way to update the title through PowerShell, using the following code:
$titletext = "My Title"
# Start a thread job to change the window title to $titletext
$null = Start-ThreadJob { param( $rawUI, $windowTitle )
Start-Sleep -s 2
if ( $rawUI.WindowTitle -ne $windowTitle ) {
$rawUI.WindowTitle = $windowTitle
}
}-ArgumentList $host.ui.RawUI, $titletext
& 'c:\Program Files\Application\Application.exe' '-id=userid -pass=password'
This works perfectly and the title change is permanent, so exactly what I want. The only problem is that everything is being logged in the Windows PowerShell log, including the parameters -id= and -pass=.
A solution would be if I can start application.exe through my VB application and do the rename through a PowerShell script, but I don't know if that is possible through a ThreadJob.
Is it possible to start a ThreadJob and rename another window, maybe through it's handle?

Changing the console title from inside that console is your best bet, which is what your PowerShell code does.
While it is possible to call the SetWindowText() API function to set another process' console-window title, this change isn't guaranteed to stay in effect, because any subsequent interaction with such a window causes the original window title to be restored (this behavior seems to be built into conhost.exe, the console host underlying regular console windows on Windows).
By contrast, setting the title of the console window associated with the current process, does stay in effect (unless overridden again later), which is what the SetConsoleWindow() WinAPI function does (which shell- and API-based mechanisms such as title in cmd.exe, and [Console]::Title / $hostUI.RawUI.WindowTitle in PowerShell presumably ultimately call).
Therefore, stick with your PowerShell approach and avoid the password-logging problem with the help of an environment variable, as detailed below.
Windows PowerShell's script-block logging - see about_Logging - logs the source code of code being created.
You can avoid argument values from being logged if you - instead of providing literal arguments - provide them indirectly, via variables that you set from outside PowerShell.
Therefore:
Make your VB.NET application (temporarily) set an environment variable that contains the password. (Perhaps needless to say, storing and passing plain-text passwords is best avoided).
In your PowerShell script, refer to that environment variable instead of passing a literal password - that way, the actual password will not be shown in the logs.
For example, assuming that your VB.NET application has created environment variable MYPWD containing the password, before launching the PowerShell script:
$titletext = "My Title"
# Start a thread job to change the window title to $titletext
$null = Start-ThreadJob { param( $rawUI, $windowTitle )
Start-Sleep -s 2
if ( $rawUI.WindowTitle -ne $windowTitle ) {
$rawUI.WindowTitle = $windowTitle
}
} -ArgumentList $host.ui.RawUI, $titletext
# Note:
# * Assumes that your VB.NET application has set env. var. "MYPWD".
# * The arguments must be passed *individually*, not inside a single string.
& 'c:\Program Files\Application\Application.exe' -id=userid "-pass=$env:MYPWD"

Related

Changing the caption permanently

In my script, I start a third-party non-GUI application. I'm sort of trying to run this embedded in my script itself, so I will be able to change the icon and the windows caption.
I have two restriction:
I have to use & 'application.exe' to start the application. I tested Start-Process -NoNewWindow, but that breaks the functionality of application.exe.
The application.exe needs to be running in my script. I can only change the icon when I compile my script with PS1 to Exe afterwards.
The challenge I'm now facing is related to the first restriction. I need to change the caption a-synchronously. The $host.ui.RawUI.WindowTitle = “New Title” is not working, because application.exe changes the caption right after execution. So I need to change it by using functions like SetWindowText(). This is working in VB.NET, but I'm looking for a way to start this function in parallel with the & 'application.exe'. When I use &, the application is executed and the script waits until it terminates. So I need to do the SetWindowText() in parallel.
Visual Basic/C has a BackgroundWorker functions for such cases. Is something like that also available in PowerShell?
Thanks for any help in advance!
Kind regards,
Eric
Everybody thank you very much for your help!
The solution proved to be a lot easier that I thought. You don't have to keep on renaming the window. You just have to start the cmd window, wait a bit (in the background it's doing something with conhost.exe) and then rename it once. Here's the code I used:
$titletext = "My New CMD Window Title"
# Start a thread job to change the window title to $titletext
$null = Start-ThreadJob { param( $rawUI, $windowTitle )
Start-Sleep -s 2 #Wait until cmd.exe is started
if ( $rawUI.WindowTitle -ne $windowTitle ) {
$rawUI.WindowTitle = $windowTitle
}
} -ArgumentList $host.ui.RawUI, $titletext
& 'c:\windows\system32\cmd.exe'
Kind regards,
Eric

Powershell issues with Start-Process of same script elevated

I have a script that inventories the installed software on a machine, based on certain criteria provided in arguments. It also needs to support being buried in a very deep folder structure (Architects just think like that and I need to support it, not control it). And since it now also supports AppX, which requires running elevated, which in turn requires the full path to the PS1 in the -file argument, I am running into issues with the overall length of the Target value in the shortcut.
So, I was looking at the usual run with the -verb argument approach, like this
If (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
$psArguments = #('-nologo', '-noExit', '-executionpolicy', 'bypass', '-verb', 'Runas', '-file',$PSCommandPath)
$pxArguments = #($myInvocation.BoundParameters.GetEnumerator().ForEach({"-$($_.Key)", "$($_.Value)"}))
$arguments = $psArguments + $pxArguments
Start-Process 'powershell.exe' -ArgumentList:$arguments -Verb:Runas
Stop-Process $PID
} else {
Write-Host 'Elevated'
# actual code to process
}
But I am having two issues there, one is critical, one less so.
On the critical side, I see a second blue PowerShell console open for a split second, but then it closes with no error or anything. I would have expected the -noExit argument in $psArguments to at least leave that window open for me, so I could read any errors. But I can't see any red, so that makes me think there is no error, and this usually happens when the -file argument doesn't point to an actual script. However, if I validate the arguments like so
foreach ($item in $arguments) {
Write-Host "$item"
}
Write-Host "$PSCommandPath $(Test-Path $PSCommandPath)"
The arguments all look good and Test-Path returns true.
So, what in this am I getting wrong, such that I don't get a new PowerShell console that actually works?
The second issue is that I have defined fonts and colors and console size in the shortcut, and I wonder if there is a single variable, perhaps $host, that I could then send to the second session populated with the values of the first. I suspect not, because I think the only types that can be used as arguments are strings and numbers.
DOH: I was on the right track, with that comment about it looking like the script wasn't found. There are SPACES in that path, so I need
$psArguments = #('-nologo', '-noExit', '-executionpolicy', 'bypass', '-file',"`"$PSCommandPath`"")
Now I just need to deal with resizing the console. I can live without fonts and colors, but the width and text wrapping I need to address.

Get output when using .run

I'm trying to run a program that will web scrape from Pastebin using PowerShell. I used the following code to do so:
Set Wshell = CreateObject("WScript.Shell")
Wshell.Run "%ComSpec% /c powershell & $result = Invoke-WebRequest
""https://pastebin.com/raw/wAhYB4UY"" & $result.content ", 0, True
$result.content will bring up everything I need from Pastebin. How can I transfer $result.content to a VBScript variable?
I know this is possible using the Exec() method as demonstrated here, but I can't use it because I want my code to stay hidden, which to my knowledge is not possible with Exec() (without having a window popping and closing)
I also don't want to use File I/O in Powershell because that can really complicate other things I want my program to do in the future; however, If absolutely no options are available, then I can use it.
EDIT: Some readers pointed out that my script only consists of running Powershell, so why not program my script in PowerShell? Well, not everything I am planning for this script to do can be done in PS. for example, I want my script to type some stuff outside of PS. I also want to wait until the user has pressed a certain key, in my case PrtSc (which will create a popup a message using MsgBox).
$Path = 'https://pastebin.com/raw/etc'
$Raw = Invoke-WebRequest $Path
$Raw.Content
What do you want to do with the data that using VBScript is the preferable tool?

Windows Powershell directory specific functions

How I can create functions inside my $profile file that will be executed only if I am inside some specific path when trying to execute them?
There is nothing built into PowerShell to effectively hide a command based on any sort of context (e.g. your current directory.)
In PowerShell V3 or greater, there are some event handlers around command lookup that you could use. One solution would look something like this:
$ExecutionContext.InvokeCommand.PreCommandLookupAction = {
param([string]$commandName,
[System.Management.Automation.CommandLookupEventArgs]$eventArgs)
if ($commandName -eq 'MyCommand' -and $pwd -eq 'some directory')
{
$eventArgs.StopSearch = $true
}
}
Your profile is evaluated at PowerShell start up so current directory doesn't really come into play. Any function inside the profile will be available as soon as your able to use the PowerShell console. You could re-implement the tabexpansion2 function to not tab-complete certain functions based on the current directory but that seems a bit over-the-top. Another option would be to override the prompt function and depending on the current directory, set the function's visibility to either public or private. If they are private, they won't show up in tab expansion e.g.:
$func = Get-Command MyFunc
$func.Visibility = 'private' # or 'public'

How to pass needed parameters to script in Powershell ISE?

See Title.
I specified needed parameters in the head of a script:
param ($G_ARCHIVE = $(throw "Need file to upload!"),
$G_LOGFILE = $(throw "Need logfile!"))
When I want to debug the script with Powershell ISE: how can I fill these parameters?
Use the command pane. Open the script file in the ISE editor, set the breakpoints (F9). Then in the command pane type a command invoking this script with required parameters. I do not think there is another (built-in) way of doing this in ISE.
Open the script (myscript.ps1) in Windows Powershell ISE
Press F9 at the variable you want to inspect (debug). For instance 2nd line in the sample below where the $outputText variable is being assigned
In the shell window provide the relative path of the script along with the param value. For instance: .\myscript.ps1 "my value"
Hit enter (you don't need to hit F5)
You'll be able to see the debugging breakpoints in highlighted with yellow color. Place your cursor to the desired variable to inspect the current value.
There is another way. You can use the $PSDefaultParameterValues automatic variable, which exists (since v3) to provide new default arguments to cmdlets and advanced functions (doesn't work with normal functions). However, it does work for scripts, even when debugging in ISE. You have to declare [CmdletBinding()] or [Parameter()] like you would for an advanced function.
So for your example,
[CmdletBinding()]
param ($G_ARCHIVE = $(throw "Need file to upload!"),
$G_LOGFILE = $(throw "Need logfile!"))
you would execute something like this on the ISE Prompt:
$PSDefaultParameterValues.add("ExampleScript.ps1:G_ARCHIVE","File-to-upload.txt")
$PSDefaultParameterValues.add("ExampleScript.ps1:G_LOGFILE","Example.log")
You could also set the parameter value to a script block which will auto-execute at run-time:
$PSDefaultParameterValues["ExampleScript.ps1:G_LOGFILE"]={
"Example-{0:yyMMddHHmm}.log" -f [datetime]::Now
}
The variable is a hashtable and all the standard syntax applies, except the key must have the name of the script (or advanced function or cmdlet) followed by a colon then the parameter name. You can set defaults for multiple scripts or commands, and multiple parameters for each (each parameter is a new table entry).
By doing it this way, you can just hit F5 to run your script like normal. The parameters will be taken from the variable, so you don't have to type anything in.
Other use cases for $PSDefaultParameterValues might be customizations, like have the Get-History get only the last 10 entries, unless you specify the -Count parameter in the command. Because entries only persist for the current session, you would want to add customizations to your profile. You can read more by typing Get-Help about_Parameters_Default_Values at the prompt or view the same information on TechNet.
There is a much simpler way to set needed Parameters in ISE:
Before pressing F5 in ISE, set the Parameter you need. I usually comment the Parameter I need, example:
# $G_ARCHIVE = "C:\Temp\TestFile_001.txt"
I select everything after "#" and press F8. Next time I debug the script with F5, the Parameter is set to the value I am testing with, no need to pass the Parameters through the command line.
At least in Powershell 5.1 ISE when you press F5 to run a parameterized script, you will be asked to enter values for the parameters one by one.
When using the $PSDefaultParameterValues to populate the variables, you can reference the loaded script through the $psISE variable like
$PSDefaultParameterValues.add("$($psISE.CurrentFile.DisplayName):G_ARCHIVE","test")
I had this problem today. After messing around I discovered that the lower panel was running in Debug E.g. Prompt '[Dbg]: PS'
I stopped the debugging and re-issued my script with paramaeters.
Example: ./myScript.ps1 -ForceBuild $true
My results were that the script started and the breakpoints fired and allowed me to debug as expected.