How to find Windows SDK's SetEnv.cmd / SetEnv.cmd Does not work correctly - windows

We have a Team City Build Server running and want to compile a Visual C++ project. So far this would be easy, since I've setup our Windows Build Agent with the Windows SDK, but we don't have a solution / project file.
The project files are instead created with CMake. CMake seems to be a little bit dumb (can't generate Solution when Visual Studio is not installed), but with some tricks, I could get it to do it. The solution can then be built with MSBuild.
And here comes the problem. For this to work automatically, I need to call the Windows SDK's SetEnv.cmd. And I can't seem to find it automatically. It's in the bin sub directory of the Windows SDK, but neither bin nor the root are in the path, and the %mssdk% environment variable is set by the SetEnv.cmd and is not available beforehand!
Adding the Windows SDK\bin dir to the PATH leads to SetEnv.cmd no longer working (exits with a message like The x86 compilers are not currently installed and Jump target Set_x86 not found.
The start menu link is calling the SetEnv.cmd with the Windows SDK dir as working directory instead. But if I add the root directory to the PATH, Bin\SetEnv.cmd is not available.
How can I find SetEnv.cmd automatically? Even setting an environment variable to the full path of the setenv.cmd doesn't work, and when I define %mssdk% as the sdk dir, then call %mssdk%\bin\SetEnv doesn't work as well. I also tried to define %mssdk%, then cd %mssdk%, then calling bin\SetEnv. Also compilers not found in all these cases. It also doesn't work if I manually cd to the root or bin dir on a command line and then call SetEnv.cmd...
The start menu link works fine though.
For the record, my solution for now, as strange as this is, is the following:
I created a MSBuild file that creates the solution file with CMake on the command line, then invokes the created solution with a MSBuild task. The MSBuild file can be easily built from TeamCity, though I needed some additional tricks to satisfy CMake's stupid looking for the compiler, though I won't invoke it thing. Not really satisfying, but it works.

My solution (sets %WindowsSdkPath%, so that SetEnv.cmd could be found under %WindowsSdkPath%Bin\):
#ECHO OFF
IF "%WindowsSdkVersion%"=="" (
CALL :SetWindowsSdkVersionHelper HKCU > nul 2>&1
IF ERRORLEVEL 1 CALL :SetWindowsSdkVersionHelper HKLM > nul 2>&1
IF ERRORLEVEL 1 GOTO ERROR_NOWSDK
)
CALL :SetWindowsSdkPathHelper > nul 2>&1
IF ERRORLEVEL 1 GOTO ERROR_NOWSDK
GOTO END
:SetWindowsSdkPathHelper
SET WindowsSdkPath=
FOR /F "tokens=1,2*" %%i in ('REG QUERY "HKLM\SOFTWARE\Microsoft\Microsoft SDKs\Windows\%WindowsSdkVersion%" /V InstallationFolder') DO (
IF "%%i"=="InstallationFolder" (
SET "WindowsSdkPath=%%k"
)
)
IF "%WindowsSdkPath%"=="" EXIT /B 1
EXIT /B 0
:SetWindowsSdkVersion
CALL :GetWindowsSdkVersionHelper HKCU > nul 2>&1
IF ERRORLEVEL 1 CALL :GetWindowsSdkVersionHelper HKLM > nul 2>&1
IF ERRORLEVEL 1 EXIT /B 1
EXIT /B 0
:SetWindowsSdkVersionHelper
SET WindowsSdkVersion=
FOR /F "tokens=1,2*" %%i in ('REG QUERY "%1\SOFTWARE\Microsoft\Microsoft SDKs\Windows" /V "CurrentVersion"') DO (
IF "%%i"=="CurrentVersion" (
SET "WindowsSdkVersion=%%k"
)
)
IF "%WindowsSdkVersion%"=="" EXIT /B 1
EXIT /B 0
:ERROR_NOWSDK
ECHO The Windows SDK %WindowsSdkVersion% could not be found.
EXIT /B 1
:END
I was inspired for this by the SetEnv.cmd itself...

Mac, nice answer!
Now I would like to run msbuild with my project file. But before I should run SetEnv.Cmd - right?
So, here we go:
run_Macs_code.bat REM see above
call "%WindowsSdkPath%\bin\Setenv.cmd" /Release /x86 /xp
cd E:\client
msbuild client.proj
Now it's working :)

Related

Access system32 from VisualStudio 2019 build script

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.

Keil C51 tools integrated into Qt Creator

I like the Qt Creator development environment and now need to compile some C code using the Keil C51 compiler/tools.
Is it possible to use the Qt Creator IDE with Keil C51 tools? Can someone describe in detail how to set that up?
OR - is than another 8052 chip compiler that can be integrated with Qt Creator?
I can confirm, that I'm able to use QT with C51.
Environment
I use QT as Editor.
And Keil C51 as compiler. I write a program in a pure C (not C++) for the STC microcontroller.
TLDR
I use the Windows Batch file to compile all *.c files in the working directory and then link it together into a one hex file. Then I setup the QT (Projects -> Build Settings -> Build Steps -> Custom) to use this Batch during a building stage (don't forget to setup the working directory correctly).
How to get a needed Batch file
Simple way
Keil can generate a batch for the particular project (see Project->Options for Target->Output->Create Batch File). Then use this file in the QT as described above.
The main flaw of this way is - when you change the project configs or add a new file to the project, you need to regenerate a Batch from the Keil.
Better way
Using generated Batch from Keil, I write my own Bath, that doesn't have the flaws, described above.
Even better way
I guess using a Makefile is better for this than Batch, as it gives QT more control and info about the errors during the compilation. But I dont't have an "on-shelf" example right now.
Batch file description
In a nutshell, this Batch:
Setups the settings and directories
Calls compiler for every *.c file in the working dir
Builds *.obj list and call linker to obtain a firmware image in a binary format
Converts the obtained imge into a HEX format
The listing:
REM "Folders"
SET C51FLDR=d:\Keil\C51
SET OUTFLDR=.\Objects
SET LSTFLDR=.\Listings
SET HEXNAME=PREP_FIRMWARE
SET C51INC=%C51FLDR%\Inc;%C51FLDR%\Inc\STC
SET C51LIB=%C51FLDR%\Lib
REM "Variables"
SET C51EXE=%C51FLDR%\BIN\C51.EXE
SET C51LNK=%C51FLDR%\BIN\BL51.EXE
SET C51HEX=%C51FLDR%\BIN\OH51.EXE
set OBJLIST=
REM "Defines"
SET CPU_TYPE=STC15W408S
SET CPU_VENDOR=STC
SET UV2_TARGET=Target 1
SET CPU_XTAL=0x02160EC0
SET SETTINGS="COMPACT ROM(COMPACT) OPTIMIZE (9,SIZE) BROWSE DEBUG OBJECTEXTEND LISTINCLUDE SYMBOLS TABS (2)"
echo ===================== Compile =====================
for %%f in (*.c) do (
echo %errorlevel%
setlocal EnableDelayedExpansion
%C51EXE% %%f %SETTINGS% "PRINT(%LSTFLDR%\%%~nf.lst) OBJECT(%OUTFLDR%\%%~nf.obj)"
if !errorlevel! neq 0 exit /b !errorlevel!
)
echo ===================== Link =====================
call :obj_list
echo %OBJLIST%
%C51LNK% %OBJLIST% TO "%OUTFLDR%\%HEXNAME%" PRINT("%LSTFLDR%\%HEXNAME%.m51") RAMSIZE(256)
echo %errorlevel%
if %errorlevel% GTR 1 exit /b %errorlevel%
echo ===================== Hex =====================
%C51HEX% "%OUTFLDR%\%HEXNAME%"
if %errorlevel% neq 0 exit /b %errorlevel%
echo ==========================================
echo Build Success
echo ==========================================
goto :eof
:obj_list
for %%f in (.\Objects\*.obj) DO call :concat %%f
set OBJLIST=%OBJLIST:~0,-1%
goto :eof
:concat
set OBJLIST=%OBJLIST%"%1",
goto :eof
Right now, it is possible to use QtCreator with Qbs (since 1.15). Also, it is possible to use VSCode with the Qbs extension.

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.

Problems working with different drive units in a batch file

This is my first batch file, and also my first time with batch language (I usually use UNIX and don't know a lot about Windows commands).
I'm creating a batch file called install.bat which does all the work to install a Java application from source files. Here a snapshot of the install section:
#ECHO off
SET INSTALL_DIR=%1\
SET SRC_DIR=sources\
SET LIB_DIR=lib\
SET IMG_DIR=img\
SET BIN_DIR=bin\
SET INIT_DIR=%CD%
SET MAIN_CLASS=%SRC_DIR%\main\Main.java
SET CLASS_PATH=%LIB_DIR%log4j.jar;%LIB_DIR%jdom.jar;
SET JAR_NOM=myApp.jar
SET JAR_MF=MANIFEST.MF
:BEGIN
CLS
ECHO Checking directory...
IF EXIST %INSTALL_DIR% (
GOTO Ask_Overwrite
) ELSE (
GOTO Install
)
:Ask_overwrite
SET OVERW=Y
SET /P OVERW="The program is already installed. Overwrite? ([Y]/N): "
IF %OVERW%==N GOTO Cancel
IF %OVERW%==n GOTO Cancel
IF %OVERW%==Y (
RD /S /Q %INSTALL_DIR% <--- Here was the error
GOTO Install
)
IF %OVERW%==y (
RD /S /Q %INSTALL_DIR% <--- Here was the error
GOTO Install
)
GOTO Ask_overwrite
:Install
MD %INSTALL_DIR%
XCOPY . %INSTALL_DIR% /E
CD /D %INSTALL_DIR%
MD %BIN_DIR%
ECHO Compiling...
javac -cp %CLASS_PATH% -sourcepath %SRC_DIR% %MAIN_CLASS% -d %BIN_DIR%
ECHO Creating JAR file...
jar cfm %JAR_NOM% %JAR_MF% -C %BIN_DIR% .
ECHO Succes! The application has been installed in %INSTALL_DIR%
GOTO CleanUp
:Abort
ECHO Abort! The application has not been installed.
GOTO CleanUp
:Cancel
ECHO Canceled by user. The application has not been installed.
GOTO END
:Cleanup
REM Code for clean up
GOTO END
:END
CD /D %INIT_DIR%
PAUSE
NOTE: The javac and jar commands are correct, at least work in my machine.
Well, the script works correctly when I test it with the INSTALL_DIR belonging to the same drive where I execute it, but if I use a target directory out of the drive where I'm executing, I have problems.
Executions without problems (called in a cmd.exe session):
C:\Users\TC\testInstall> install.bat .\..\installTarget
C:\Users\TC\testInstall> install.bat C:\Users\TC\installTarget
Execution with problems (called in a cmd.exe session):
C:\Users\TC\testInstall> install.bat D:\Documents\installTarget
The problems happen when I try to copy files specially, but also making and removing directories.
I hope someone can tell to me which options I must use in order to fix the problems.
Regards!
Well, I have two mistakes that I fixed and then the script work correctly.
The first was that I didn't use the /d option in cd command in order to change also the drive unit. It means, C:Users\TC> cd D:\Documents is wrong, the correct command is the following: C:Users\TC> cd /d D:\Documents as well as the MS-DOS manual page indicates.
The second error, it wasn't actually an error, I put rm -rf %INSTALL_DIR% (like in Linux) instead of the correct Windows command rd /s/q %INSTALL_DIR%
Now all the problems have been fixed and the script works properly :)
It has to do with batch files not accessing other drives than the C:\ drive by default. Even if you open up your command line, you shouldn't be able to CD D:\. Try this (not sure if this will work as I have never tried it)
PUSHD D:\
C:\Users\TC\testInstall> install.bat D:\Documents\installTarget
Or else, use PUSHD D:\ then move the install file to D:\ temporarily and install. Only solutions I can think of.

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.

Resources