Powershell - Launching outlook 2016 with switch commands with a hidden window - powershell

I'm working on an automaton script for use by our end users in SCCM. Everything is working the way i want it to except for the section for launching outlook with the /cleanviews switch in a hidden window.
Current script section
$path = "C:\Program Files (x86)\Microsoft Office\root\Office16"
$path2 = "C:\Program Files\Microsoft Office\root\Office16"
Test-path $path
if($True){
Start-Process -workingdirectory $path OUTLOOK.EXE /cleanviews -WindowStyle Hidden -ErrorAction Ignore}
Else {
Start-Process -workingdirectory $path2 OUTLOOK.EXE /cleanviews -WindowStyle Hidden -ErrorAction Ignore}
Start-Sleep -s 15
get-process OUTLOOK -ErrorAction ignore | stop-process
Start-Sleep -s 5
I've also tried removing the -working directory portion, and moving -windowstyle from the end to directly after start-process. So far the application is launching in a normal window
There is another script section after this portion and the reason i don't want our users to see the second window during the cleanviews switch is because they could easily freak out with it closing again and or interrupt the script.
suggestions?

just to start, the
Test-path $path
if($True){}
bit makes no sense and will always return true, i'm assuming this was put in for testing?
Second, some applications will ignore the -WindowStyle part, you can however do this:
Start-Process -FilePath "C:\Program Files\Microsoft Office\root\Office16\OUTLOOK.EXE"
$Win32ShowWindowAsync = Add-Type –memberDefinition #”
[DllImport("user32.dll")]
public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
“# -name “Win32ShowWindowAsync” -namespace Win32Functions –PassThru
$Show = 1 ; $Hide = 0 ; $TimeOut = 0
do{
Get-Process 'outlook' | % {
$Hidden = $Win32ShowWindowAsync::ShowWindowAsync($_.MainWindowHandle,$Hide)
}
Start-Sleep -Milliseconds 100
$TimeOut++
} while (!$Hidden -or $TimeOut -gt 100)
which is pretty ugly, but PowerShell doesn't have a native way to hide an existing process window.

Related

How to minimize the window for foo.exe after bar.exe opens

I'm re-writting a batch file that combined powershell and cmd commands to be strictly a powershell script, and I'm looking for a way to minimize/close the API confirmation window which pops up in the user's web browser after the application the script calls on starts.
My original batch file code looked like this using a shell object to minimize everything before moving on to the next command:
cd /d "%userprofile%\Desktop\streamblastoff\libraries\binaries\Snip"
start Snip.exe
timeout /t 1 /nobreak
powershell -command "(new-object -com shell.application).minimizeall()"
And this is what I have so far:
Push-Location -Path $PSScriptRoot\libraries\binaries\snip
Start-Process .\Snip.exe; (new-object -com shell.application).minimizeall()
Pop-Location
It doesn't work though, everything minimizes before the browser window appears ... which I guess I should have seen coming. Ideally I'd like to do it all a bit cleaner in my new script, and be able to close/minimize the specific tab once the window pops up in the users default browser, but I'm not sure if that'd be possible ...
needing to minimize a users default browser window after the first application starts
# First application.
$bar = "powershell"
# First application starts.
Start-Process -FilePath $bar
# Get Default Browser
$foo = Get-ItemPropertyValue -Path "Registry::HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice" -Name 'ProgId' |
ForEach-Object {
Get-ItemPropertyValue -Path "Registry::HKEY_CLASSES_ROOT\$_\shell\open\command" -Name '(default)'
} |
ForEach-Object {
$_ -match '^"([^"]+\.exe)"' | Out-Null
$matches[1]
} |
ForEach-Object {
Get-Item -Path $_
} |
ForEach-Object {
$_.BaseName
}
# End Get Default Browser
Function minimizeProcess
{
Param(
[Parameter(Mandatory=$true,
ValueFromPipeline=$true,
HelpMessage="Input a System.Diagnostics.Process object.")]
[System.Diagnostics.Process] $Process
)
Begin
{
# P/Invoke Definition
$signature = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
# Compile new class.
$myType = Add-Type -MemberDefinition $signature -Name MyClass -Namespace MyNamespace -PassThru
}
Process
{
# Minimize the window.
# For different display actions see:
# https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
$myType::ShowWindowAsync($Process.MainWindowHandle, 7) | Out-Null
}
}
# Minimize $foo if $bar is running.
if ((Get-Process | ForEach-Object { $_.Name }) -contains $bar)
{
Get-Process -Name $foo |
ForEach-Object {
$process = Get-CimInstance Win32_Process -Filter "ProcessID = '$($_.Id)'"
$owner = Invoke-CimMethod -InputObject $process -MethodName GetOwner
Add-Member -InputObject $_ -NotePropertyName Owner -NotePropertyValue $owner.User
$_
} |
# Only minimize processes that belong to the user.
Where-Object { $_.Owner -like $env:USERNAME } |
ForEach-Object {
minimizeProcess $_
# foo minimizes.
}
}
PowerShell cmdlet to open a URL in the user's default browser. · GitHub
How to find the default browser via the registry on Windows 10 - Stack Overflow
PowerShell P/Invoke Walkthrough | Precision Computing
Add-Type (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Docs
Process.WaitForInputIdle Method (System.Diagnostics) | Microsoft Docs
Process.MainWindowHandle Property (System.Diagnostics) | Microsoft Docs
ShowWindowAsync function (winuser.h) - Win32 apps | Microsoft Docs
powershell - Get-Process and Process Owner - Stack Overflow
.net - How do I determine the owner of a process in C#? - Stack Overflow
everything minimizes before the browser window appears
The error you were making was you did not copy the sleep.
Push-Location -Path $PSScriptRoot\libraries\binaries\snip
Start-Process .\Snip.exe
# timeout /t 1 /nobreak
Start-Sleep 1
(new-object -com shell.application).minimizeall()
Pop-Location
When creating macros, you have to insert sleeps to accommodate delays in the graphical user interface.
Start-Sleep (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Docs
Timeout - delay in seconds - Windows CMD - SS64.com
be able to close/minimize the specific tab
SeleniumHQ Browser Automation

How to check the return code of Start-Process [duplicate]

I'm trying to run a program from PowerShell, wait for the exit, then get access to the ExitCode, but I am not having much luck. I don't want to use -Wait with Start-Process, as I need some processing to carry on in the background.
Here's a simplified test script:
cd "C:\Windows"
# ExitCode is available when using -Wait...
Write-Host "Starting Notepad with -Wait - return code will be available"
$process = (Start-Process -FilePath "notepad.exe" -PassThru -Wait)
Write-Host "Process finished with return code: " $process.ExitCode
# ExitCode is not available when waiting separately
Write-Host "Starting Notepad without -Wait - return code will NOT be available"
$process = (Start-Process -FilePath "notepad.exe" -PassThru)
$process.WaitForExit()
Write-Host "Process exit code should be here: " $process.ExitCode
Running this script will cause Notepad to be started. After this is closed manually, the exit code will be printed, and it will start again, without using -wait. No ExitCode is provided when this is quit:
Starting Notepad with -Wait - return code will be available
Process finished with return code: 0
Starting Notepad without -Wait - return code will NOT be available
Process exit code should be here:
I need to be able to perform additional processing between starting the program and waiting for it to quit, so I can't make use of -Wait. How can I do this and still have access to the .ExitCode property from this process?
There are two things to remember here. One is to add the -PassThru argument and two is to add the -Wait argument. You need to add the wait argument because of this defect.
-PassThru [<SwitchParameter>]
Returns a process object for each process that the cmdlet started. By default,
this cmdlet does not generate any output.
Once you do this a process object is passed back and you can look at the ExitCode property of that object. Here is an example:
$process = start-process ping.exe -windowstyle Hidden -ArgumentList "-n 1 -w 127.0.0.1" -PassThru -Wait
$process.ExitCode
# This will print 1
If you run it without -PassThru or -Wait, it will print out nothing.
The same answer is here: How do I run a Windows installer and get a succeed/fail value in PowerShell?
It's also worth noting that there's a workaround mentioned in the "defect report" link above, which is as following:
# Start the process with the -PassThru command to be able to access it later
$process = Start-Process 'ping.exe' -WindowStyle Hidden -ArgumentList '-n 1 -w 127.0.0.1' -PassThru
# This will print out False/True depending on if the process has ended yet or not
# Needs to be called for the command below to work correctly
$process.HasExited
# This will print out the actual exit code of the process
$process.GetType().GetField('exitCode', 'NonPublic, Instance').GetValue($process)
While trying out the final suggestion above, I discovered an even simpler solution. All I had to do was cache the process handle. As soon as I did that, $process.ExitCode worked correctly. If I didn't cache the process handle, $process.ExitCode was null.
example:
$proc = Start-Process $msbuild -PassThru
$handle = $proc.Handle # cache proc.Handle
$proc.WaitForExit();
if ($proc.ExitCode -ne 0) {
Write-Warning "$_ exited with status code $($proc.ExitCode)"
}
Two things you could do I think...
Create the System.Diagnostics.Process object manually and bypass Start-Process
Run the executable in a background job (only for non-interactive processes!)
Here's how you could do either:
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "notepad.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = ""
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
#Do Other Stuff Here....
$p.WaitForExit()
$p.ExitCode
OR
Start-Job -Name DoSomething -ScriptBlock {
& ping.exe somehost
Write-Output $LASTEXITCODE
}
#Do other stuff here
Get-Job -Name DoSomething | Wait-Job | Receive-Job
The '-Wait' option seemed to block for me even though my process had finished.
I tried Adrian's solution and it works. But I used Wait-Process instead of relying on a side effect of retrieving the process handle.
So:
$proc = Start-Process $msbuild -PassThru
Wait-Process -InputObject $proc
if ($proc.ExitCode -ne 0) {
Write-Warning "$_ exited with status code $($proc.ExitCode)"
}
Or try adding this...
$code = #"
[DllImport("kernel32.dll")]
public static extern int GetExitCodeProcess(IntPtr hProcess, out Int32 exitcode);
"#
$type = Add-Type -MemberDefinition $code -Name "Win32" -Namespace Win32 -PassThru
[Int32]$exitCode = 0
$type::GetExitCodeProcess($process.Handle, [ref]$exitCode)
By using this code, you can still let PowerShell take care of managing redirected output/error streams, which you cannot do using System.Diagnostics.Process.Start() directly.
Here's a variation on this theme. I want to uninstall Cisco Amp, wait, and get the exit code. But the uninstall program starts a second program called "un_a" and exits. With this code, I can wait for un_a to finish and get the exit code of it, which is 3010 for "needs reboot". This is actually inside a .bat file.
If you've ever wanted to uninstall folding#home, it works in a similar way.
rem uninstall cisco amp, probably needs a reboot after
rem runs Un_A.exe and exits
rem start /wait isn't useful
"c:\program files\Cisco\AMP\6.2.19\uninstall.exe" /S
powershell while (! ($proc = get-process Un_A -ea 0)) { sleep 1 }; $handle = $proc.handle; 'waiting'; wait-process Un_A; exit $proc.exitcode

Maximizing Lync Window Using Powershell?

I've created a script that will automatically initiate a video call with a user of my choice.
When ran, the script leaves the video call docked, with the lync video call window flashing.
How would I be able to get this window to maximize and go to full screen when the script is ran?
Thank you so much for your help.
Below is my code
$assemblyPath = “C:\Program Files (x86)\Microsoft Office 2013\LyncSDK\Assemblies\Desktop\Microsoft.Lync.Model.DLL”
Import-Module $assemblyPath
$LyncClient = [Microsoft.Lync.Model.LyncClient]::GetClient()
$StartVideoCallMethod = {
$Conversation = $this.ConversationManager.AddConversation();
$contact = $LyncClient.ContactManager.GetContactByUri("useremailhere")
[void]$Conversation.AddParticipant($contact);
[void]$Conversation.Modalities['AudioVideo'].BeginConnect({}, 0);
};
Add-Member -InputObject $LyncClient -MemberType ScriptMethod -Name StartVideoCall -Value $StartVideoCallMethod -Force;
# Initiate the video call
$Conversation = $LyncClient.StartVideoCall();
I don't have Lync, but something like this should work. I'm using the process name (or what I'm guessing it is) to get the MainWindowHandle for the Lync window, then sending that a command to maximize (cmd=3, see here for the full list of values: https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548%28v=vs.85%29.aspx).
This code may break if more than one process matches by name, but it should get you started; if you can get the PID or some other, better unique identifier, use that. Just mess around with the output of Get-Process and you should see a number of options, and remember you can always use a Where clause to filter the output. Or of course if there's some way to get the MainWindowHandle directly from $LyncClient, even better.
$w = Get-Process -Name "Lync"
$Win32ShowWindowAsync = Add-Type –memberDefinition `
'[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);' `
-name “Win32ShowWindowAsync” -namespace Win32Functions –passThru
$Win32ShowWindowAsync::ShowWindowAsync($w.MainWindowHandle,3) | Out-Null
Here the code that I have so far.
Still needs some tweaks to perfect it but it does the job.
Tweaks would be specifying which window to maximize as it will sometimes maximize the lync contacts window.
Code
$assemblyPath = “C:\Program Files (x86)\Microsoft Office 2013\LyncSDK\Assemblies\Desktop\Microsoft.Lync.Model.DLL”
Import-Module $assemblyPath
$exePath = "C:\Program Files\Microsoft Office 15\root\office15\lync.exe"
if(!(get-process | ?{$_.path -eq $exePath})){
Start-Process -FilePath $exePath -WindowStyle Maximized
Start-Sleep -s 10
}
$LyncClient = [Microsoft.Lync.Model.LyncClient]::GetClient()
$StartVideoCallMethod = {
$Conversation = $this.ConversationManager.AddConversation();
$contact = $LyncClient.ContactManager.GetContactByUri("ernesto.gomila#quirchfoods.com")
[void]$Conversation.AddParticipant($contact);
[void]$Conversation.Modalities['AudioVideo'].BeginConnect({}, 0);
};
Add-Member -InputObject $LyncClient -MemberType ScriptMethod -Name StartVideoCall -Value $StartVideoCallMethod -Force;
# Initiate the video call
$Conversation = $LyncClient.StartVideoCall();
#Maximize window
$w = Get-Process -Name "lync"
$Win32ShowWindowAsync = Add-Type –memberDefinition #"
[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
"# -name “Win32ShowWindowAsync” -namespace Win32Functions –passThru
Start-Sleep -s 2
$Win32ShowWindowAsync::ShowWindowAsync($w.MainWindowHandle,3) | Out-Null

Powershell script running fine on Windows 8 but not on Windows 7

I am very new to powershell and I'm not sure what I did wrong. It is running fine on my Windows 8 PC but when I send it to someone else (he has Windows 7; created this for him), he gets a not allowed to run scripts error.
Tried with -ExecutionPolicy RemoteSigned but still no luck.
##################
<# CONFIG START #>
##################
#replace the path with your steam path. For example: C:\Program Files (x86)\Steam\Steam.exe
$steam_path = "C:\Program Files (x86)\Steam\steam.exe"
#You can change it to Ethernet 1 or Ethernet 2 or Ethernet 3 etc depending on which adapter you want to disable.
#If you have custom name for them (you can rename them from control panel), have to use that name.
$adapter_name = "Ethernet 1"
<#
What program to run.
1: Steam Dota 2
2: Steam CS
3: Steam CSS
4: Steam CSGO
5: Custom Program 1
6: Custom Program 2
7: Custom Program 3
#>
$game_id = "5"
<# Custom Program path and arguments#>
$cp1_path = "C:\Program Files (x86)\counter-strike source\css.exe"
$cp1_arg = " "
$cp2_path = ""
$cp2_arg = " "
$cp3_path = ""
$cp2_arg = " "
$delay = 20
################
<# CONFIG END #>
################
"Checking admin permissions..."
If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{
"Administrator permissions required."
$arguments = '-ExecutionPolicy RemoteSigned -file "' + $myinvocation.mycommand.definition + '"'
# $arguments
Start-Process powershell -Verb runAs -ArgumentList $arguments
Break
}
"Exiting Steam..."
Start-Process -FilePath $steam_path -ArgumentList "-shutdown" -Wait:$true
Start-Sleep -s 2
"Disabling Network Adapter..."
Disable-NetAdapter -Name $adapter_name -Confirm:$false
Start-Sleep -s 5
"Starting Game..."
Switch($game_id)
{
1
{
Start-Process -filepath "steam://rungameid/570"
}
2
{
Start-Process -filepath "steam://rungameid/10"
}
3
{
Start-Process -filepath "steam://rungameid/240"
}
4
{
Start-Process -filepath "steam://rungameid/730"
}
5
{
Start-Process $cp1_path -ArgumentList $cp1_arg
}
6
{
Start-Process $cp2_path -ArgumentList $cp2_arg
}
7
{
Start-Process $cp3_path -ArgumentList $cp3_arg
}
}
Start-Sleep -s $delay
"Enabling Network Adapter..."
Enable-NetAdapter $adapter_name -Confirm:$false
exit
If you sent him the script, then RemoteSigned is doing it's job just fine. He got the script remotely (from you) and it is not signed, so it won't be executed.
Tell your friend to navigate to the ps1 script in Windows Explorer and right click, then choose "Unblock." He will then need to restart the PowerShell instance if it has failed to run the script already since this kind of information is cached by Windows.
The error indicates you didn't correctly set the execution policy. Firstly, you have run Powershell as an administrator. To do this, right click on the Powershell icon in the start menu and click on "Run as Administrator". Next you have to run the following command in Powershell:
Set-ExecutionPolicy RemoteSigned
You will be prompted to allow the change. Type "Y" and press return.
Finally, try running your script.

Obtaining ExitCode using Start-Process and WaitForExit instead of -Wait

I'm trying to run a program from PowerShell, wait for the exit, then get access to the ExitCode, but I am not having much luck. I don't want to use -Wait with Start-Process, as I need some processing to carry on in the background.
Here's a simplified test script:
cd "C:\Windows"
# ExitCode is available when using -Wait...
Write-Host "Starting Notepad with -Wait - return code will be available"
$process = (Start-Process -FilePath "notepad.exe" -PassThru -Wait)
Write-Host "Process finished with return code: " $process.ExitCode
# ExitCode is not available when waiting separately
Write-Host "Starting Notepad without -Wait - return code will NOT be available"
$process = (Start-Process -FilePath "notepad.exe" -PassThru)
$process.WaitForExit()
Write-Host "Process exit code should be here: " $process.ExitCode
Running this script will cause Notepad to be started. After this is closed manually, the exit code will be printed, and it will start again, without using -wait. No ExitCode is provided when this is quit:
Starting Notepad with -Wait - return code will be available
Process finished with return code: 0
Starting Notepad without -Wait - return code will NOT be available
Process exit code should be here:
I need to be able to perform additional processing between starting the program and waiting for it to quit, so I can't make use of -Wait. How can I do this and still have access to the .ExitCode property from this process?
There are two things to remember here. One is to add the -PassThru argument and two is to add the -Wait argument. You need to add the wait argument because of this defect.
-PassThru [<SwitchParameter>]
Returns a process object for each process that the cmdlet started. By default,
this cmdlet does not generate any output.
Once you do this a process object is passed back and you can look at the ExitCode property of that object. Here is an example:
$process = start-process ping.exe -windowstyle Hidden -ArgumentList "-n 1 -w 127.0.0.1" -PassThru -Wait
$process.ExitCode
# This will print 1
If you run it without -PassThru or -Wait, it will print out nothing.
The same answer is here: How do I run a Windows installer and get a succeed/fail value in PowerShell?
It's also worth noting that there's a workaround mentioned in the "defect report" link above, which is as following:
# Start the process with the -PassThru command to be able to access it later
$process = Start-Process 'ping.exe' -WindowStyle Hidden -ArgumentList '-n 1 -w 127.0.0.1' -PassThru
# This will print out False/True depending on if the process has ended yet or not
# Needs to be called for the command below to work correctly
$process.HasExited
# This will print out the actual exit code of the process
$process.GetType().GetField('exitCode', 'NonPublic, Instance').GetValue($process)
While trying out the final suggestion above, I discovered an even simpler solution. All I had to do was cache the process handle. As soon as I did that, $process.ExitCode worked correctly. If I didn't cache the process handle, $process.ExitCode was null.
example:
$proc = Start-Process $msbuild -PassThru
$handle = $proc.Handle # cache proc.Handle
$proc.WaitForExit();
if ($proc.ExitCode -ne 0) {
Write-Warning "$_ exited with status code $($proc.ExitCode)"
}
Two things you could do I think...
Create the System.Diagnostics.Process object manually and bypass Start-Process
Run the executable in a background job (only for non-interactive processes!)
Here's how you could do either:
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "notepad.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = ""
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
#Do Other Stuff Here....
$p.WaitForExit()
$p.ExitCode
OR
Start-Job -Name DoSomething -ScriptBlock {
& ping.exe somehost
Write-Output $LASTEXITCODE
}
#Do other stuff here
Get-Job -Name DoSomething | Wait-Job | Receive-Job
The '-Wait' option seemed to block for me even though my process had finished.
I tried Adrian's solution and it works. But I used Wait-Process instead of relying on a side effect of retrieving the process handle.
So:
$proc = Start-Process $msbuild -PassThru
Wait-Process -InputObject $proc
if ($proc.ExitCode -ne 0) {
Write-Warning "$_ exited with status code $($proc.ExitCode)"
}
Or try adding this...
$code = #"
[DllImport("kernel32.dll")]
public static extern int GetExitCodeProcess(IntPtr hProcess, out Int32 exitcode);
"#
$type = Add-Type -MemberDefinition $code -Name "Win32" -Namespace Win32 -PassThru
[Int32]$exitCode = 0
$type::GetExitCodeProcess($process.Handle, [ref]$exitCode)
By using this code, you can still let PowerShell take care of managing redirected output/error streams, which you cannot do using System.Diagnostics.Process.Start() directly.
Here's a variation on this theme. I want to uninstall Cisco Amp, wait, and get the exit code. But the uninstall program starts a second program called "un_a" and exits. With this code, I can wait for un_a to finish and get the exit code of it, which is 3010 for "needs reboot". This is actually inside a .bat file.
If you've ever wanted to uninstall folding#home, it works in a similar way.
rem uninstall cisco amp, probably needs a reboot after
rem runs Un_A.exe and exits
rem start /wait isn't useful
"c:\program files\Cisco\AMP\6.2.19\uninstall.exe" /S
powershell while (! ($proc = get-process Un_A -ea 0)) { sleep 1 }; $handle = $proc.handle; 'waiting'; wait-process Un_A; exit $proc.exitcode