List of files located in text file to be send as parameter to WinSCP in script mode - batch-file

I've to create solution that is based on WinSCP and windows batch script. Below are tasks the script has to do:
Save list of files from remote directory, to file located on machine where batch script is run.
Run WinSCP in command mode (/command) using WinSCP script passed as parameter (/script=name_of_script_file.txt) and get files (get command) from previously generated list.
The most important is to get file list, save it and pass names of files located in created file to WinSCP to get them.
How to implement this?

There's no easy way to implement this using WinSCP scripting only. It's possible, see the very end of my answer, but it may not be an ideal solution.
Why do you do this in two steps? Why don't you directly download the directory?
winscp.com /command ^
"option batch abort" ^
"option confirm off" ^
"open scp://user:password#example.com/" ^
"get /path/*" ^
"exit"
If you really need the list (e.g. for some further processing), you can instead of getting a list of files in the directory, get a list of actually downloaded files.
Enable the XML logging, and get the list from the XML log.
winscp.com /xmllog=log.xml /command ^
....
You get a log like:
<?xml version="1.0" encoding="UTF-8"?>
<session xmlns="http://winscp.net/schema/session/1.0" name="user#host" start="2015-01-30T06:45:57.008Z">
<download>
<filename value="/path/file1.txt" />
<destination value="C:\path\file1.txt" />
<result success="true" />
</download>
<download>
<filename value="/path/file2.txt" />
<destination value="C:\path\file2.txt" />
<result success="true" />
</download>
</session>
If you need a plain-text list, you can use the XSLT to convert it (e.g. download.xslt):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:winscp="http://winscp.net/schema/session/1.0">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match='winscp:download[winscp:result[#success="true"]]/winscp:filename'>
<xsl:value-of select="#value"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
To process it, use any XSLT processor:
Microsoft msxsl.exe (deprecated, but available from Internet Archive)
msxsl.exe log.xml download.xslt
Libxml2 xsltproc.exe:
xsltproc.exe download.xslt log.xml
And you get:
/path/file1.txt
/path/file2.txt
See Transforming XML Log to Text Output Using XSLT Transformation.
To answer your actual question. If you really insist on getting a file list and downloading files according to it, I suggest you use the WinSCP .NET assembly from a PowerShell script.
Use the Session.ListDirectory to retrieve a contents of a remote directory.
See the Session.ListDirectory documentation and its example code.
For recursive listing, you can also use the Session.EnumerateRemoteFiles.
Iterate the results and call the Session.GetFiles for every file.
See the Session.GetFiles documentation and its example code.
If the simple scripting is your preference:
The only way to obtaining a plain text list of remote files reliably, is to use the XML logging to capture an output of the ls scripting command:
winscp.com /xmllog=log.xml /command ^
"option batch abort" ^
"option confirm off" ^
"open scp://user:password#example.com/" ^
"ls /path" ^
"exit"
You get a log like:
<?xml version="1.0" encoding="UTF-8"?>
<session xmlns="http://winscp.net/schema/session/1.0" name="user#host" start="2015-01-30T07:08:27.749Z">
<ls>
<destination value="/path" />
<files>
<file>
<filename value="." />
<type value="d" />
<modification value="2014-07-28T07:06:49.000Z" />
<permissions value="rwxr-sr-x" />
</file>
<file>
<filename value=".." />
<type value="d" />
<modification value="2015-01-23T12:22:44.000Z" />
<permissions value="rwxr-xr-x" />
</file>
<file>
<filename value="file1.txt" />
<type value="-" />
<size value="1306091" />
<modification value="2015-01-29T23:58:12.000Z" />
<permissions value="rw-rw-rw-" />
</file>
<file>
<filename value="file2.txt" />
<type value="-" />
<size value="88" />
<modification value="2007-11-17T22:40:43.000Z" />
<permissions value="rw-r--r--" />
</file>
</files>
<result success="true" />
</ls>
</session>
Again, use the XSLT to convert the XML log to a plain-text list of files:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:winscp="http://winscp.net/schema/session/1.0">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match='winscp:file[winscp:type/#value="-"]/winscp:filename'>
<xsl:value-of select="#value"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
You get:
file1.txt
file2.txt
To get WinSCP script to download files according to a plain-text list, see scripting example Uploading a list of files. It's for an upload, but just replace the put with the get and reverse an order of arguments to turn it to a download.
Note that the /command parameter does not enter a "command" (scripting?) mode. It's for passing scripting commands on a command line (as used in my answer). You are using a script file (/script). There's no point adding an empty /command parameter to it.
See the documentation of WinSCP scripting command-line parameters.

Related

How can i change the xml file values by bat commands. 'sed' is not recognized as internal or external command

Folks,
Trying to update the XML file values using 'sed'. bat command not recognized.
CODE:
**#echo off
SET PREFIX=ROOM107-
SET SUFFIX=\Admin
SET /P PCNAME1=Please enter your desired NUMBER:
SET PCNAME=%PREFIX%%PCNAME1%%SUFFIX%
sed -i "s#<UserId>Admin</UserId>#<UserId>%PCNAME%</UserId>#" changexml.xml
pause**
Any other way to update the below XML file using BAT command ?
XML:
<xml>
<Main_group>
<group_1>
<add key="A" value="AMERICA"/>
<add key="B" value="BALL"/>
</group_1>
<group_2>
<add key="A" value="AMERICA"/>
<add key="B" value="BALL"/>
</group_2>
</Main_group>
<SubGroup>
<add key="A" value="AMERICA"/>
<add key="B" value="BALL"/>
</SubGroup>
</xml>
trying to update the value of 'A' and 'B' of the node
like below
<SubGroup>
<add key="A" value="USA"/>
<add key="B" value="Basket Ball"/>
</SubGroup>
Your xml was not valid so I've edited it a little bit (saved as xml.txt):
<xml>
<Main_group>
<group_1>
<add key="A" value="AMERICA"/>
<add key="B" value="BALL"/>
</group_1>
<group_2>
<add key="A" value="AMERICA"/>
<add key="B" value="BALL"/>
</group_2>
</Main_group>
<SubGroup>
<add key="A" value="AMERICA"/>
<add key="B" value="BALL"/>
</SubGroup>
</xml>
Here's a bat file that edit his values (should be in the same directory).Without need of installing external programs and the xml is parsed as xml without using regex. You can change the hardcoded values:
#if (#X)==(#Y) #end /* JScript comment
#echo off
cscript //E:JScript //nologo "%~f0"
exit /b %errorlevel%
#if (#X)==(#Y) #end JScript comment */
//------------------------
// -- hard coded values --
var originalFile="xml.txt";
var newFile="xml1.xml";
var xpath1='//SubGroup/add[#key="A"]/#value';
var xpath2='//SubGroup/add[#key="B"]/#value';
var newValue1="USA";
var newValue2="Basket Ball";
//---------------------------
var objDoc;
var objNodes;
var loaded;
try {
objDoc = WScript.CreateObject("MSXML.DOMDocument");
loaded=objDoc.load(originalFile);
} catch (err){
WScript.Echo("Error while parsing the xml");
WScript.Echo(err.message);
WScript.Quit(1);
}
if(!loaded){
WScript.Echo("Error while parsing the xml");
WScript.Echo("");
WScript.Echo("Error Code:"+objDoc.parseError.errorCode);
WScript.Echo("");
WScript.Echo("Line:"+objDoc.parseError.line+" Posotion:"+objDoc.parseError.filepos);
WScript.Echo("");
WScript.Echo("Reason:"+objDoc.parseError.reason);
WScript.Echo("");
WScript.Echo("URL:"+objDoc.parseError.url);
WScript.Echo("");
WScript.Echo(objDoc.parseError.srcText);
WScript.Quit(5);
}
var node1=objDoc.selectSingleNode(xpath1);
var node2=objDoc.selectSingleNode(xpath2);
node1.text=newValue1;
node2.text=newValue2;
objDoc.save(newFile);
You probably want to use xmlstarlet to handle XML documents.
Here is the link to install it:
http://xmlstar.sourceforge.net/download.php
After getting it working, here is the code to handle the required changes:
xmlstarlet ed [--inplace] -u '/xml/SubGroup/add[#key="A"]/#value' -v "USA" -u '/xml/SubGroup/add[#key="B"]/#value' -v "Basket Ball" changexml.xml
The argument --inplace will change the original file.
If you want to use sed on Windows, you have to get a Unix environment like MSYS, Cygwin or Windows Subsystem for Linux.
However, it's not a good idea to parse XML with RegEx.

Reading keys from a XML file into a shell array

Im trying to parse an xml file and get all tag values and fonts values and add them to an associative arrays. My issue is that the array doesn't seem to be having the values assigned to it properly
#!/bin/bash
GAME_NAME="."
LOCALIZATION_DIR="$GAME_NAME/assets/data/localization"
INDEX=0
OUTPUT_KEYS=()
# parse english xml for tags and font names first
for str in $(echo "cat //strings/string/#key" | xmllint --shell "$LOCALIZATION_DIR/en.xml")
do
echo "$str"
echo "--"
OUTPUT_KEYS[$index]="$str"
((INDEX++))
done
echo ${OUTPUT_KEYS[0]}
The last echo just echos the end of the tag > A little confused on how arrays should be working in shell or if there is a better way to approach this.
My XML looks like this.
<?xml version="1.0" encoding="UTF-8" ?>
<strings version="5.6051.4-en">
<!--<StarLineUI>-->
<!-- Menu -->
<string key="betProper" value="Bet" fonts="uiAccountTitle" />
<string key="linesProper" value="Lines" fonts="uiAccountTitle" />
<string key="spinsProper" value="Spins" fonts="uiAccountTitle" />
<string key="bet" value="BET" fonts="uiMenuTitle, uiAccountTitle" />
<string key="line" value="LINE" fonts="uiMessage" />
</strings>
I'm trying to build a solution that works with GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin16)
If you have bash 4.0 or newer:
readarray -t output_keys \
< <(xmlstarlet sel -t -m '//strings/string[#key]' -v #key -n <in.xml)
echo "${output_keys[0]}"
Otherwise:
output_keys=( )
while IFS= read -r line; do
output_keys+=( "$line" )
done < <(xmlstarlet sel -t -m '//strings/string[#key]' -v #key -n <in.xml)
In either of these, the output of xmlstarlet is just the keys you're trying to extract, as in:
betProper
linesProper
spinsProper
bet
line
...and this can be iterated over as you'd expect:
for key in "${output_keys[#]}"; do
echo "Found key: $key"
done
If you don't have xmlstarlet, you can run the XSLT equivalent to the above command line; if you have a stylesheet print-strings.xslt with the following contents:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common" version="1.0" extension-element-prefixes="exslt">
<xsl:output omit-xml-declaration="yes" indent="no"/>
<xsl:template match="/">
<xsl:for-each select="//strings/string[#key]">
<xsl:call-template name="value-of-template">
<xsl:with-param name="select" select="#key"/>
</xsl:call-template>
<xsl:value-of select="'
'"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="value-of-template">
<xsl:param name="select"/>
<xsl:value-of select="$select"/>
<xsl:for-each select="exslt:node-set($select)[position()>1]">
<xsl:value-of select="'
'"/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
...then you can run:
xsltproc print-strings.xslt in.xml
...to get the same output as xmlstarlet sel -t -m '//strings/string[#key]' -v #key -n <in.xml.

How to access local env variables from ANT file

I have this ANT file that calls a batch file to set the environment variables, before these environment variables are referred to in the ANT script.
So my batch file (set_env.bat) contains:
REM ------ ECLIPSE ------
SET DIR_ECLIPSE=C:\Eclipse
SET DIR_ECLIPSE_MARS=%DIR_ECLIPSE%\eclipse-dsl-mars-2-win32
SET DIR_DEST=C:\mydsl\generated
Then my ANT script will call it like this:
<target name="setvars" >
<exec executable="cmd">
<arg value="/c"/>
<arg value="set_env.bat"/>
<arg value="-p"/>
</exec>
</target>
<property environment="env"/>
<property name="tool" value="${env.DIR_ECLIPSE_MARS}" />
<property name="destDir" value=${env.DIR_DEST}" />
<target name="create_run_jar">
<copy file="${env.DIR_ECLIPSE_MARS}/plugins/org.eclipse.xtext_2.9.1.v201512180746.jar"
todir="${destDir}/myDsl_lib" />
</target>
I keep on getting an error saying the file cannot be found and when I print the log via , I get "[echo] ${env.DIR_ECLIPSE_MARS}". So this means that the env vars are not set.
During copy, it displays this log:
D:\mydsl\antbuild.xml:129: Warning: Could not find file D:\mydsl\${env.DIR_ECLIPSE_4_5_2}\plugins\org.eclipse.xtext_2.9.1.v201512180746.jar to copy.
I execute the setvars target first followed by the create_run_jar.

Running a Batch file with ant 'build.xml'

I am trying to run a batch file called 'sparql.bat' by using ant build.xml. The last line in the batch file is -
java %JVM_ARGS% -Dlog4j.configuration="%LOGGING%" -cp "%JENA_CP%" arq.sparql --data=\apache-jena-2.10.0\test.ttl --query=\apache-jena-2.10.0\testing.rq
Where %JVM_ARGS% is -Xmx1024M and the other things can also be replaced.
I am trying to run this using an ant build.xml, "--data=\apache-jena-2.10.0\test.ttl --query=\apache-jena-2.10.0\testing.rq" are the arguments.
I have prepared the build.xml like this-
<?xml version="1.0"?>
<project name="apache-jena-2.10.0" basedir="." default="notify">
<target name="notify">
<java JVM_ARGS="-Xmx1024M" Dlog4j.configuration="%LOGGING%" cp="C:\apache-jena-2.10.0\lib\*" arq.sparql="%*">
<arg data="C:\apache-jena-2.10.0\test.ttl"/>
<arg query="\apache-jena-2.10.0\ASKTest.rq"/>
<classpath>
<pathelement location="."/>
</classpath>
</java>
</project>
Can somebody please help me to write a build.xml file which can execute this?
Thanks
Somsuvra
Try something more along these lines:
<java classname="arq.sparql">
<arg value="--data=C:\apache-jena-2.10.0\test.ttl"/>
<arg value="--query=C:\apache-jena-2.10.0\testing.rq"/>
<jvmarg value="-Xmx1024M"/>
<jvmarg value="-Dlog4j.configuration=log4j.properties”/>
<classpath>
<pathelement location="C:\apache-jena-2.10.0\lib"/>
</classpath>
</java>
Final solutions is -
<?xml version="1.0"?>
<project name="apache-jena-2.10.0" basedir="." default="notify">
<target name="notify">
<java classname="arq.sparql" fork="true">
<arg value="--data=C:\apache-jena-2.10.0\test.ttl"/>
<arg value="--query=C:\apache-jena-2.10.0\ASKTest.rq"/>
<jvmarg value="-Xmx1024M"/>
<classpath>
<pathelement location="C:\apache-jena-2.10.0\lib\jena-core-2.10.0.jar"/>
<pathelement location="C:\apache-jena-2.10.0\lib\jena-arq-2.10.0.jar"/>
<pathelement location="C:\apache-jena-2.10.0\lib\log4j-1.2.16.jar"/>
</classpath>
</java>
</target>
</project>
Now my question is instead of writing all the .jar files seperately is there any way of specifying them in a single line.
Thanks and regards
Somsuvra
You could replace the pathelement elements with a fileset. Something like this:
<fileset dir="C:\apache-jena-2.10.0\lib">
<include name="**/*.jar"/>
</fileset>

WIX invoking Batch file in InstallExecution Sequence

I am new to WIX and I am trying to invoke a batch file from my WIX installer.
MyBatch.bat contains a simple Copy of a Text file from c:\ to D:\
I am unable to invoke the batch file. Below is my code.
Please help me out.
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?define ProductName='Test Product' ?>
<?define ProductVersion='1.0.0.0' ?>
<?define ProductCode='b7bc7c6f-9a4e-4973-be84-eca8e3427c97'?>
<?define UpgradeCode='06a81104-1e30-463d-87e1-e8a79b4c682a'?>
<?define Manufacturer='Respond'?>
<Product Id="$(var.ProductCode)" Language="1033" Version='$(var.ProductVersion)' Name='Respond'
Manufacturer='$(var.Manufacturer)' UpgradeCode='$(var.UpgradeCode)'>
<Package InstallerVersion='200' Compressed='yes' />
<Media Cabinet='media1.cab' EmbedCab='yes' Id ='1' />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id='ProgramFilesFolder'>
<Directory Id='INSTALLDIR' Name='$(var.ProductName)'>
<Component Id='ProductComponent' Guid='b11556a2-e066-4393-af5c-9c9210187eb2'>
<File Id='Calc' DiskId='1' Source='C:\WINDOWS\system32\calc.exe'/>
<File Id='my_batch_script' Name='MyBatch.bat' DiskId='1' Source='MyBatch.bat' />
</Component>
</Directory>
</Directory>
<Directory Id='ProgramMenuFolder'>
<Directory Id='ApplicationProgramsFolder' Name='$(var.ProductName)'>
<Component Id='MainExecutable' Guid='EDED00D8-2465-46CA-86D6-B20DE921EFA6'>
<Shortcut Id='ShortcutCalc' Description='$(var.ProductName)' Name ='Calculator of Windows'
Target='[INSTALLLOCATION]Calc.exe' WorkingDirectory='INSTALLLOCATION'/>
<RemoveFolder Id='ApplicationProgramsFolder' On='uninstall'/>
<RegistryValue Root='HKCU' Key='Software\$(var.Manufacturer)\$(var.ProductName)' Type ='multiString'
Name='installed' Value='1' KeyPath='yes'/>
</Component>
</Directory>
</Directory>
</Directory>
<Feature Title='SetupProject1' Level='1' Id ='AppFeature'>
<ComponentRef Id='ProductComponent' Primary='yes'/>
<ComponentRef Id='MainExecutable'/>
</Feature>
<Property Id='WIXUI_INSTALLDIR' Value='INSTALLDIR' ></Property>
<WixVariable Id='WixUILicenseRtf' Overridable='yes' Value='License.rtf'/>
<?include CustomActions.wxi?>
<UI>
<?include UISequence.wxi?>
<?include Dialogs.wxi?>
</UI>
<CustomAction Id="BatchCmd" Property="ThePath" Value="[INSTALLDIR]MyBatch.bat" />
<CustomAction Id="BatchRun" Property="ThePath" ExeCommand='"[INSTALLDIR]MyBatch.bat"' Return='asyncWait' Impersonate="yes"/>
<InstallExecuteSequence>
<Custom Action="BatchCmd" After="InstallFiles" >Not Installed </Custom>
<Custom Action="BatchRun" After="BatchCmd">Not Installed </Custom>
</InstallExecuteSequence>
</Product>
</Wix>
This worked for me.
<CustomAction Id="test"
ExeCommand="[INSTALLDIR]MyBatch.bat"
Directory="INSTALLDIR" Execute="deferred" Return="asyncWait"/>
<InstallExecuteSequence>
<Custom Action="test" After="InstallFiles">NOT Installed</Custom>
</InstallExecuteSequence>
It seems you're solving your problem with the wrong tool. If the file to copy is a part of your application, you should author a Component with this File inside. Otherwise, if you need just to copy some external file already present on a target system, you should author a CopyFile element, nesting it under Component element.
I agree with Yan, you solve your task the wrong way.
As for batch files, they are not executed on its own like an .exe, it is cmd.exe which reads the bat file and executes it line by line.
To run a batch file, you should run it this way:
cmd.exe /c batch.bat
Add your CustomAction defnition as given below. Make sure that Return="ignore" if you are not returning anything from bat file.
<CustomAction Id="RunBat" Directory="your_directory"
ExeCommand='"c:\test\test.BAT"'
Execute='deferred' Impersonate='no' Return='ignore'/>
Also in the installExecuteSequence sequence add the action before InstallFinalize
<InstallExecuteSequence>
<Custom Action="RunBat" Before="InstallFinalize">
NOT (REMOVE~="ALL")
</Custom>
</InstallExecuteSequence>

Resources