I have created a batch file that reads a .json
file containing npm packages and their version.
Now everything works except concatenating values in 2 variables before writing to file
This is the InstallPackage.json
file:
{
"dependencies": {
"@types/react": "16.3.10",
"react": "16.3.2",
"react-dom": "16.3.2",
"react-scripts": "1.1.4"
},
"devdependencies":{}
}
This is the batch file:
<# : batch portion (contained within a PowerShell multi-line comment)
@echo off & setlocal
setlocal EnableDelayedExpansion
set LF=^
set JSON=
for /f "delims=" %%x in (InstallPackage.json) do (
set "JSON=!JSON!%%x!LF!"
)
rem # re-eval self with PowerShell and capture results
for /f "delims=" %%I in ('powershell "iex (${%~f0} | out-string)"') do set "%%~I"
rem # output captured results
set JSON[
rem # end main runtime
goto :EOF
: end batch / begin PowerShell hybrid code #>
add-type -AssemblyName System.Web.Extensions
$JSON = new-object Web.Script.Serialization.JavaScriptSerializer
$obj = $JSON.DeserializeObject($env:JSON)
# output object in key=value format to be captured by Batch "for /f" loop
foreach ($key in $obj.keys) {
foreach($package in $obj[$key].keys){
### error happens here ###
echo $package@$obj[$key][$package] >> packages.txt
}
}
instead of getting
@types/react@16.3.10
I get:
@types/react@System.Collections.Generic.Dictionary`2[System.String,System.Object][dependencies][@types/react]
It's a simple concatenate don't know why it's not working.
Your PowerShell output line isn't quite PowerShell-ese. It's sort of PowerCmd or something. Replace
echo $package@$obj[$key][$package] >> packages.txt
with
"JSON[{0:d}]={1}@{2}" -f $i++, $package, $obj[$key][$package] >> packages.txt
... if you want to populate the JSON[] array when PowerShell exits back to the Batch environment, or
"{0}@{1}" -f $package, $obj[$key][$package] >> packages.txt
... if you only want the concatenated values sent to packages.txt. And if that's the case, get rid of the for /F
loop within Batch. You can also use gc
within PowerShell to read the JSON, which is much more graceful than trying to populate a Batch variable with a multi-line value.
<# : batch portion (contained within a PowerShell multi-line comment)
@echo off & setlocal
set "JSON=InstallPackage.json"
rem # re-eval self with PowerShell and capture results
powershell -noprofile "iex (${%~f0} | out-string)"
rem # end main runtime
goto :EOF
: end batch / begin PowerShell hybrid code #>
add-type -AssemblyName System.Web.Extensions
$JSON = new-object Web.Script.Serialization.JavaScriptSerializer
$obj = $JSON.DeserializeObject((gc $env:JSON))
# output object in key@value format to packages.txt
foreach ($key in $obj.keys) {
foreach($package in $obj[$key].keys){
"{0}@{1}" -f $package, $obj[$key][$package] >> packages.txt
}
}
And I've got one more recommendation. Instead of writing packages.txt using the append redirect, your intentions would be clearer if you use the out-file
cmdlet. Do you expect packages.txt to be created from scratch on every run? Or will packages.txt continue to grow with multiple runs? If you revisit this code a year from now, will you remember your intentions?
This would be clearer:
# output object in key@value format to packages.txt
&{
foreach ($key in $obj.keys) {
foreach($package in $obj[$key].keys){
"{0}@{1}" -f $package, $obj[$key][$package]
}
}
} | out-file packages.txt
If your intention is to grow packages.txt with successive runs, then use
out-file packages.txt -append
See? This way, packages.txt will explicitly be either overwritten or appended on each run, and will not require any checks whether the file already exists or expectations that the file will not already exist. It's less ambiguous this way.