Access system32 from VisualStudio 2019 build script - visual-studio

In a post build step i want check if OpenSSH.Client and OpenSSH.Server is installed and install it if it is not there. Checking the installed features with Powershell needs administrative privileges.
Therefore i test the existence of the relevant OpenSSH commands with the following code (extract of long script)
SET /a res=0
SET /a three=3
%windir%\system32\OpenSSH\ssh-keygen.exe /?
echo Errorlevel %ERRORLEVEL%
IF %ERRORLEVEL%==%three% (SET /a res=%res%+1)
%windir%\system32\OpenSSH\ssh-keyscan.exe /?
echo Errorlevel %ERRORLEVEL%
IF %ERRORLEVEL%==%three% (SET /a res=%res%+1)
SET /a check=0
IF %res% == %check% (
echo already installed
goto skipopenSSH
)
echo installation
:skipopenSSH
By checking the existence of the commands no admin privileges are necessary for the check so a normal build will not cause a administrative popup.
On cmd.exe it works fine, but as a post build step in Visual Studio both commands in %windir%\systrem32\OpenSSH are not found, although the variable is expanded to the same c:\Windows\System32\OpenSSH\*.exe as if executed on commandline.
After trying the different find mechanisms which all fail i made a test batch file C:\Windows\System32\OpenSSH\ssh-keyscan.exe /?
which leads to a file not found error if executed as a post build step. So the real question should be: Modifies the visual studio build step commandline the path?

The directory OpenSSH exists in directory %SystemRoot%\System32 with the files ssh-keygen.exe and ssh-keyscan.exe depending on version of Windows 10. The directory %SystemRoot%\System32 is for 64-bit applications on 64-bit Windows. But Visual Studio is a 32-bit application and for that reason 32-bit Windows command processor in directory %SystemRoot%\SysWOW64 is executed to process the batch file with the commands to execute as post build step.
Microsoft documented with WOW64 Implementation Details, File System Redirector and Registry Keys Affected by WOW64 and other documentation pages how Windows on Windows 64 works.
All file system accesses to %windir%\system32\OpenSSH in the batch file processed by 32-bit %SystemRoot%\SysWOW64\cmd.exe being started by 32-bit Visual Studio results in the approach to access %SystemRoot%\SysWOW64\OpenSSH which does not exist at all. There is no subdirectory OpenSSH in Windows system directory for 32-bit applications.
One solution would be using the following code for the batch file executed as post build step.
rem This simple check is for 32-bit Windows and for 64-bit Windows with batch
rem file executed in 64-bit environment by 64-bit Windows command processor.
set FolderSSH=%SystemRoot%\System32\OpenSSH
if exist %FolderSSH%\ssh-keygen.exe if exist %FolderSSH%\ssh-keyscan.exe goto UseOpenSSH
rem This check is for 64-bit Windows with batch file executed
rem in 32-bit environment by 32-bit Windows command processor.
if exist %SystemRoot%\Sysnative\cmd.exe set FolderSSH=%SystemRoot%\Sysnative\OpenSSH
if exist %FolderSSH%\ssh-keygen.exe if exist %FolderSSH%\ssh-keyscan.exe goto UseOpenSSH
rem Search entire system drive on machines without Windows 10 or with older
rem versions of Windows 10 with OpenSSH not installed at all by default.
for /F "delims=" %%I in ('%SystemRoot%\System32\where.exe /R %SystemDrive%\ ssh-keygen.exe 2^>nul') do (
if exist "%%~dpIssh-keyscan.exe" for %%J in ("%%~dpI.") do set "FolderSSH=%%~fJ" & goto UseOpenSSH
)
echo ERROR: ssh-keygen.exe AND ssh-keyscan.exe not found.
rem More code to handle this use case.
goto :EOF
:UseOpenSSH
echo Found ssh-keygen and ssh-keyscan in: "%FolderSSH%"
rem More code to handle this use case with existing SSH tools.
The remarks explain most of the code. The inner FOR loop is used to get the full qualified name of the directory containing ssh-keygen.exe and ssh-keyscan.exe without backslash at end of the folder path to have FolderSSH defined always without a backlash at end for further usage in the batch file.
Please note that it is safe to use %FolderSSH%\ssh-keygen.exe and %FolderSSH%\ssh-keyscan.exe without surrounding " at beginning of the batch script as it is impossible that %FolderSSH% expands to a folder path containing a space or one of these characters &()[]{}^=;!'+,`~.
But "%FolderSSH%\ssh-keygen.exe" and "%FolderSSH%\ssh-keyscan.exe" must be used on the command lines below the label UseOpenSSH because it could happen that WHERE was used to find the two executables anywhere on system drive and for that reason %FolderSSH% could expand now to a folder path containing a space or a character with a special meaning for Windows command processor outside a double quoted argument string.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
echo /?
for /?
goto /?
if /?
rem /?
set /?
where /?
Read also the Microsoft article about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded where command line with using a separate command process started in background with %ComSpec% /c and the command line between ' appended as additional arguments on which 2^>nul became already 2>nul.

The following idea also uses the where.exe command, as mentioned in the comments. This one will search the system drive for ssh-keyscan.exe, and if found will ensure that ssh-keygen.exe is also located there. If both are located in the same place, then it is considered as already installed:
#Set "dirOpenSSH="
#For /F Delims^= %%G In (
'%__AppDir__%where.exe /R %SystemDrive%\ ssh-keyscan.exe 2^>NUL'
)Do #For /F Delims^= %%H In (
'%__AppDir__%where.exe "%%~dpG.":ssh-keygen.exe 2^>NUL'
)Do #Set "dirOpenSSH=%%~dpH"
#If Defined dirOpenSSH (
Echo OpenSSH is available at %dirOpenSSH%.
%__AppDir__%timeout.exe 3 /NoBreak>NUL
GoTo skipopenSSH
)
#Echo OpenSSH is not available, beginning installation.
#%__AppDir__%timeout.exe 3 >NUL
:skipopenSSH
Please note that where.exe and timeout.exe require a minimum OS version of Windows NT 6.x
As your application is currently a 32-bit one, the console session it is using is not accessing the 64-bit locations. To fix that you could replace %__AppDir__%where.exe, with %SystemRoot%\SysNative\where.exe
It is probably worth mentioning that in the versions of windows-10 with OpenSSH part of the base OS, the location, %SYSTEMROOT%\System32\OpenSSH\, should exist in %PATH%, so you should be able to find your files directly using them:
%__AppDir__%where.exe ssh-keygen.exe
%__AppDir__%where.exe ssh-keyscan.exe
For %I In (ssh-keygen.exe) Do #Echo %~$PATH:I
For %I In (ssh-keyscan.exe) Do #Echo %~$PATH:I
I have found on the systems which have upgraded from an earlier version of Windows, that many of the default locations using C:\Windows were replicated, but using %SYSTEMROOT%/%SystemRoot% instead.
In that state, the Where command and For's %~$PATH: failed to locate some items which did exist and should have been found.
Removing the duplicated items, I removed those which were carried over, (those not using variables), although removing either format seems to have worked fine.
Both where and %~$PATH: were then functioning as intended.

Related

Self-extracting executable do not recognize an existing command in System32

I'm developing a stack of Windows Batch scripts (.bat) which are subsequently converted to self-extracting executabled.
Currently I'm facing a really strange problem. I create this self-extracting executable, which, after extracting all the files, will launch the main wrapper, (SETUP.bat). The whole stack of the scripts gets running and everything goes right until some point.
One part of the stack has to commit some files with UWFMGR, and I have a loop for like this:
SETLOCAL EnableDelayedExpansion
FOR /F usebackq "tokens=*" %%F IN ("DIR /S /B /A-D") DO (
uwfmgr file commit "%%F"
)
If I run the executable, the execution of the stack does not recognize the command UWFMGR
I stopped the execution of the script, and went with the same CMD, (which it's launched by the executable), to the System32 folder to check if the UWFMGR is there. I did a DIR "uwfmgr.exe", and it's not there. But if I go with another CMD (a new one) the command is there. I even went with a file explorer to verify it and it's completely there but somehow the CMD which it's being launched by the executable cannot recognize it.
I have tried to specify the whole path in the loop like this "C:\Windows\System32\UWFMGR.exe" and it doesn't even work.
The funny thing is that if run this script it works and commits all the files required.
SETLOCAL EnableDelayedExpansion
FOR /F usebackq "tokens=*" %%F IN ("DIR /S /B /A-D") DO (
uwfmgr file commit "%%F"
)
Does anyone have any idea of why this is happening?
Check the output of the command and the errorlevel, it's 9009 (the file/command does not exist)
Do an echo to check the command I'm passing through the loop, the command is OK
I expect the executable to do the whole stack and commit the files but the executable does not recognize UWFMGR command. I use other commands in my stack and I didn't have any problem.
64-bit Windows has two system32 folders. Windows\system32 is the real folder used by 64-bit applications. 32-bit applications are automatically redirected to Windows\Syswow64 when they access system32. There is a backdoor you can use to access the real system32 folder.
You should use if exist and set to find and store a usable path:
#echo off
set app=%windir%\system32\UWFMGR.exe
set appalt=%windir%\sysnative\UWFMGR.exe
if not exist "%app%" if exist "%appalt%" set app=%appalt%
echo.I will use %app%
UWFMGR seems to be an optional feature and is probably not present by default on most machines.
I solved it after finding out the exe was created as 32bits. I had to create as 64 bits in order to make it work. Now the executable is able to use the UWFMGR.
Thank you everyone for your answers and have a nice day.
I'm using this 7sfx module, just in case anyone's wondering: "7zsd_All_x64.sfx"

Start a command and if it doesn't work start another

I want to start acrobat from my software.
At this moment I'm using the following command:
cmd /c start AcroRd32.exe /t filename
But now acrobat sometimes updates to 64 bit version and then AcroRd32.exe doesn't exists anymore.
So we have to start acrobat.exe instead:
cmd /c start acrobat.exe /t filename
But we are working with a lot of clients with different computers and maybe different versions of acrobat. That is also why we don't specify an install path.
So is there a way to say in one command line entry (not a script) to start AcroRd32.exe and if this doesn't work start immediately acrobat.exe instead?
So something like:
cmd /c start AcroRd32.exe /t filename | if not 1 is ok then cmd /c start acrobat.exe /t filename
#ECHO OFF
SETLOCAL
FOR /f "delims=" %%b IN ('where autoruns64.exe autorunsc64.exe 2^>nul') DO CMD /c START "" "%%b" &GOTO done
ECHO NOT found!
:done
GOTO :eof
Note: START command : extra pair of quotes. This sets the title of the STARTed session, otherwise the first "quoted string" is used.
"%%b" is used in case the path to the executable (I used autoruns) contains a space.
The first argument to where is the preferred target.
where examines the path for filenames matching its arguments and lists them. The for /f reads the list and on the first match found, executes the START. If no match is found,an error message is produced by WHERE, which is suppressed by the 2^>nul.
Edit: one-line version
FOR /f "delims=" %b IN ('where autoruns64.exe autorunsc64.exe 2^>nul') DO CMD /c START "" "%b" &exit
Posted as a batch file because it's easier to re-run during testing.
WHERE locates files on the PATH and lists them
START locates files on the PATH and executes the first one found.
The PATH is the list of directories that will be examined by START, in the order in which they will be searched for the required executable, so WHERE examines that list of directories for the target executable, it does not search all of the drives in the hope of finding it.
Traditionally even on 64bit it was still Acrord32 for the free reader to avoid such dual name problems / complication and the 32bit or 64bit full product was start Acrobat.exe
However Adobe did not help by using version specifics within the registry so a query there would generally produce differing results ! Microsoft start adding enhancements as to where or how simple launchers may be deployed so the location now gets cloudier (pun :-)
However the traditional ask the registry works best except for my portable
version (where /r h:\ acrord32.exe is very slow on a TB drive and it was not on that one anyway, it was on a network drive!).
Simplest way I can think of via cmd to find an installed copy is:-
cmd /v:on /c "where acro*.exe>temp.txt&&set /p acroexe=<temp.txt&&if exist !acroexe! (!acroexe! /t %filename%)"
there is little use of errorlevel since the fail will simply report
> INFO: Could not find files for the given pattern(s).
Priority will be the first match within %path%
You also need to consider where temp.txt will be written perhaps "%tmp%\delme.txt"
Potential "Gotcha" is if first match finds something like AcrobatUpdater.exe
To avoid random acro's best use
cmd /v:on /c "where /f acrord32.exe acrobat.exe>temp.txt&&set /p acroexe=<temp.txt&&if exist !acroexe! (!acroexe! /t %filename%)"
actually since all you desire is start one or the other then
start "Reader" acrord32.exe /t "filename.pdf" "printer name"||start "Editor" acrobat.exe /t "filename.pdf" "printer name"
The downside to this last method is Windows will raise a GUI error if acrord32 cannot "start" Thus gui warning needs to be dismissed before the second option can be run and start. In that case START cannot be told to be silent on error.
Finally make your choice from above or use a hybrid approach
where /f acrord32.exe... || start acrobat.exe.. will not warn user on first fail but only on second if both missing.

download a file from a website with batch

i am attempting to create an automation script to install and update the mine craft editor MCEDIT on windows. here is what i have so far
#echo off
REM finds the architecture of the windows installation.
reg Query "HKLM\Hardware\Description\System\CentralProcessor\0" | find /i "x86" > NUL && set arc=32BIT || set arc=64BIT
if %arc%==32BIT GOTO 32
if %arc%==64BIT GOTO 64
REM placeholder for testing.
set version=1.5.3.0
:32
REM attepts to use bitsadmin to download file but fails.
bitsadmin.exe /transfer "JobName" https://github.com/Khroki/MCEdit-Unified/releases/download/^%version%/MCEdit.^%version%.Win.32bit.exe .\install.exe
:64
REM another attempt at bitsadmin that also fails
bitsadmin.exe /transfer "test" /download https://github.com/Khroki/MCEdit-Unified/releases/download/^%version%/MCEdit.^%version%.Win.64bit.exe .\install.exe
REM unzips the auto extractor to current location
install.exe /s /d %cd%
:end
echo end
pause
so the first issue i am having is that.
bitsadmin.exe /transfer "JobName" https://github.com/Khroki/MCEdit-Unified/releases/download/^%version%/MCEdit.^%version%.Win.32bit.exe .\install.exe
always fails, it is also depreciated and soon to be dropped from windows entirly. i am looking for a native solution that would not require a user to download anything in additon to this script to complete the task.
The second issue i am having is that i have no way to get the current version. the files are always stored at
https://github.com/Khroki/MCEdit-Unified/releases/download/(version_number)/mcedit.(version_number).exe
So i need a way to test for the newest version on the server or a way to print a text file containing the directories and do it locally.

How to test if an executable exists in the %PATH% from a windows batch file?

I'm looking for a simple way to test if an executable exists in the PATH environment variable from a Windows batch file.
Usage of external tools not provided by the OS is not allowed. The minimal Windows version required is Windows XP.
Windows Vista and later versions ship with a program called where.exe that searches for programs in the path. It works like this:
D:\>where notepad
C:\Windows\System32\notepad.exe
C:\Windows\notepad.exe
D:\>where where
C:\Windows\System32\where.exe
For use in a batch file you can use the /q switch, which just sets ERRORLEVEL and doesn't produce any output.
where /q myapplication
IF ERRORLEVEL 1 (
ECHO The application is missing. Ensure it is installed and placed in your PATH.
EXIT /B
) ELSE (
ECHO Application exists. Let's go!
)
Or a simple (but less readable) shorthand version that prints the message and exits your app:
where /q myapplication || ECHO Cound not find app. && EXIT /B
for %%X in (myExecutable.exe) do (set FOUND=%%~$PATH:X)
if defined FOUND ...
If you need this for different extensions, just iterate over PATHEXT:
set FOUND=
for %%e in (%PATHEXT%) do (
for %%X in (myExecutable%%e) do (
if not defined FOUND (
set FOUND=%%~$PATH:X
)
)
)
Could be that where also exists already on legacy Windows versions, but I don't have access to one, so I cannot tell. On my machine the following also works:
where myExecutable
and returns with a non-zero exit code if it couldn't be found. In a batch you probably also want to redirect output to NUL, though.
Keep in mind
Parsing in batch (.bat) files and on the command line differs (because batch files have %0–%9), so you have to double the % there. On the command line this isn't necessary, so for variables are just %X.
Here is a simple solution that attempts to run the application and handles any error afterwards.
file.exe /? 2> NUL
IF NOT %ERRORLEVEL%==9009 ECHO file.exe exists in path
Error code 9009 usually means file not found.
The only downside is that file.exe is actually executed if found (which in some cases is not desiderable).
This can be accomplished via parameter substitution.
%~$PATH:1
This returns the full path of the executable filename in %1, else an empty string.
This does not work with user-defined variables. So if the executable filename is not a parameter to your script, then you need a subroutine. For example:
call :s_which app.exe
if not "%_path%" == "" (
"%_path%"
)
goto :eof
:s_which
setlocal
endlocal & set _path=%~$PATH:1
goto :eof
See http://ss64.com/nt/syntax-args.html
For those looking for a PowerShell option. You can use the Get-Command cmdlet passing two items. First give the current dir location with .\ prefixed, then give just the exe name.
(Get-Command ".\notepad", "notepad" -ErrorAction Ignore -CommandType Application) -ne $null
That will return true if found local or in system wide paths.
#echo off
set found=
set prog=cmd.exe
for %%i in (%path%) do if exist %%i\%prog% set found=%%i
echo "%found%"
if "%found%"=="" ....
Sometimes this simple solution works, where you check to see if the output matches what you expect. The first line runs the command and grabs the last line of standard output.
FOR /F "tokens=*" %%i in (' "xcopy /? 2> nul" ') do SET xcopyoutput=%%i
if "%xcopyoutput%"=="" echo xcopy not in path.
Use command : powershell Test-Path "exe which you looking for"
It will return True if its present, otherwise False.

Finding the path of the program that will execute from the command line in Windows

Say I have a program X.EXE installed in folder c:\abcd\happy\ on the system. The folder is on the system path. Now suppose there is another program on the system that's also called X.EXE but is installed in folder c:\windows\.
Is it possible to quickly find out from the command line that if I type in X.EXE which of the two X.EXE's will get launched? (but without having to dir search or look at the process details in Task Manager).
Maybe some sort of in-built command, or some program out there that can do something like this? :
detect_program_path X.EXE
Use the where command. The first result in the list is the one that will execute.
C:\> where notepad
C:\Windows\System32\notepad.exe
C:\Windows\notepad.exe
According to this blog post, where.exe is included with Windows Server 2003 and later, so this should just work with Vista, Win 7, et al.
On Linux, the equivalent is the which command, e.g. which ssh.
As the thread mentioned in the comment, get-command in powershell can also work it out. For example, you can type get-command npm and the output is as below:
Here's a little cmd script you can copy-n-paste into a file named something like where.cmd:
#echo off
rem - search for the given file in the directories specified by the path, and display the first match
rem
rem The main ideas for this script were taken from Raymond Chen's blog:
rem
rem http://blogs.msdn.com/b/oldnewthing/archive/2005/01/20/357225.asp
rem
rem
rem - it'll be nice to at some point extend this so it won't stop on the first match. That'll
rem help diagnose situations with a conflict of some sort.
rem
setlocal
rem - search the current directory as well as those in the path
set PATHLIST=.;%PATH%
set EXTLIST=%PATHEXT%
if not "%EXTLIST%" == "" goto :extlist_ok
set EXTLIST=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH
:extlist_ok
rem - first look for the file as given (not adding extensions)
for %%i in (%1) do if NOT "%%~$PATHLIST:i"=="" echo %%~$PATHLIST:i
rem - now look for the file adding extensions from the EXTLIST
for %%e in (%EXTLIST%) do #for %%i in (%1%%e) do if NOT "%%~$PATHLIST:i"=="" echo %%~$PATHLIST:i

Resources