Removal of Office - powershell

I'm currently trying to run a batch file as a startup script to detect and remove whatever version of office a user has installed and then to install Office 365. I have the install working however, when I attempted to uninstall Office 2013 I received the following error:
Input Error: Can not find script file "C:\Windows\OffScrub_O15msi.vbs"
The Offscrub file is in the same location as the script, is someone able to tell me why it's looking in C:\Windows for it?
Update
Please find my current script which now works for Office 2013, I previously added the line Remove2016Installs $true when using -Command to remove Office 2016, this worked. Since using -File to work around my initial problem I've been unable to get the script to remove Office 2016 and would like some advice on how to do this, I've read that whatever command is after -File needs to be the last which I believe might be why it's failing.
My full script is below:
start "----NOTICE----" cmd.exe /t:ec /Q /k "echo OFFICE 365 IS BEING INSTALLED. THIS WINDOW WILL CLOSE WHEN COMPLETE&&prompt $h"
#echo off
pushd "%~dp0"
powershell.exe -executionpolicy bypass -NoExit -File "Remove-PreviousOfficeInstalls.ps1"
popd
reg Query "HKLM\Hardware\Description\System\CentralProcessor\0" | find /i "x86" > NUL && set OS=32BIT || set OS=64BIT
if %OS%==32BIT "\\domain\SYSVOL\domain\Policies\{Policy Number}\Machine\Scripts\Startup\setup.exe" /configure "\\domain\SYSVOL\domain\Policies\{Policy Number}\Machine\Scripts\Startup\configuration-Office365-x86.xml"
if %OS%==64BIT "\\domain\SYSVOL\domain\Policies\{Policy Number}\Machine\Scripts\Startup\setup.exe" /configure "\\domain\SYSVOL\domain\Policies\{Policy Number}\Machine\Scripts\Startup\configuration-Office365-x64.xml"
taskkill /IM cmd.exe /FI "WINDOWTITLE EQ ----NOTICE----"
taskkill /IM cmd.exe /FI "WINDOWTITLE EQ Administrator: ----NOTICE----"
echo %date% %time% Setup ended with error code %errorlevel%. >> %LogLocation%\%computername%.txt
Update Finished
There's a line that calls the Powershell script Remove-PreviousOfficeInstalls, this is a file from GitHub that is very popular for the removal of whichever Office version you have installed.
I can run this command if say I copy these files to the desktop and amend the locations in the scripts, I'm not sure what this reference to C:\Windows is though when run from \domain\SYSVOL\domain\Policies{Policy Number}\Machine\Scripts\Startup\?

If you run a default instances of PowerShell it always starts in a certain directory. It depends on how and who it is started by. For instance an administrative PowerShell usually starts in C:\Windows\System32. If you use any paths that are not absolute they're applied relative to this directory.
To work around this you need to change the directory it's using. For instance by using cd to change the directory. My guess would be that your script Remove-PreviousOfficeInstalls.ps1 contains a relative call to the VBS.
An easy fix would be to run a script block instead of a single command and just cd to \\domain\SYSVOL\domain\Policies\'{Policy Number}'\Machine\Scripts\Startup\ prior to running the ps1.
The PowerShell help you can view by following [this] link or running powershell -h has the following information in regards to using the -Command switch.
...
Script blocks must be enclosed in braces ({}). You can specify a script block only when running PowerShell.exe in PowerShell. If you want to use a script block when running from another shell you must use the format:
"& {}"
...
The other important parameter for your use case is -File.
Runs the specified script in the local scope ("dot-sourced"), so that the functions and variables that the script creates are available in the current session. Enter the script file path and any parameters.
...
Your batch contains the following line:
powershell.exe -executionpolicy bypass -Command "\\domain\SYSVOL\domain\Policies\'{Policy Number}'\Machine\Scripts\Startup\Remove-PreviousOfficeInstalls.ps1 -Remove2016Installs $true"
What you do is run a single command to invoke a script with a parameter. The problems is that said script checks its locations based on certain function and with your invocation that location is wrongly detected.
There are multiple ways to fix this. One would be to change the directory before invoking the script. To do this you'd need to use a script block as the parameter for -Command. An example for this would be:
powershell.exe -Command "& {Write-Output 'Hello'; Write-Output 'World';}"
As you can see there are two independent Write-Output commands being run. You'd change this to a cd \\domain\SYSVOL\domain\Policies\'{Policy Number}'\Machine\Scripts\Startup\ and the invocation of your script. As a bonus you wouldn't need to put the whole path in front of the script anymore.
The other option would be to run powershell -File with your current invocation of the script. That should also mean that the script is read from the file and the corresponding parameters are populated accordingly.
If neither of these options work you will have to check what $PSScriptRoot is being populated with and/or what the return of (Get-Item -Path ".\").FullName is as those are the two commands used to determine the location of the script that's being executed. To do this you could use a script block.

thanks for your help regarding this. My resolution was to use the following bat command:
`start "----NOTICE----" cmd.exe /t:ec /Q /k "echo OFFICE 365 IS BEING INSTALLED. THIS WINDOW WILL CLOSE WHEN COMPLETE&&prompt $h"`
#echo off
pushd "%~dp0"
powershell.exe -executionpolicy bypass -File Remove-PreviousOfficeInstalls.ps1 -Remove2016Installs
popd
reg Query "HKLM\Hardware\Description\System\CentralProcessor\0" | find /i "x86" > NUL && set OS=32BIT || set OS=64BIT
if %OS%==32BIT "\\Server\Folder\Folder\setup.exe" /configure "\\Server\Folder\Folder\configuration-Office365-x86.xml"
if %OS%==64BIT "\\Server\Folder\Folder\setup.exe" /configure "\\Server\Folder\Folder\configuration-Office365-x64.xml"
taskkill /IM cmd.exe /FI "WINDOWTITLE EQ ----NOTICE----"
taskkill /IM cmd.exe /FI "WINDOWTITLE EQ Administrator: ----NOTICE----"
echo %date% %time% Setup ended with error code %errorlevel%. >> %LogLocation%\%computername%.txt
I had to amend the Remove-PreviousOfficeInstalls powershell script to include the switch command:
[Parameter(ValueFromPipelineByPropertyName=$true)]
[switch]$Remove2016Installs = $false,
This then did exactly what I was after, it detected the current version of Office, removed it and installed the correct bit version of Office 365 for that PC\Laptop.
Thanks for all your help

Related

Trying to run a headless executable command through Powershell that works on cmd line

I am trying to run an executable through powershell to run headless, to install a program onto a VM/LocalHost machine. I can get the wizard to open, but for whatever reason I cannot get it to run headless. Here is the cmd line that I run that works:
start /WAIT setup.exe /clone_wait /S /v" /qn"
This is my attempts in powershell
Start-Process .\setup.exe /S -Wait -PassThru
Start-Process .\setup.exe /S /v /qn -Wait -PassThru
Start-Process setup.exe -ArgumentList '/clone_wait /S /v /qn' -Wait
In the cmd line instance the application installs without issue - in the powershell instance the wizard opens and is on the first "Next" prompt. Any help would be appreciated!
I also attempted to add the additional parameters "/v" and "/qn" which return an error : Start-Process : A positional parameter cannot be found that accepts argument '/v'
The bottom attempt runs but it's not waiting for the installation to complete
You may be overthinking it. Remember that PowerShell is a shell. One of the purposes of a shell is to run commands that you type.
Thus: You don't need Start-Process. Just type the command to run and press Enter.
PS C:\> .\setup.exe /clone_wait /S /v /qn
Now if the executable (or script) you want to run contains spaces in the path or name, then use the call/invocation operator (&) and specify the quotes; for example:
PS C:\> & "\package files\setup.exe" /clone_wait /S /v /qn
(This behavior is the same no matter whether you are at the PowerShell prompt or if you put the command in a script.)
This worked for me. You need to quote the whole argumentlist, plus embed double quotes to pass what you want to /v.
start-process -wait SetupStata16.exe -ArgumentList '/s /v"/qb ADDLOCAL=core,StataMP64"'
Running the command normally and then using wait-process after might be a simpler alternative, if you're sure there's only one process with that name:
notepad
wait-process notepad
To follow-up to all that you have been given thus far. Running executables via PowerShell is a well-documented use case.
PowerShell: Running Executables
Solve Problems with External Command Lines in PowerShell
Top 5 tips for running external commands in Powershell
Using Windows PowerShell to run old command-line tools (and their
weirdest parameters)
So, from the first link provides more validation of what you've been given.
5. The Call Operator &
Why: Used to treat a string as a SINGLE command. Useful for dealing with spaces.
In PowerShell V2.0, if you are running 7z.exe (7-Zip.exe) or another command that starts with a number, you have to use the command invocation operator &.
The PowerShell V3.0 parser do it now smarter, in this case you don’t need the & anymore.
Details: Runs a command, script, or script block. The call operator, also known as the "invocation operator," lets you run commands that are stored in variables and represented by strings. Because the call operator does not parse the command, it cannot interpret command parameters
Example:
& 'C:\Program Files\Windows Media Player\wmplayer.exe' "c:\videos\my home video.avi" /fullscreen
Things can get tricky when an external command has a lot of parameters or there are spaces in the arguments or paths!
With spaces you have to nest Quotation marks and the result it is not always clear!
In this case it is better to separate everything like so:
$CMD = 'SuperApp.exe'
$arg1 = 'filename1'
$arg2 = '-someswitch'
$arg3 = 'C:\documents and settings\user\desktop\some other file.txt'
$arg4 = '-yetanotherswitch'
& $CMD $arg1 $arg2 $arg3 $arg4
# or same like that:
$AllArgs = #('filename1', '-someswitch', 'C:\documents and settings\user\desktop\some other file.txt', '-yetanotherswitch')
& 'SuperApp.exe' $AllArgs
6. cmd /c - Using the old cmd shell
** This method should no longer be used with V3
Why: Bypasses PowerShell and runs the command from a cmd shell. Often times used with a DIR which runs faster in the cmd shell than in PowerShell (NOTE: This was an issue with PowerShell v2 and its use of .Net 2.0, this is not an issue with V3).
Details: Opens a CMD prompt from within powershell and then executes the command and returns the text of that command. The /c tells CMD that it should terminate after the command has completed. There is little to no reason to use this with V3.
Example:
#runs DIR from a cmd shell, DIR in PowerShell is an alias to GCI. This will return the directory listing as a string but returns much faster than a GCI
cmd /c dir c:\windows
7. Start-Process (start/saps)
Why: Starts a process and returns the .Net process object Jump if -PassThru is provided. It also allows you to control the environment in which the process is started (user profile, output redirection etc). You can also use the Verb parameter (right click on a file, that list of actions) so that you can, for example, play a wav file.
Details: Executes a program returning the process object of the application. Allows you to control the action on a file (verb mentioned above) and control the environment in which the app is run. You also have the ability to wait on the process to end. You can also subscribe to the processes Exited event.
Example:
#starts a process, waits for it to finish and then checks the exit code.
$p = Start-Process ping -ArgumentList "invalidhost" -wait -NoNewWindow -PassThru
$p.HasExited
$p.ExitCode
#to find available Verbs use the following code.
$startExe = new-object System.Diagnostics.ProcessStartInfo -args PowerShell.exe
$startExe.verbs

cmd.exe throws error "& was unexpected at this time."

so my problem is that when i just ran cmd.exe in terminal, i get "& was unexpected at this time." Error at the end - looks like this
So the problem is that i'm getting erros in Unity 3D when it wants to run the unity_csc.bat file and compile solution. These errors are exactly the same as the one when i just run cmd.exe - therefore i suspect its not an Unity3D based problem (if you want you can check the Unity3D specific thread here https://forum.unity.com/threads/2-empty-errors-in-console-was-unexpected-at-this-time.799110/ )
Does anyone know why this might be happening ? This also happens when i try to run a .bat file - which I suspect is why i cant compile Unity project
I'm running Windows 10 with all of the latest updates
EDIT:
Since cmd /d does not throw the error, might there be some problem with this registry record ?
In User Folder i do have this Autorun record
#mode 20,5 & tasklist /FI "IMAGENAME eq SoundMixer.exe" 2>NUL | find /I /N "SoundMixer.exe">NUL && exit & if exist " ( start /MIN "" " & tasklist /FI "IMAGENAME eq explorer.exe" 2>NUL | find /I /N "explorer.exe">NUL && exit & explorer.exe & exit ) else ( tasklist /FI "IMAGENAME eq explorer.exe" 2>NUL | find /I /N "explorer.exe">NUL && exit & explorer.exe & exit )
Stephan has provided the crucial pointer:
It sounds like you have a broken autorun command defined for cmd.exe; that is, your registry defines a command that is automatically executed whenever you call cmd.exe, and that command causes the syntax error you're seeing.
Note that such commands are executed irrespective of whether you open an interactive cmd session or invoke via a batch file or pass a command to cmd with /C.
Passing /D to cmd bypasses any autorun commands.
There are two locations in the registry where such a command can be defined, one at the local-machine level (which applies to all users), HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor, and another for the current user only, HKEY_CURRENT_USER\Software\Microsoft\Command Processor, in a value named AutoRun.
If commands are defined in both locations, the HKEY_LOCAL_MACHINE's commands run first.
To list any defined autorun commands:
Get-ItemProperty -ea Ignore ('HKCU:', 'HKLM:' -replace '$', '\Software\Microsoft\Command Processor') AutoRun
You can use the following PowerShell snippet to remove autorun commands from both locations, but note that you'll have to run it with elevation (as administrator), if a local-machine value is present:
Get-ItemProperty -ea Ignore ('HKCU:', 'HKLM:' -replace '$', '\Software\Microsoft\Command Processor') AutoRun |
Remove-ItemProperty -Name AutoRun -WhatIf
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf, once you're sure the operation will do what you want.

Running CMD command in PowerShell

I am having a bunch of issues with getting a PowerShell command to run. All it is doing is running a command that would be run in a CMD prompt window.
Here is the command:
"C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\i386\CmRcViewer.exe" PCNAME
I have tried the following with no success (I have tried many iterations of this to try and get one that works. Syntax is probably all screwed up):
$TEXT = $textbox.Text #$textbox is where the user enters the PC name.
$CMDCOMMAND = "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\i386\CmRcViewer.exe"
Start-Process '"$CMDCOMMAND" $TEXT'
#iex -Command ('"C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\i386\CmRcViewer.exe"' $TEXT)
The command will just open SCCM remote connection window to the computer the user specifies in the text box.
Try this:
& "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\i386\CmRcViewer.exe" PCNAME
To PowerShell a string "..." is just a string and PowerShell evaluates it by echoing it to the screen. To get PowerShell to execute the command whose name is in a string, you use the call operator &.
To run or convert batch files externally from PowerShell (particularly if you wish to sign all your scheduled task scripts with a certificate) I simply create a PowerShell script, e.g. deletefolders.ps1.
Input the following into the script:
cmd.exe /c "rd /s /q C:\#TEMP\test1"
cmd.exe /c "rd /s /q C:\#TEMP\test2"
cmd.exe /c "rd /s /q C:\#TEMP\test3"
*Each command needs to be put on a new line calling cmd.exe again.
This script can now be signed and run from PowerShell outputting the commands to command prompt / cmd directly.
It is a much safer way than running batch files!
One solution would be to pipe your command from PowerShell to CMD. Running the following command will pipe the notepad.exe command over to CMD, which will then open the Notepad application.
PS C:\> "notepad.exe" | cmd
Once the command has run in CMD, you will be returned to a PowerShell prompt, and can continue running your PowerShell script.
Edits
CMD's Startup Message is Shown
As mklement0 points out, this method shows CMD's startup message. If you were to copy the output using the method above into another terminal, the startup message will be copied along with it.
For those who may need this info:
I figured out that you can pretty much run a command that's in your PATH from a PS script, and it should work.
Sometimes you may have to pre-launch this command with cmd.exe /c
Examples
Calling git from a PS script
I had to repackage a git client wrapped in Chocolatey (for those who may not know, it's a package manager for Windows) which massively uses PS scripts.
I found out that, once git is in the PATH, commands like
$ca_bundle = git config --get http.sslCAInfo
will store the location of git crt file in $ca_bundle variable.
Looking for an App
Another example that is a combination of the present SO post and this SO post is the use of where command
$java_exe = cmd.exe /c where java
will store the location of java.exe file in $java_exe variable.
You must use the Invoke-Command cmdlet to launch this external program. Normally it works without an effort.
If you need more than one command you should use the Invoke-Expression cmdlet with the -scriptblock option.

How to change the cmd's current-dir using PowerShell?

I read some file using PowerShell, and change current dir accordingly, but all I can do is change the current PowerShell's current dir, not the caller's dir (the cmd.exe environment that called that ps1 file). Things I tried:
powershell ch-dir.ps1 | cd
(won't work, obviously, since CD is internal command)
powershell cd $myDir
(changes current dir in PowerShell, but when script exits, the cmd environment still in original dir)
I really hope I won't need to find the script's caller process (the cmd), and make a change in it's cur-dir by-force... (or even worse - to save the dir I want in some env-var and then cd %my_var% since it would require two lines of command)
I'm not sure if this meets your needs, but if you set it up so that the only output from your powershell script is your desired new working directory, you could do this:
c:\>for /F %i IN ('powershell -noprofile -command "write-output 'c:\users'" ') DO #cd %i
c:\Users>
The cmd prompt is hosting your powershell session, unless you can figure out a way to return an exit code to the prompt that will (on exit code 99999) change directory to (predefined values, switch?). As far as powershell is concerned they're different processes.
Heres a good example for you to try:
Open a cmd prompt.
Open task manager, find cmd.exe
In your cmd prompt type Powershell
View powershell as a different process (check the PID.)
End the powershell process. Watch what happens.
Alternatively, if you need something run from cmd in a specific directory based on logic in your powershell script, you can invoke it with a cmd /c from within Powershell.

Windows batch file does not wait for commands to complete

I have a batch file, which exists as soon as start it (run as admin) and does not execute commands that are in it, but if I specify it at the command-line, it runs fine and executes all commands.
Here's what's in it:
start /wait msiexec /x SetupServices.msi /qn /l* "SetupServices.uninstall.log"
start /wait msiexec /i SetupServices.msi /qn /l* "SetupServices.install.log"
(Corrected answer)
First, if you start .exe files in a batch, it is safer, to prefix it with "call".
Sometimes this is necessary to assure, that the batch is waiting to complete.
Using "start" is another possibility, but for this simple usecase not necessary.
You write that the commands are not executed. So, obviously, you have another problem, instead of the "does not wait.. to complete" problem.
Taking a look of your newly provided example, this is the case. In admin mode, you have to provide a full path. With my small trick below ("%~dp0", including already backslash), you can still use the current directory in batchfiles.
Most times, if such a problem occurs with admin rights, this is a problem of the "current directory" path. A batch file with admin rights is not using it in the same way as we were used to, it does not start in it's own directory (but in System32 mostly). Not relying on the CD is an important matter of writing bullet-proof batch files.
A good example batch , combining other answers here, and solving a number of possible problems in your case is:
call msiexec /i "%~dp0MySetup.msi" /qb /L*v "%~dp0MySetup.log"
echo Returncode: %ERRORLEVEL%
pause
It uses the current directory correctly, and assumes an install commandline including a logfile (works only, if you have write access in the current directory, if not specify a path for the logfile with write access like "%TEMP%\MySetup.log".
Attention: Remember to really start the batch file with admin rights (right mouse menu or opening an admin command shell before:)
Coming back to this question I think the "correct way" to do it is via PowerShell
Start-Process -Wait -FilePath msiexec -ArgumentList /i, "setup.msi", /qn, /l*v, "install.log"
Or just prefix it with PowerShell; to invoke directly from CMD
PowerShell; Start-Process -Wait -FilePath msiexec -ArgumentList /i, "setup.msi", /qn, /l*v, "install.log"
No hacks and no tricks :-)
Try taking the start /wait out for the msiexec lines, if that doesn't work create two more bat files one called uninstall.bat the other install.bat and use call to execute them in series.
It goes a little beyond the question, but an extension to my answer concerning handling the current directory: Here is my recommended beginning for every batch file conserving it's own path. The specialty is that it also works for UNC paths. "Pushd" automatically creates a new drive letter, if necessary (assumed, you have one free of 26). Of course you can use "popd" also at the end of the batch file instead immediately, but stable commands do not rely on the current directory as I mentioned, so it is better to always provide full paths.
#echo off
cls
pushd %~dp0
popd
set MYDIR=%CD%
echo Directory of this batch fil: %MYDIR%
You can then add the msi lines from the other answer like this:
call msiexec /i "%MYDIR%\MySetup.msi" /qb /L*v "%MYDIR%\MySetup.log"
echo Returncode: %ERRORLEVEL%
pause
(Remark: For the logfile path of course you are free, it has not to be necessarily in the same directory. But good for testing/debugging. In every case you have to have write access to the directory/file you are giving MSI with.)
While for normal MSI files it is not always necessary to start the batch with admin rights from the beginning, this technique is by far more safe (to start the MSI like already with admin rights) than too rely on the MSI UAC coming later (maybe).
And it works with msiexec ... /qn too, which is important (silent installs).
Add pause statement to the end of the batch, this will prevent the console window from closing and you will be able to see error messages if any. Errors could be the reason why it exits without actually running anything. What kind of error it may be? SetupServices.msi is not found — that's what comes to my mind.
Needs the "Window Title" if you use Parameter
start /wait "Window Title" "MsiExec.exe" /i SetupServices.msi /qn /l* SetupServices.uninstall.log
start /wait "Window Title" "MsiExec.exe" /i SetupServices.msi /qn /l* SetupServices.install.log