How to parse a XML file with Command Line (cmd/batch) - windows

I have a XML file Testing.Config with the following content:
<?xml version="1.0" encoding="utf-8"?>
<connectionStrings>
<add name="name1" connectionString="user id=id1;password=password1;"/>
<add name="name2" connectionString="user id=id2;password=password2;"/>
<add name="name3" connectionString="user id=id3;password=password3;"/>
</connectionStrings>
I need parse this file and obtain id and password key-value pairs in an attribute of a particular tag identified by the provided attribute, for example "name=name1".
Example
Input:
name=name1
Returns:
id=id1
password=password1

#echo off
set "xml_file=test.xml"
set /p search_for=Enter name:
for /f "skip=2 tokens=3,9 delims=;= " %%a in ('find """%search_for%""" "%xml_file%"') do (
set "name=%%~a"
set "pass=%%b"
)
echo name : %name%
echo pass : %pass%
If all connectionStrings are on separated lines and every string is on one line.Change the location of the xml_file
You can also try the xpath.bat (better option according to me) -small script that will allow you to get a xml values by xpath expression without using external binaries:
call xpath.bat connection.xml "//add[#name = 'name1']/#connectionString"

Since you indicated (in comments on the question) that powershell is also okay, put the following code in a script file (lets say Foo.ps1)
param
(
[Parameter(Mandatory=$true)]
[string] $ConfigFilePath,
[Parameter(Mandatory=$true)]
[string] $Name
)
([xml](Get-Content -LiteralPath $ConfigFilePath)).connectionStrings.add |
Where-Object {$_.name -eq $name} |
ForEach-Object {($_.connectionString -split ' ')[1] -split ';'}
and then run the script with parameters to get the output.

In case someone comes looking for this like I did... the xpath.bat linked by npocmaka works great, but not for file one network paths e.g. \server\foo\bar.xml. And I couldn't get Vikus's PS script to work for attributes. So... I managed to turn Vikus's PS into this:
param
(
[Parameter(Mandatory=$true)]
[string] $XMLFile,
[Parameter(Mandatory=$true)]
[string] $Xpath
)
[xml]$xml = Get-Content -Path $XMLFile
$value = Select-XML -xml $xml -xpath $Xpath
$value.ToString()
Which I then collapsed to this for use in a .cmd file:
set psc="(Select-XML -xml ([xml](Get-Content -Path %xmlfile%)) -xpath %xpath%).ToString()"
for /f %%a in ('powershell %psc%') do set myvar=%%a
Though it's worth noting that neither %xmlfile% nor %xpath% can have spaces. That requires escaping all the parentheses so that the powershell command doesn't have to be wrapped in double quotes:
for /F %a in ('powershell ^(Select-XML -xml ^([xml]^(Get-Content -Path "test.xml"^)^) -xpath "/Computers/Computer/Name/#id"^).ToString^(^)') do #echo %a
FWIW, they're all markedly slower that my original fragile monster:
#for /F eol^=^#^tokens^=2^,5^,7^ delims^=^<^>^" %%i in (%xmlfile%) do #(
#if "%targetnodename% ATTR1=" == "%%i" (
set myvar1=%%j
set myvar1=%%k
)
)

Related

Batch file: Combine echos into one line

I want to dump all the file names in a folder without extension into a text file. They should be in one line separated by commas.
So in my folder I have
File1.bin
File2.bin
....
With
(for %%a in (.\*.bin) do #echo %%~na,) >Dump.txt
I got
File1,
File2,
But what I want in the end is a text file with, so one long combined string.
File1,File2,...
I'm kinda stuck here and probably need something else than echo.
Thanks for trying to help.
Try like this:
#echo off
setlocal enableDelayedExpansion
for %%a in (.\*.txt) do (
<nul set /p=%%~nxa,
)
check also the accepted answer here and the dbenham's one.
You could also leverage powershell from a batch-file for this task:
#"%__APPDIR__%WindowsPowerShell\v1.0\powershell.exe" -NoProfile -Command "( Get-Item -Path '.\*' -Filter '*.bin' | Where-Object { -Not $_.PSIsContainer } | Select-Object -ExpandProperty BaseName ) -Join ',' | Out-File -FilePath '.\dump.txt'"
This could probably be shortened, if necessary, to:
#PowerShell -NoP "(GI .\*.bin|?{!$_.PSIsContainer}|Select -Exp BaseName) -Join ','>.\dump.txt"

file listing - Including all subfolders with filename, path, date and size

I am trying to get a list of all files within a directory including those within all subfolders.
The columns I would like are the Filename, Path, Size and Date.
I have tried to do my own research and come close but not yet hit the full solution.
I can get the filepath and filename together with date and size using this command below, unfortunately I cannot get all the files within a subfolders.
dir /t > filelist1.txt
This below CMD command does get the filenames from all subfolders but I cannot get it to produce dates.
(#For /F "Delims=" %A in ('dir /B/S/A-D') Do #Echo %~fA %~zA) >filelist.txt
I thought maybe do this to include dates but it didn't work.
(#For /F "Delims=" %A in ('dir /B/S/A/D') Do #Echo %~fA %~zA) >filelist.txt
This file also gives me the path and filename together which I can accept (I will use Excel to separate) but is it possible to have the path and filename separated?
Also it is possible to have those columns separated by tab for easier Excel import?
This could be done with %~ variables in a cmd.exe batch script. But, it is easier and more readable in PowerShell. The output is in a file named FileList.csv.
Get-ChildItem -Recurse |
ForEach-Object {
[PSCustomObject]#{
FileName = $_.Name
Path = $_.Directory
Size = $_.Length
Date = $_.LastWriteTime
}
} |
Export-Csv -Path './FileList.csv' -Delimiter "`t" -Encoding ASCII -NoTypeInformation
If you do not want to run the .ps1 file from the .bat script, it can, with enough effort, be put into the .bat file script.
powershell -NoLogo -NoProfile -Command ^
"Get-ChildItem |" ^
"ForEach-Object {" ^
"[PSCustomObject]#{" ^
"FileName = $_.Name;" ^
"Path = $_.Directory;" ^
"Size = $_.Length;" ^
"Date = $_.LastWriteTime" ^
"}" ^
"} |" ^
"Export-Csv -Path './FileList.csv' -Delimiter "`t" -Encoding ASCII -NoTypeInformation"

Batch to PowerShell Language Conversion

My batch script in DOS is processing way to slow, so someone recomended I use powershell. I'm running it now for my first time on windows, but I've never used it before today. I hear it's similar to batch scripting, so I'm currently converting my batch script into a powershell script. Below is my script so far half way through conversion:
# ask user for network share and file that they would like to search
$textFilePath = Read-Host Please enter filesystem location of "filenames.txt". Include drive letter or // at start of path
$uncPath = Read-Host Please enter the UNC path you would like to search. Include // at start of path.
# REM check if network path is available. If it is, search network directory for files with same name as the strings in filenames.txt
IF (Test-Path %uncPath%) {
echo Network Path Exists. Searching %uncPath% for files with same name and extension as filenames in the filenames.txt file
for (/r %uncPath% %%G IN (*)) {for (/F "tokens=*" %%i in (%textFilePath%)) {if (%%~nxG==%%i) {echo %%~nxG,%%~fG >> filenamesOutput.txt}}}
pause
}
IF (!(Test-Path exist %uncPath%)) {
echo File not found
GOTO:userInput
}
I'm currently learning the powershell commands as I go and changing the batch command to powershell. Help with conversion would be appreciated.
AFter Edit:
Here's my original batch script:
#echo off
echo Please enter filesystem location of "filenames.txt". (Include Drive letter or // at start of path)
set /p textFilePath=Enter The Value:%=%
:userInput
REM ask user for network share and file that they would like to search
echo Please enter the UNC path you would like to search. (Include // at start of path)
set /p uncPath=Enter The Value:%=%
REM check if network path is available. If it is, search network directory for files with same name as the strings in filenames.txt
IF exist %uncPath% (
echo Network Path Exists. Searching %uncPath% for files with same name and extension as filenames in the filenames.txt file
for /r %uncPath% %%G IN (*) DO for /F "tokens=*" %%i in (%textFilePath%) DO if %%~nxG==%%i echo %%~nxG,%%~fG >> filenamesOutput.txt
pause
)
IF NOT exist %uncPath% (
echo File not found
GOTO:userInput
)
After 2nd Edit:
$VerbosePreference = "continue"
# ask user for network share and file that they would like to search
$textFilePath = Read-Host Please enter filesystem location of "filenames.txt". Include drive letter or // at start of path
$uncPath = Read-Host Please enter the UNC path you would like to search. Include // at start of path.
# check if network path is available. If it is, search network directory for files with same name as the strings in filenames.txt
IF (Test-Path $uncPath){
echo "Network Path Exists. Searching $uncPath for files with same name and extension as filenames in the filenames.txt file"
foreach($file in Get-ChildItem $uncPath -Recurse) {
# Get-Content reads in a file, line by line
foreach($line in Get-Content $_.FullName) {
# if goes in here
if($file.Name -eq $line){
echo $file.Name
"{0},{1}" -f $file.Name,$file.FullName | Out-File filenamesOutput2.txt -Append
}
}
}
Write-Host -NoNewLine 'Press any key to continue...';
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');
}
IF (!(Test-Path $uncPath)){
echo "UNC path not found"
Write-Host -NoNewLine 'Press any key to continue...';
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');
}
Variables:
In PowerShell, variable references are always prefixed with $ (much like in PHP or Perl).
So any variable you would assign and dereference in cmd/batch as:
set /p varname= somevalue
echo %varname%
Would in PowerShell be treated as (notice no difference between assigning and dereferencing):
$varname = "varvalue"
Write-Host $varname
So your exists/Test-Path statements should be:
if(Test-Path $uncPath){
# Loops in here
# "#" starts is a single-line comment btw
}
For loop:
In cmd, the for loop construct behaves different depending on the first switch:
for /r roughly means "loop recursively through filesystem tree"
for /f roughly means "loop through tokens in a file"
it should be noted that cmd for loops use parameters, denoted by the prefix %% (like %%G or %%i in your example)
PowerShell doesn't have this concept and just uses variables in loops. Thus, your for /r and for /f loops become:
# Get-ChildItem is equivalent to the "dir" command
# The -Recurse is pretty self-explanatory ( = /S)
foreach($file in Get-ChildItem $uncPath -Recurse) {
# Get-Content reads in a file, line by line
foreach($line in Get-Content $textFilePath) {
# if goes in here
}
}
Parameter modifiers:
In cmd, a parameter (like %%G) can be modified using a tilde (~) followed by a sequence of modifier characters.
%%~nG means "treat %%G as a path, return the name without extension"
%%~xG means "treat %%G as a path, return the file extension"
so %%~nxG naturally means "return filename WITH extension".
In PowerShell, everything is a .NET object, and in the case of $file, it's a FileInfo object. From the FileInfo object, the filename (WITH the extension) is stored in the Name property, so your if statement:
if %%~nxG==%%i
becomes:
if($file.Name -eq $line){
# echo and output goes in here
}
%%~fG means "treat %%G as a path, give me the full rooted path"
Again, the fact that $file is a FileInfo object comes in handy, the full path can be accessed from the FullName property:
"{0},{1}" -f $file.Name,$file.FullName | Out-File filenamesOutput.txt -Append
The -f operator is a simplified syntactic shortcut to String.Format, .NET's version of sprintf if you will.
Ultimately resulting in something like:
# ask user for network share and file that they would like to search
$textFilePath = Read-Host 'Please enter filesystem location of "filenames.txt". Include drive letter or \\ at start of path'
$uncPath = Read-Host 'Please enter the UNC path you would like to search. Include \\ at start of path.'
# check if network path is available. If it is, search network directory for files with same name as the strings in filenames.txt
if (Test-Path $uncPath) {
Write-Host "Network Path Exists. Searching $uncPath for files with same name and extension as filenames in the filenames.txt file"
foreach($file in Get-ChildItem $uncPath) {
foreach($line in Get-Content $textFilePath) {
if($file.Name -eq $line){
'"{0}","{1}"' -f $file.Name,$file.FullName | Out-File filenamesOutput.txt -Append
}
}
}
pause
} else {
Write-Host "File not found"
}

Converting a batch FOR /F with multiple commands to a FOREACH in PowerShell

Some time ago, #Magoo was nice enough to help me in working out a FOR /F command to archive files into 7-zip:
Using FOR or FORFILES batch command to individually archive specific files
Since then I've expanded it somewhat and want to do more elaborate things with it. However, to keep this question simple, I've included the basics to try and get this working, which I haven't had much success at.
I'm rather new to PowerShell and I have some specific reasons to use this instead of batch files, moving forward.
I understand that some more experienced users may note that I will have a reduction in performance by using such statements in PowerShell, but it isn't an important issue for me.
$env:Path += ";C:\Program Files\7-Zip"
$sourcedir = read-host "Enter the directory to archive: "
foreach ($aname in {
'cmd /c dir /s/b /a-d "$sourcedir\*.iso" '
'cmd /c dir /s/b /a-d "$sourcedir\*.daa" '
'cmd /c dir /s/b /a-d "$sourcedir\*.nrg" '
'cmd /c dir /s/b /a-d "$sourcedir\*.flp" '}
) {
IF NOT EXIST $aname.7z (
echo 7z a -t7z "$aname.7z" "$aname" -mx9 -mmt >> Z:\test\7z-log.txt
ECHO "$aname" archived.
) ELSE (
ECHO "$aname" archive file already exists.
)
}
I got into some trouble with the IF EXIST statement and even when I removed the IF and had a one-line ECHO just to simplify it even more, but I couldn't get it to output what I wanted.
So, I tried a different approach:
$env:Path += ";C:\Program Files\7-Zip"
$sourcedir = read-host "Enter the directory to archive: "
$dir_iso = ForEach-Object { cmd /c dir /s/b /a-d "$sourcedir\*.iso" }
$dir_daa = ForEach-Object { cmd /c dir /s/b /a-d "$sourcedir\*.daa" }
$dir_nrg = ForEach-Object { cmd /c dir /s/b /a-d "$sourcedir\*.nrg" }
$dir_flp = ForEach-Object { cmd /c dir /s/b /a-d "$sourcedir\*.flp" }
foreach ($aname in $dir_iso,$dir_daa,$dir_nrg,$dir_flp) {
ECHO "$aname" archived.
}
But what this did, is clumped each item of each type together, then appended "archived" to that set. Something like:
C:\folder1\iso1.iso C:\folder1\iso2.iso C:\folder1\iso3.iso archived.
C:\folder2\image.nrg archived.
C:\folder3\app1.flp C:\folder3\app2.flp archived.
instead of:
C:\folder1\iso1.iso archived.
C:\folder1\iso2.iso archived.
C:\folder1\iso3.iso archived.
C:\folder2\image.nrg archived.
C:\folder3\app1.flp archived.
C:\folder3\app2.flp archived.
I'm having a real hard time with getting this to work. Can anyone help?
Thanks.
The first thing I see here is you are using this to get file information from the filesystem
$dir_iso = ForEach-Object { cmd /c dir /s/b /a-d "$sourcedir\*.iso" }
More specifically cmd /c dir /s/b /a-d "$sourcedir\*.iso". This would translate easily to Get-ChildItem
$dir_iso = Get-ChildItem -Path $sourcedir -Filter "*.iso" -Recurse -File | Select-Object -ExpandProperty FullName
Path is the file path of the folder you are checking
Filter you want only files ending with .iso
Recurse all subdirectories are checked
File returns only files and not directories (PowerShell 3.0 or higher!. There is a simple equivalent if this is an issue. )
Select-Object -ExpandProperty FullName would just return the full path of the files found in an array.
IF EXIST could be replaced by Test-Path
If(Test-Path "$aname.7z"){
Do stuff...
}
As for the ForEach loop foreach ($aname in $dir_iso,$dir_daa,$dir_nrg,$dir_flp) there are a couple good approaches with this but the simplest transistion would be
$dir_iso + $dir_daa + $dir_nrg + $dir_flp | ForEach-Object{
Do Stuff
}
I would probably build the file collection in one variable to begin with to avoid the to concat the arrays together
$files = Get-ChildItem -Path $sourcedir -Recurse -File | Where-Object{$_.Extension -match "(iso|daa|nrg|flp)$" } | Select-Object -ExpandProperty FullName
$files | ForEach-Object{
Write-Host "$_"
}
Use write-host instead of echo.
Multidimensional array, so you'll need an inner loop.
foreach ($aname in $dir_iso,$dir_daa,$dir_nrg,$dir_flp) {
foreach ($bname in $aname) {
write-host "$bname" archived.
}
}
Another possibility; this pipes the commands together rather than storing each in a separate variable.
Get-Item $sourcedir\* |
Where {$_.Extension -like ".iso" -or $_.Extension -like ".daa" -or $_.Extension -like ".nrg" -or $_.Extension -like ".flp"} |
Foreach-Object {
if (-Not (Test-Path "$_.BaseName.7z"))
{
Write-Host $_.FullName not yet archived.
}
else
{
Write-Host $_.FullName already archived.
}
}

Starting a batch file from PowerShell with arguments

Based on How can I auto-elevate my batch file, so that it requests from UAC administrator rights if required? I'm trying to make an RunElevated.bat (see code below) that accepts a command-line (batch file name and parameters) as arguments.
The RunElevated.bat works fine when the target batch file path has no spaces in them. But it fails as soon as that path has spaces: no matter how I quote things, either PowerShell barfs, or the parameters are not passed correctly from PowerShell to the batch file.
I tried:
escaping with "" (as suggested by many sources)
escaping with \" (as suggested by Escaping quotes in powershell.exe -command via command prompt)
adding --% (as suggested by PowerShell and external commands done right and Easier Reuse of Command Lines From Cmd.exe)
surrounding with ' (as suggested by CB.).
So:
Is what I want to do possible at all?
If so: how?
RunElevated.bat:
:checkParameters
echo [%*]
if [%1]==[] ( goto :help ) else ( goto :checkPrivileges )
:help
echo Syntax:
echo %0 CmdLine
echo Where CmdLine is executed with UAC privileges.
goto :exit
:checkPrivileges
net file 1>nul 2>nul
if '%errorlevel%' == '0' ( goto :gotPrivileges ) else ( goto :getPrivileges )
:getPrivileges
PowerShell "Start-Process -FilePath \"%0\" -Verb RunAs -ArgumentList \"%*\""
goto :exit
:gotPrivileges
%*
pause
:exit
pause
exit /b
echo-cd.bat which I stored in both D:\tools\echo-cd.bat and "D:\to ols\echo-cd.bat":
echo Current Directory: [%CD%]
echo Parameters: [%*]
pause
This runs fine:
D:\tools\RunElevated.bat D:\tools\echo-cd.bat foo
These fail:
D:\tools\RunElevated.bat "D:\to ols\echo-cd.bat" foo
First failure is at the %*:
C:\Windows\system32>D:\to ols\echo-cd.bat foo
'D:\to' is not recognized as an internal or external command,
operable program or batch file.
This is because PowerShell removed the quotes around the batch file name:
C:\Windows\system32>echo [D:\to ols\echo-cd.bat foo]
[D:\to ols\echo-cd.bat foo]
I expected D:\to ols\echo-cd.bat to have double quotes around it, and foo not.
D:\tools\RunElevated.bat \"D:\to ols\echo-cd.bat\" foo
Second failure is at the PowerShell level:
D:\>PowerShell "Start-Process -FilePath \"D:\tools\RunElevated.bat\" -Verb RunAs -ArgumentList \"\"D:\to ols\echo-cd.bat\" foo\""
Start-Process : Cannot validate argument on parameter 'ArgumentList'. The argument is null or empty. Supply an argument that is not null or empty and then try the command again.
At line:1 char:77
+ Start-Process -FilePath "D:\tools\RunElevated.bat" -Verb RunAs -ArgumentList <<<< ""D:\to ols\echo-cd.bat" foo"
This is because escaping twice will end up with an empty string for ArgumentList.
D:\tools\RunElevated.bat --% "D:\to ols\echo-cd.bat" foo
Third failure is also at the %*:
C:\Windows\system32>--% D:\to ols\echo-cd.bat foo
'--%' is not recognized as an internal or external command,
operable program or batch file.
This too is because PowerShell removed the quotes around the batch file name:
C:\Windows\system32>echo [--% D:\to ols\echo-cd.bat foo]
[--% D:\to ols\echo-cd.bat foo]
I expected --% to be absent, D:\to ols\echo-cd.bat to have double quotes around it, and foo not.
D:\tools\RunElevated.bat '"D:\to ols\echo-cd.bat"' foo
Again a failure is at the %*:
C:\Windows\system32>'D:\to ols\echo-cd.bat' foo
The filename, directory name, or volume label syntax is incorrect.
This is because PowerShell removed the double quotes (and left the single quotes) around the batch file name:
C:\Windows\system32>echo ['D:\to ols\echo-cd.bat' foo]
['D:\to ols\echo-cd.bat' foo]
C:\Windows\system32>if ['D:\to] == [] (goto :help ) else (goto :checkPrivileges )
I recall using the following approach for this:
start "" %systemroot%\System32\windowspowerShell\v1.0\powershell.exe -exec bypass -noprofile -command "&{ start-process powershell -verb RunAs -ArgumentList '-noprofile -exec bypass -file \"c:\temp\test folder\elevatedpowershell.ps1\"'}"
You can replace \"c:\temp\test folder\elevatedpowershell.ps1\" with \"$0\" like you did there.
I also found the solution from Keith Hill on self elevating PowerShell to be useful if I need the person to be admin. I do not have the link now but it goes like this:
function IsAdministrator
{
$Identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$Principal = New-Object System.Security.Principal.WindowsPrincipal($Identity)
$Principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
}
function IsUacEnabled
{
(Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System).EnableLua -ne 0
}
if (!(IsAdministrator))
{
if (IsUacEnabled)
{
[string[]]$argList = #('-NoProfile', '-File', ('"' + $MyInvocation.MyCommand.Path + '"'))
$argList += $MyInvocation.BoundParameters.GetEnumerator() | Foreach {"-$($_.Key)", "$($_.Value)"}
$argList += $MyInvocation.UnboundArguments
Start-Process "$env:Windir\System32\WindowsPowerShell\v1.0\PowerShell.exe" -Verb Runas -WorkingDirectory $pwd -WindowStyle Hidden -ArgumentList $argList
return
}
else
{
throw "You must be administrator to run this script"
}
}
Assuming, you want to have elevated PowerShell script, you can have that at the top of your PowerShell script.
Ugly but it can do the job.
Use single quotes (or any character of your choice) and insert this in your code:
:gotPrivileges
cd "%~p0"
set arg=%*
set arg=%arg:'="%
%arg%
:: %*
pause
If you have 8.3 file names enabled you can try this:
:getPrivileges
setlocal enabledelayedexpansion
set Args=%*
for %%a in (%*) do if exist %%a (
set Args=!Args:%%a="%%~sa"!
)
PowerShell "Start-Process -FilePath \"%0\" -Verb RunAs -ArgumentList \"%Args%\"" 2>nul
goto :exit
However, if you pass an argument that happens to be the name of a file or folder (that exists relative to RunElevated.bat) it can get altered.

Resources