Application doesn't get triggered from Powershell script, when invoking inside Do...Until - powershell

I am trying to build custom Windows System Utility script which offers some tasks with relevant keypress choices.
For cleanup task, I am trying to invoke CCleaner64.exe from this script, with it's correct switches as mentioned here. And the script I built so far is below:
$ScriptDir = Split-Path $MyInvocation.MyCommand.Path
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
$CommandLine = "-File `"" + $MyInvocation.MyCommand.Path + "`" " + $MyInvocation.UnboundArguments
Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList $CommandLine
Exit
}
}
Set-Location $ScriptDir; Echo 'Current Directory: ' + (Get-Location | Out-String)
function SysUtilMenu {
param (
[string]$Title = 'Windows System Utility'
)
Clear-Host
Write-Host "============ $Title ==========="
Write-Host "1: Do task 1 here."
Write-Host "2: Cache/Temp Files Cleanup."
Write-Host "Q: Exit this Application."
}
Do
{
SysUtilMenu
$selection = Read-Host "Press key to run given task..."
switch ($selection)
{
'1' {
## Do task 1 here...
} '2' {
$CclnrApp64 = "$Env:ProgramFiles\CCleaner\CCleaner64.exe"
Start-Process $CclnrApp64 -ArgumentList '/Clean'
Start-Process $CclnrApp64 -ArgumentList '/Registry'
}
}
}
Until($selection -eq 'q')
But when I press '2', it doesn't invoke CCleaner64.exe in the taskbar, which I checked.
I don't get, why the same Start-Process line doesn't work in that script, but if I open the Powershell terminal separately and run below commands one-by-one, it works perfectly ?
$CclnrApp64 = "$Env:ProgramFiles\CCleaner\CCleaner64.exe"
Start-Process $CclnrApp64 -ArgumentList '/Clean'
Is it due to Script's Self-Elevation, I have taken care of setting the location of the script instead of C:\Windows\System32.
Suggestion with detailed explanation is greatly appreciated...

From the link you have added, the documentation under Command-line parameters for CCleaner operation pane focus, it says the switch should be /CLEANER, not /Clean, and since your code also shows the switch /Registry, I thought this is what you were after (to open the app on a particular tab page).
My initial thoughts were:
it is possible you need to add the -Wait switch so PowerShell will ony start the second command after the first one has completed.
so the ful command would be Start-Process -FilePath "$CclnrApp64" -ArgumentList '/Cleaner' -Wait
to try and use the call operator & instead of Start-Process like & "$CclnrApp64" "/CLEANER"
Both above have the paths in variable $CclnrApp64 in between quotes because $env:programfiles will usually expand to C:\Program Files which has a space in the path.
Under Command-line parameters for CCleaner Business and Technician Edition, there is also a switch called /Clean
If you have that version, the switch should clean up using whatever rules are rules defined in ccleaner.ini and optionally puts the results in log_filename.txt
However, on that same CCleaner page, there is also a listing of other parameters, especially for use in a commandline and as you have experimented using the /AUTO switch, it appears this is what you were after:
CCleaner runs silently and automatically, using the current set of saved options to clean the PC. CCleaner then exits.
A note about the /AUTO switch though:
When you run CCleaner.exe using the /AUTO parameter, CCleaner does not run the Registry cleaner. You cannot currently run the Registry cleaner through a command-line parameter
All this means there are several switches you can use with CCleaner, but they all serve a different purpose.
/CLEANER, /REGISTRY, /TOOLS and /OPTIONS are for opening the application at a certain pane
/AUTO (with optional /SHUTDOWN), /EXPORT and /DELETE (with optional /METHOD) are to have the application perform cleaning/delete actions
and for the Business and Technician Edition there is also
/analyze, /clean and /update

Related

Powershell Script won't run correctly in Task Scheduler

I recently created some PowerShell code that runs perfectly fine on its own or via a command prompt (or a .bat file). Hell, it runs fine as a .cmd file too. When I create a task scheduler task for it to run automatically, the result claims that the job runs 'successfully' but there isn't any output in the folder destination.
The whole goal of the script is to look at a particular subfolder within outlook and find files with a particular name. Then it outputs the latest related attachments to a specific destination.
PowerShell Script
$olFolderInbox = 6
$limit = (Get-Date).AddDays(-1)
$filepath = "C:\Scripts\attachment Project\Spreadsheets"
$ol = New-Object -com outlook.application;
$ns = $ol.GetNamespace("MAPI");
$acct = $ns.GetDefaultFolder($olFolderInbox)
$targetfolder = $acct.Folders | where-object { $_.name -eq "Training" }
$targetfolder.Items | foreach {
if ($_.ReceivedTime -ge $limit) {
$_.attachments |
foreach {
Write-Host "attached file: ",$_.filename
If ($_.filename -match 'SessionCompletions' -or $_.filename -match 'Certifications'){
$_.saveasfile((Join-Path $filepath $_.FileName))
$ol.quit()
}
}
}
}
I've tried all kinds of things to get the Task within Task Scheduler to work and nothing so far seems to help. To start I tried using a basic bat file to run the task. Mind you, this basic format has worked with many other PowerShell related tasks that I set up to run via a bat file.
Basic Bat Script
powershell -file extract-spreadsheets.ps1
Now sometimes I've needed to include an -executionpolicy argument as well but it sadly didn't work in this case.
A little more complex Script
Powershell.exe -executionpolicy RemoteSigned -File "C:\Scripts\attachment Project\extract spreadsheets.ps1"
Note: I made sure the get-executionpolicy on the computer was / is set to RemoteSigned. I've also tried Unrestricted as well.
I've also tried even more complex / different approaches such as . . .
Even more Complex of a Script
#ECHO OFF
SET ThisScriptsDirectory=C:\Scripts\David attachment Project\
SET PowerShellScriptPath=%ThisScriptsDirectory%extract-spreadsheets.ps1
PowerShell -NoProfile -ExecutionPolicy RemoteSigned -Command "& {Start-Process PowerShell -ArgumentList '-NoProfile -ExecutionPolicy RemoteSigned -File ""%PowerShellScriptPath%""' -Verb RunAs}";
And I've even tried getting a .cmd file going and trying to use that within Task Scheduler:
##:: This prolog allows a PowerShell script to be embedded in a .CMD file.
##:: Any non-PowerShell content must be preceeded by "##"
##setlocal
##set POWERSHELL_BAT_ARGS=%*
##if defined POWERSHELL_BAT_ARGS set POWERSHELL_BAT_ARGS=%POWERSHELL_BAT_ARGS:"=\"%
##PowerShell -Command Invoke-Expression $('$args=#(^&{$args} %POWERSHELL_BAT_ARGS%);'+[String]::Join(';',$((Get-Content '%~f0') -notmatch '^^##'))) & goto :EOF
$olFolderInbox = 6
$limit = (Get-Date).AddDays(-1)
$filepath = "C:\Scripts\attachment Project\Spreadsheets"
$ol = New-Object -com outlook.application;
$ns = $ol.GetNamespace("MAPI");
$acct = $ns.GetDefaultFolder($olFolderInbox)
$targetfolder = $acct.Folders | where-object { $_.name -eq "Training" }
$targetfolder.Items | foreach {
if ($_.ReceivedTime -ge $limit) {
$_.attachments |
foreach {
Write-Host "attached file: ",$_.filename
If ($_.filename -match 'SessionCompletions' -or $_.filename -match 'Certifications'){
$_.saveasfile((Join-Path $filepath $_.FileName))
$ol.quit()
}
}
}
}
In terms of Task Scheduler, I've tried a multitude of things. To start . . .
For Program / script, I tried various relative and explicit destinations / paths for bat, cmd and PowerShell.
Some examples:
Powershell.exe (again I've also done the full path as well)
C:\Scripts\attachment Project\Training_Reports.bat
Training_Reports.bat
C:\Scripts\attachment Project\extract-spreadsheets.cmd
Note: I've added the above Programs / Actions with and without quotes.
Again, the more frustrating thing is the basic format of Training_Reports.bat with the start in (optional) field of:
C:\Scripts\attachment Project or C:\Scripts\attachment Project\
Has worked with every other script / task I have set up in this format!
I've tried numerous arguments for Execution Policy such as RemoteSigned, Bypass and Unrestricted. I've Added other arguments like: -NoProfile, -noninteractive, -File, -command, -NoLogo etc etc.
Examples of what the Arguments Field has looked like within Task Scheduler:
-ExecutionPolicy Bypass -File "C:\Scripts\attachment Project\extract-spreadsheets.ps1"
-NoLogo -NonInteractive -ExecutionPolicy RemoteSigned -File "C:\Scripts\attachment Project\extract-spreadsheets.ps1"
-NoProfile -ExecutionPolicy Unrestricted -Command "C:\Scripts\attachment Project\extract-spreadsheets.ps1"
I tried using various accounts to 'run the task'.
my domain account (which my account is a global domain admin and has
full permissions)
local Admin account
another Domain Admin Account
I've tried even SYSTEM
None of the accounts above have changed the outcome. Other settings I've looked at that people claim could mess with Tasks.
Turned off 'Start the task only if the computer is on AC power'
Ran the task whether user is logged on or not
Also ran it as when user is logged on
Set it for 'Highest privileges'
I tried running the task in 'configure for' drop down menu for
Windows 10 and Windows 7 / Server 2008.
Other things I have tried:
The initial PowerShell Script had mapped drive destinations. I changed the code to use C drive locations.
Explicitly giving that user account full control over the
directories involved.
Changing ownership of the directories involved to the same user.
Changing the security permissions on the ps1, cmd, bat, outlook.exe files to always Run as Administrator.
Tried running the script with Outlook running and not running.
Note: running it manually works regardless if Outlook is running.
Why is this task within Task Scheduler not outputting correctly?

PowerShell function not running as expected

I have a curious case that I cannot fathom the reason for...
Please know I am a novice to PowerShell.
I am working on a PowerShell menu system to help automate building out new computers in my environment. I have a PS1 file that holds the script for an app install. When I use the script to reference this I am able to run it and have no issue. However, when I try inserting this into a function and referencing it does not.
This works:
4 # Microsoft Office 32-bit
{
Write-Host "`nMicrosoft Office 32-bit..." -ForegroundColor Yellow
# {installMS32Bit}
Invoke-Expression "cmd /c start powershell -NoExit -File '\\**SERVERPATH**\menuItems\ms_office\32-bit\install.ps1'"
Start-Sleep -seconds 2
}
This does not:
function installMS32Bit(){
Invoke-Expression "cmd /c start powershell -NoExit -File '\\**SERVERPATH**\menuItems\ms_office\32-bit\install.ps1'"
}
}
4 # Microsoft Office 32-bit
{
Write-Host "`nMicrosoft Office 32-bit..." -ForegroundColor Yellow
{installMS32Bit}
Start-Sleep -seconds 2}
install.ps1 file:
# Copy MS Office uninstall and setup to local then run and install 32-bit Office
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\setup.exe' -Destination 'C:\temp\' -Force
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\uninstall.xml' -Destination 'C:\temp\' -Force
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\32-bit\Setup.exe' -Destination 'C:\temp' -Force
Invoke-Expression ("cmd /c 'C:\temp\setup.exe' /configure 'C:\temp\uninstall.xml'")
Start-Process -FilePath 'C:\temp\Setup.exe'
Secondary question and a little explanation for Invoke-Expression...
I like to see progress and like to have secondary windows open to monitor the new process being run. I was unable to find a solution with a persistent window that worked for me to do this without Invoke-Expression.
If there is a better way to do this in PowerShell I am all ears!
{installMS32Bit}
As Mathias points out in a comment on the question, this statement doesn't call your function, it wraps it in a script block ({ ... })[1], which is a piece of reusable code (like a function pointer, loosely speaking), for later execution via &, the call (execute) operator.
To call your function, just use its name (by itself here, given that there are no arguments to pass): installMS32Bit
Invoke-Expression should generally be avoided; definitely don't use it to invoke an external program, as in your attempts.
Additionally, there's generally no need to call an external program via cmd.exe (cmd /c ...), just invoke it directly.
For instance, replace the last Invoke-Epression call from your question with:
# If the EXE path weren't quoted, you wouldn't need the &
& 'C:\temp\setup.exe' /configure 'C:\temp\uninstall.xml'
I like to see progress and like to have secondary windows open to monitor the new process being run. I was unable to find a solution with a persistent window that worked for me to do this without Invoke-Expression.
(On Windows), Start-Process by default executes a console application in a new window (unless you specify -NoNewWindow), asynchronously (unless you specify -Wait).
You cannot pass a .ps1 script directly to Start-Process (it will be treated like a document to open rather than an executable to call), but you can pass it to PowerShell's CLI via the -File parameter:
Start-Process powershell.exe '-File install.ps1'
The above is short for:
Start-Process -FilePath powershell.exe -ArgumentList '-File install.ps1'
That is, PowerShell will execute the following in a new window:
powershell.exe -File install.ps1
[1] Since you're not assigning the script block being created to a variable, it is implicitly output (printed to the display, in the absence of a redirection); a script block stringifies by its literal contents, excluding the enclosing { and }, so string installMS32Bit will print to the display.

Passing environment variables to a process in PowerShell 2.0 when the variable is changed multiple times

I'm having some issue when it comes to passing the corresponding environment variables to a process. Below you can see part of my code so that you can understand what I'm trying to do.
I have two EXE files that I need to run. The processes run some updates based on the location of the Environment variable %MainFiles%. When I run the code it seems the EXE files do not recognize the change. However, when i look under the Computer properties I do see that the variables are changed correctly.
Does anyone know how could I force the process to recognize the change?
Thanks
while ($i -lt $Size) {
if ($TempEnv[$i] -eq "Done"){
$ExitCode="Completed"
return
} else {
$Temp = $TempEnv[$i]
Write-Host ("Starting Update for " + $Temp) -foregroundcolor "Green"
[System.Environment]::SetEnvironmentVariable("MainFiles", "$Temp","Machine")
[System.Environment]::GetEnvironmentVariable("MainFiles","Machine")
Copy-Item $CopyInstallData -destination $Temp
$process = Start-Process XMLUpgrade.exe -WorkingDirectory "C:\Program Files\Dtm" -wait
$process = Start-Process Update.exe -WorkingDirectory "C:\Program Files\Dtm" -wait
.
.
.
This line makes the env var change permanent:
[System.Environment]::SetEnvironmentVariable("MainFiles", "$Temp","Machine")
Unfortunately PowerShell has already launched before you set this. Its env block is snapshotted at launch time. That environment is what the two spawned processes inherit.
To get the two processes to launch with the correct environment variable value do this first:
$env:MainFiles = $Temp

Powershell uninstall program with msiexec

I've run into a problem getting msiexec to remove java with Powershell. I've output my resultant command to the screen and pasted it into a batch file and it runs great. But when it's executed via Powershell it fails saying the "package cannot be found". Can anyone spot what I might be doing wrong? I've looked up and down google and tried a few different ways of executing the command w/o success and with the same result.
cls
$java = Get-WmiObject -Class win32_product | where { $_.Name -like "*Java*"}
$msiexec = "c:\windows\system32\msiexec.exe";
#$msiexecargs = '/x:"$app.LocalPackage" /qr'
$msiexecargs = '/uninstall "$app.IdentifyingNumber" /qr /norestart'
if ($java -ne $null)
{
foreach ($app in $java)
{
write-host $app.LocalPackage
write-host $app.IdentifyingNumber
#&cmd /c "msiexec /uninstall $app.IdentifyingNumber /passive"
#Start-Process -FilePath $msiexec -Arg $msiexecargs -Wait -Passthru
[Diagnostics.Process]::Start($msiexec, $msiexecargs);
}
}
else { Write-Host "nothing to see here..." }
Write-Host "check end"
The goal is to use the Windows 7 logon script to remove all versions of Java on end-user systems and then install the latest. I prefer to make it all Powershell, but if I can't get this working I'll just use a batch file hard coded with the uninstall GUID's
The write-host statements are all for the purpose of debugging, I'm just interested in the execution of msiexec in some variation of this format: msiexec /x {GUID} /passive /norestart
The error I get is:
"This installation package could not be opened. Verify that the package exists and that you can access it, or contact the application vendor to verify that this is a valid Windows Installer package."
I know it works on its own, just not in this script...so I'm thinking it's a syntax thing.
If you have any questions let me know.
First you have to know the difference between this:
"$app.IdentifyingNumber"
and this
"$($app.IdentifyingNumber)"
So I think you wanted to use the latter (the code is a little bit confusing because of the commented lines):
&cmd /c "msiexec /uninstall $($app.IdentifyingNumber) /passive"

How to reload user profile from script file in PowerShell

I want to reload my user profile from a script file. I thought that dot sourcing it from within the script file would do the trick, but it doesn't work:
# file.ps1
. $PROFILE
However, it does work if I dot source it from PowerShell's interpreter.
Why do I want to do this?
I run this script every time I update my profile and want to test it, so I'd like to avoid having to restart PowerShell to refresh the environment.
If you want to globally refresh your profile from a script, you will have to run that script "dot-sourced".
When you run your script, all the profile script runs in a "script" scope and will not modify your "global" scope.
In order for a script to modify your global scope, it needs to be "dot-source" or preceded with a period.
. ./yourrestartscript.ps1
where you have your profile script "dot-sourced" inside of "yourrestartscript.ps1". What you are actually doing is telling "yourrestartscript" to run in the current scope and inside that script, you are telling the $profile script to run in the script's scope. Since the script's scope is the global scope, any variables set or commands in your profile will happen in the global scope.
That doesn't buy you much advantage over running
. $profile
So, the approach that you marked as the answer may work inside the Powershell command prompt, but it doesn't work inside PowerShell ISE (which, to me, provides a superior PowerShell session) and probably won't work right in other PowerShell environments.
Here's a script that I have been using for a while, and it has worked very well for me in every environment. I simply put this function into my Profile.ps1 at ~\Documents\WindowsPowerShell, and whenever I want to reload my profile, I dot-source the function, i.e.
. Reload-Profile
Here's the function:
function Reload-Profile {
#(
$Profile.AllUsersAllHosts,
$Profile.AllUsersCurrentHost,
$Profile.CurrentUserAllHosts,
$Profile.CurrentUserCurrentHost
) | % {
if(Test-Path $_){
Write-Verbose "Running $_"
. $_
}
}
}
& $profile
works to reload the profile.
If your profile sets aliases or executes imports which fail then you will see errors because they were already set in the previous loading of the profile.
Why are you trying to do this?
Because it is likely to create duplicates (appends to $env:path) and problems with setting constant/readonly objects causing errors.
There was a thread on this topic recently on microsoft.public.windows.powershell.
If you are trying to reset the state of the session there is no way to do this, even using an inner scope ($host.EnterNestedPrompt()) because of the ability to set variables/aliases/... at "all scope".
I found this workaround:
#some-script.ps1
#restart profile (open new powershell session)
cmd.exe /c start powershell.exe -c { Set-Location $PWD } -NoExit
Stop-Process -Id $PID
A more elaborated version:
#publish.ps1
# Copy profile files to PowerShell user profile folder and restart PowerShell
# to reflect changes. Try to start from .lnk in the Start Menu or
# fallback to cmd.exe.
# We try the .lnk first because it can have environmental data attached
# to it like fonts, colors, etc.
[System.Reflection.Assembly]::LoadWithPartialName("System.Diagnostics")
$dest = Split-Path $PROFILE -Parent
Copy-Item "*.ps1" $dest -Confirm -Exclude "publish.ps1"
# 1) Get .lnk to PowerShell
# Locale's Start Menu name?...
$SM = [System.Environment+SpecialFolder]::StartMenu
$CurrentUserStartMenuPath = $([System.Environment]::GetFolderPath($SM))
$StartMenuName = Split-Path $CurrentUserStartMenuPath -Leaf
# Common Start Menu path?...
$CAD = [System.Environment+SpecialFolder]::CommonApplicationData
$allUsersPath = Split-Path $([System.Environment]::GetFolderPath($CAD)) -Parent
$AllUsersStartMenuPath = Join-Path $allUsersPath $StartMenuName
$PSLnkPath = #(Get-ChildItem $AllUsersStartMenuPath, $CurrentUserStartMenuPath `
-Recurse -Include "Windows PowerShell.lnk")
# 2) Restart...
# Is PowerShell available in PATH?
if ( Get-Command "powershell.exe" -ErrorAction SilentlyContinue ) {
if ($PSLnkPath) {
$pi = New-Object "System.Diagnostics.ProcessStartInfo"
$pi.FileName = $PSLnkPath[0]
$pi.UseShellExecute = $true
# See "powershell -help" for info on -Command
$pi.Arguments = "-NoExit -Command Set-Location $PWD"
[System.Diagnostics.Process]::Start($pi)
}
else {
# See "powershell -help" for info on -Command
cmd.exe /c start powershell.exe -Command { Set-Location $PWD } -NoExit
}
}
else {
Write-Host -ForegroundColor RED "Powershell not available in PATH."
}
# Let's clean up after ourselves...
Stop-Process -Id $PID
This is only a refinement of the two line script in guillermooo's answer above, which did not get the new PowerShell window into the correct directory for me. I believe this is because $PWD is evaluated in the new PowerShell window's context, which is not the value we want set-location to process.
function Restart-Ps {
$cline = "`"/c start powershell.exe -noexit -c `"Set-Location '{0}'" -f $PWD.path
cmd $cline
Stop-Process -Id $PID
}
By rights it shouldn't work, as the command line it spits out is malformed, but it seems to do the job and that's good enough for me.
since I stumbled onto this several years later, I thought to add that you can use the invocation operator: & to load your profile with the default variable to your profile: $profile.
so, if your session somehow fails to load your profile (happens to me with cmder/conemu) just type:
& $profile
I used this to troubleshoot what profile was taking forever to load.
Start Run:
powershell_ise -noprofile
Then i ran this:
function Reload-Profile {
#(
$Profile.AllUsersAllHosts,
$Profile.AllUsersCurrentHost,
$Profile.CurrentUserAllHosts,
$Profile.CurrentUserCurrentHost
) | % {
if(Test-Path $_){
Write-Verbose "Running $_"
$measure = Measure-Command {. $_}
"$($measure.TotalSeconds) for $_"
}
}
}
. Reload-Profile
Thank you #Winston Fassett for getting me closer to finding my issue.
Pseudo Alias (simulate keys)
If you just want a function to work like an alias in the console, just simulate the key presses to get around having to use the dot source.
# when "reload" is typed in the terminal, the profile is reloaded
# use sendkeys to send the enter key to the terminal
function reload {
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait(". $")
[System.Windows.Forms.SendKeys]::SendWait("PROFILE")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
}
screenshot of it working