Can't concatenate value of 2 variables in batch script - json

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.

So much code for something so simple. At least for Xidel:
xidel -s InstallPackage.json -e "let $a:=$json/dependencies return $a() ! join((.,$a(.)),'#')" >> packages.txt
'package.txt':
#types/react#16.3.10
react#16.3.2
react-dom#16.3.2
react-scripts#1.1.4

Related

Modify For loop to go from certain string forward and delete a selection of lines using batch

Ok so i am writing a batch file in which i delete every line containing "," after finding string:
"plugins": {
Is it possible to make this condition in for loop ?
now i know you can avoid quotes using ^ but i just cant make it work.
what i do right now is the following:
#echo OFF
::removePlugins
setlocal enabledelayedexpansion
echo. 2>tempPackage.json
SET #FOUND=0
for /F "delims=" %%G in (./package.json) do (
echo %%G|find "," > nul
if not errorlevel 1 (SET #FOUND=1)
if !#FOUND!==1(
#echo ON
SET #FOUND=0
ECHO: >> tempPackage.json
#echo OFF
)
ECHO %%G>>tempPackage.json
)
move "./tempPackage.json" ".package.json"
So in this case i would only make a new line in every line that contains ",".
So how does one write a for loop that only goes from this string forward and not make new line but delete it?.
the expected result after runing the batch would be :
{
"scripts"{ still the same scripts},
"dependencies"{ still the same dependencies},
"cordova": {
"platforms": [],
"plugins": {}
}
}
}
I tryed to use the code from #Stephan , like this:
#echo OFF
::removePlugins
setlocal enabledelayedexpansion
SET #findPlugins=0
find /i /c "plugins" ./package.json >NUL
if not errorlevel 1 (SET #findPlugins=1)
IF !#findPlugins!==1 (
SET "#FOUND=1"
>tempPackage.json (
for /F "delims=" %%G in (./package.json) do (
echo %%G|findstr /b "}" >nul && SET "#FOUND=1"
if defined #FOUND ECHO %%G
echo %%G|findstr /b "\"plugins\": {" >nul && SET "#FOUND="
)
)
type tempPackage.json
)
And it just rewrote the json to temp and didnt delete anything...what am i doing wrong ?
Keep in mind, batch can't interpret .json files and handles them as pure text. So any batch solution will highly depend on the exact format of the file. Any change in the format (a stray space might be enough) may cause trash.
That said: use a flag that changes at (each) line that starts with "plugins": and changes back when hitting the line starting with } (end of the block) and write the line dependent on the flag:
#echo OFF
::removePlugins
setlocal
SET "#FLAG=1"
>tempPackage.json (
for /F "delims=" %%G in (./package.json) do (
echo %%G|findstr /e "[^{]}" >nul && SET "#FLAG=1"
if defined #FLAG ECHO %%G
echo %%G|findstr "\"plugins\":" >nul && SET "#FLAG="
)
)
type tempPackage.json
This uses a regex to capture everything up to the "plugins" line.
powershell -NoLogo -NoProfile -Command ^
Get-Content -Raw .\pi.txt ^| ^
ForEach-Object { if ($_ -match '(?sm)(.*\"plugins\": {).*') { $Matches[1] + \"`n`n}\" ^| Out-File -FilePath '.package.json'}}

How can I write a batch file using jq to find json files with certain attribute and copy the to new location

I have 100,000's of lined json files that I need to split out based on whether or not, they contain a certain value for an attribute and then I need to convert them into valid json that can be read in by another platform.
I'm using a batch file to do this and I've managed to convert them into valid json using the following:
for /r %%f in (*.json*) do jq -s -c "." "%%f" >> "C:\Users\me\my-folder\%%~nxf.json"
I just can't figure out how to only copy the files that contain a certain value. So logic should be:
Look at all the files in the folders and sub solders
If the file contains an attribute "event" with a value of "abcd123"
then: convert the file into valid json and persist it with the same filename over to location "C:\Users\me\my-folder\"
else: ignore it
Example of files it should select:
{"name":"bob","event":"abcd123"}
and
{"name":"ann","event":"abcd123"},{"name":"bob","event":"8745LLL"}
Example of files it should NOT select:
{"name":"ann","event":"778PPP"}
and
{"name":"ann","event":"778PPP"},{"name":"bob","event":"8745LLL"}
Would love help to figure out the filtering part.
Since there are probably more file names than will fit on the command line, this response will assume a shell loop through the file names will be necessary, as the question itself envisions. Since I'm currently working with a bash shell, I'll present a bash solution, which hopefully can readily be translated to other shells.
The complication in the question is that the input file might contain one or more valid JSON values, or one or more comma-separated JSON values.
The key to a simple solution using jq is jq's -e command-line option, since this sets the return code to 0 if and only if
(a) the program ran normally; and (b) the last result was a truthy value.
For clarity, let's encapsulate the relevant selection criterion in two bash functions:
# If the input is a valid stream of JSON objects
function try {
jq -e -n 'any( inputs | objects; select( .event == "abcd123") | true)' 2> /dev/null > /dev/null
}
# If the input is a valid JSON array whose elements are to be checked
function try_array {
jq -e 'any( .[] | objects; select( .event == "abcd123") | true)' 2> /dev/null > /dev/null
}
Now a comprehensive solution can be constructed along the following lines:
find . -type f -maxdepth 1 -name '*.json' | while read -r f
do
< "$f" try
if [ $? = 0 ] ; then
echo copy $f
elif [ $? = 5 ] ; then
(echo '['; cat "$f"; echo ']') | try_array
if [ $? = 0 ] ; then
echo copy $f
fi
fi
done
Have you considered using findstr?
%SystemRoot%\System32\findstr.exe /SRM "\"event\":\"abcd123\"" "C:\Users\me\my-folder\*.json"
Please open a Command Prompt window, type findstr /?, press the ENTER key, and read its usage information. (You may want to consider the /I option too, for instance).
You could then use that within another for loop to propagate those files into a variable for your copy command.
batch-file example:
#For /F "EOL=? Delims=" %%G In (
'%SystemRoot%\System32\findstr.exe /SRM "\"event\":\"abcd123\"" "C:\Users\me\my-folder\*.json"'
) Do #Copy /Y "%%G" "S:\omewhere Else"
cmd example:
For /F "EOL=? Delims=" %G In ('%SystemRoot%\System32\findstr.exe /SRM "\"event\":\"abcd123\"" "C:\Users\me\my-folder\*.json"') Do #Copy /Y "%G" "S:\omewhere Else"

Can i access 'dxdiag ' from powershell console

How can I access "dxdiag" with powershell . I want to run a script for gather some information from a few remote computers.
If you use the /x parameter, you can have dxdiag output to an xml file, which is then really easily parsed from powershell. Basically just something like this:
# Drop output in temp dir
$logFile = $env:TEMP + "\dxDiagLog.xml"
# Piping to Out-Null forces it to wait for dxdiag to complete before continuing. Otherwise
# it tries to load the file before it actuallygets written
dxdiag.exe /whql:off /dontskip /x $logFile | Out-Null
[xml]$dxDiagLog = Get-Content $logFile
$dxDiagLog.DxDiag.DirectSound.SoundDevices.SoundDevice | ft Description, DriverProvider
Which dumps this for output on my machine:
Description DriverProvider
----------- --------------
Speakers (Realtek High Definition Audio) Realtek Semiconductor Corp.
Polycom CX700 (Polycom CX700) Microsoft
In my case the command would run, and only later would it create the file.
(& dxdiag.exe /whql:off /dontskip /t `"$path`") | Out-Null
The problem is with the ampersand & which made the command exit before completion.
So either use:
dxdiag.exe /whql:off /dontskip /x $logFile | Out-Null
Or:
Start-Process -FilePath "C:\Windows\System32\dxdiag.exe" -ArgumentList "/dontskip /whql:off /t C:\Temp\dxdiag.txt" -Wait
From: https://community.spiceworks.com/topic/2116806-how-can-i-run-dxdiag-on-a-remote-pc

Powershell Copy Directories - Using A CSV Listing - Source - Destination Path

I am trying to Copy Directories - folders and Sub folders to another location using a CSV file that lists the source and destination of each directory or folder to be copied.
The Contents of the CSV are as such below:
I have referenced this thread:
https://serverfault.com/questions/399325/copying-list-of-files-through-powershell
Import-CSV C:\Users\WP\Desktop\a.csv | foreach{Copy-item "$_.Source" "$_.Destination"}
Error Received
CategoryInfo : ObjectNotFound: (#{Source=C:String) [Copy-Item], DriveNotFoundException
+ FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.CopyItemCommand
The other question I have is if in the CSV I want to copy to a folder that does not exists in the destination - can I use the CSV to command powershell to create the folder?
Thank you for your advice.
PowerShell will not expand the variable and access the property of the object inside the variable if you have them placed in double quotes by default. Only the '$_' is being expanded and '.source' is being tacked on to the end of the string, so from the view of the shell, your command looks something like Copy-item "{source=C:\Users\WP\Desktop\a;Destination=C:\Users\WP\Desktop\a}.Source" "{source=C:\Users\WP\Desktop\a;Destination=C:\Users\WP\Desktop\a}.Destination", which is probably not what you mean.
Here is the syntax that should work (I also included -Recurse so that it will copy the items inside the directory as well)
Import-CSV C:\Users\WP\Desktop\a.csv | foreach{Copy-item -Path $_.Source -Destination $_.Destination -Recurse}
Note: if you want to access the properties on an object inside of double quotes, use this syntax "$($_.source)".
For a csv like this:
Source,Destination
D:\junk\test1,D:\junk\test3
D:\junk\test2,D:\junk\test4
You can use code like the following:
$csv = Import-Csv D:\junk\test.csv
$csv | ForEach-Object {
if (-not (Test-Path $_.Destination)) {
New-Item -Name $_.Destination -ItemType Directory -Force -WhatIf
}
Copy-Item $_.Source $_.Destination -Recurse -Force -WhatIf
}
Suggestions for learning more about PowerShell:
Use WhatIf to test things.
Research what each line of this code does.
Experiment with code to see what it does.
Learn and use the debugger (PowerShell ISE) to help you write better code.
Remove the WhatIf parameters from the code to have it execute for real...
If you have dozens of problems that all involve doing the same thing with each element of a list, you might want to consider getting or writing a generic CSV template expander tool, like Expand-csv. With this tool you start with a CSV file and a template, and generate a script that contains all the commands.
Sample.csv looks like this:
Source,Destination
C:\Users\WP\Desktop\a,C:\Users\WP\Desktop\c
C:\Users\WP\Desktop\b,C:\Users\WP\Desktop\d
Sample.tmplt looks like this:
Copy-Item -Path $Source -Destination $Destination -Recurse
And the command to invoke Expand-csv looks like this:
Expand-csv Sample.csv Sample.tmplt > Sample.ps1
The output file, Sample.ps1 contains one copy command for each entry in the CSV file
And here is the definition of Expand-csv:
<# This function is a table driven template tool.
It's a refinement of an earlier attempt.
It generates output from a template and
a driver table. The template file contains plain
text and embedded variables. The driver table
(in a csv file) has one column for each variable,
and one row for each expansion to be generated.
5/13/2015
#>
function Expand-csv {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string] $driver,
[Parameter(Mandatory=$true)]
[string] $template
)
Process
{
$OFS = "`r`n"
$list = Import-Csv $driver
[string]$pattern = Get-Content $template
foreach ($item in $list) {
foreach ($key in $item.psobject.properties) {
Set-variable -name $key.name -value $key.value
}
$ExecutionContext.InvokeCommand.ExpandString($pattern)
}
}
}

How can I use command line arguments to control a batch program that prints multi-colour text?

I have a batch script that can display two or more colors of text on the same line in the command prompt. (below)
#echo off
SETLOCAL EnableDelayedExpansion
for /F "tokens=1,2 delims=#" %%a in ('"prompt #$H#$E# & echo on & for %%b in (1) do rem"') do (
set "DEL=%%a"
)
echo say the name of the colors, don't read
call :ColorText 0a "blue"
call :ColorText 0C "green"
call :ColorText 0b "red"
echo(
call :ColorText 19 "yellow"
call :ColorText 2F "black"
call :ColorText 4e "white"
pause
goto :eof
:ColorText
echo off
<nul set /p ".=%DEL%" > "%~2"
findstr /v /a:%1 /R "^$" "%~2" nul
del "%~2" > nul 2>&1
goto :eof
however that text must be entered in the script before-hand by editing the batch file with notepad. i'd like to be able to just open the command prompt and type something like:
cecho /blue hello world!
or
cecho blue "hello world!"
or something simple where i can supply the color (preferably as a string not a color code) and text (with or without quotations).
I don't know if this is of any use to you but it is possible to save this part of the script:
echo off
<nul set /p ".=%DEL%" > "%~2"
findstr /v /a:%1 /R "^$" "%~2" nul
del "%~2" > nul 2>&1
goto :eof
(from ":ColorText" to the end of the script)
and save it as "ColorText.bat" in "C:\windows\system32". Then in the other half of the script, everywhere you see:
call :ColorText
Change it to:
call ColorText
(Omit the colons)
And save that script as colors.bat in "C:\windows\system32". Then open the command prompt and type "colors". This is how i want it to function; no additional commands, setup scripts, file paths; just a simple one or two word function with all that messy code going on in the background (out of sight). However the above idea still won't let me specify my own text or color from the command prompt.... any ideas?
EDIT: Take 3
Create the folder C:\Utilities. Add this folder to your Path environment variable so Windows looks there for additional scripts and commands.
Open Control Panel, System, Advanced System Settings (or "Advanced" in Windows XP), Environment Variables.
In the "System variables" list, select the "Path" variable.
Do not mess these next steps up!
Press Edit.
Place the cursor at the end of the line and make sure no text is selected.
Add the text ;C:\Utilities, including the semi-colon. Do not remove any other text.
Press OK.
Breathe easy again.
Press OK as many times as necessary to close all windows.
Take the script following the :ColorText label and save it to C:\Utilities\cecho.bat. Put an # in front of echo off to prevent echo off from appearing during the script.
CEcho.bat
#Echo Off
SetLocal EnableDelayedExpansion
For /F "tokens=1,2 delims=#" %%a In ('"Prompt #$H#$E# & Echo On & For %%b In (1) Do Rem"') Do (
Set "DEL=%%a"
)
<Nul Set /p ".=%DEL%" > "%~2"
FindStr /v /a:%1 /R "^$" "%~2" Nul
Del "%~2" > Nul 2>&1
EndLocal
Now you can use this command from any command line or script. Usage:
CEcho color "text"
Edit: In response to your comment:
You can use words for colours by inserting the following lines and replacing the FindStr line:
Set Color=%1
If %1==blue Set Color=9
If %1==red Set Color=C
etc...
FindStr /v /a:%Color% /R "^$" "%~2" Nul
Now you can type:
CEcho red "apple"
CEcho blue "water"
CEcho A "grass"
CEcho 6 "dirt"
CEcho 26 "tree"
Note that the color word is case sensitive.
You could use the batch parameters %1, %2, ..., %n, as parameter for the color and for the content
cecho 0a "hello world!"
#echo off
call :ColorText %1 "%~2"
...
If you want to use the color names you have to convert them to the corresponding number.
cecho blue "hello"
#echo off
if "%1"=="red" set color=0c
if "%1"=="blue" set color=0b
if ...
call :ColorText %color% "%~2"
A fast alternative to color efficiently with cmd batch since Windows XP by using PowerShell as a subprocess linked to the console output through a named pipe. It can be done with FindStr too, but PowerShell offers more options and seems quicker.
The main interest in keeping PowerShell as a subprocess, using a pipe to communicate, is that the display is far more faster than launching PowerShell or FindStr for each line to display.
Other good points :
No need for temporary files
Echoing though a pipe permits the display of the full ASCII table without bothering escapes.
Works fine with fd redirection. To color only stderr as example, or to redirect to a file / other process.
Here is a sample code for doing that :
::
:: Launch a PowerShell child process in the background linked to the console and
:: earing through named pipe PowerShellCon_%PID%
::
:: Parameters :
:: [ PID ] : Console Process ID used as an identifier for the named pipe, launcher PID by default.
:: [ timeout ] : Subprocess max life in seconds, 300 by default. If -1, the subprocess
:: will not terminate while the process %PID% is still alive.
:: Return :
:: 0 if the child PowerShell has been successfully launched and the named pipe is available.
:: 1 if it fails.
:: 2 if we can't get a PID.
:: 3 if PowerShell is not present or doesn't work.
::
:LaunchPowerShellSubProcess
SET LOCALV_PID=
SET LOCALV_TIMEOUT=300
IF NOT "%~1" == "" SET LOCALV_PID=%~1
IF NOT "%~2" == "" SET LOCALV_TIMEOUT=%~2
powershell -command "$_" 2>&1 >NUL
IF NOT "!ERRORLEVEL!" == "0" EXIT /B 3
IF "!LOCALV_PID!" == "" (
FOR /F %%P IN ('powershell -command "$parentId=(Get-WmiObject Win32_Process -Filter ProcessId=$PID).ParentProcessId; write-host (Get-WmiObject Win32_Process -Filter ProcessId=$parentId).ParentProcessId;"') DO (
SET LOCALV_PID=%%P
)
)
IF "!LOCALV_PID!" == "" EXIT /B 2
START /B powershell -command "$cmdPID=$PID; Start-Job -ArgumentList $cmdPID -ScriptBlock { $ProcessActive = $true; $timeout=!LOCALV_TIMEOUT!; while((!LOCALV_TIMEOUT! -eq -1 -or $timeout -gt 0) -and $ProcessActive) { Start-Sleep -s 1; $timeout-=1; $ProcessActive = Get-Process -id !LOCALV_PID! -ErrorAction SilentlyContinue; } if ($timeout -eq 0 -or ^! $ProcessActive) { Stop-Process -Id $args; } } | Out-Null ; $npipeServer = new-object System.IO.Pipes.NamedPipeServerStream('PowerShellCon_!LOCALV_PID!', [System.IO.Pipes.PipeDirection]::In); Try { $npipeServer.WaitForConnection(); $pipeReader = new-object System.IO.StreamReader($npipeServer); while(($msg = $pipeReader.ReadLine()) -notmatch 'QUIT') { $disp='write-host '+$msg+';'; invoke-expression($disp); $npipeServer.Disconnect(); $npipeServer.WaitForConnection(); }; } Finally { $npipeServer.Dispose(); }" 2>NUL
SET /A LOCALV_TRY=20 >NUL
:LaunchPowerShellSubProcess_WaitForPipe
powershell -nop -c "& {sleep -m 50}"
SET /A LOCALV_TRY=!LOCALV_TRY! - 1 >NUL
IF NOT "!LOCALV_TRY!" == "0" cmd /C "ECHO -NoNewLine|MORE 1>\\.\pipe\PowerShellCon_!LOCALV_PID!" 2>NUL || GOTO:LaunchPowerShellSubProcess_WaitForPipe
IF "!LOCALV_TRY!" == "0" EXIT /B 1
EXIT /B 0
This "code" is written with delayed expansion ON but can be rewrite to work without it. There is many security points to consider, do not use it directly in the wild.
How to use it :
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS
IF ERRORLEVEL 1 (
ECHO Extension inapplicable
EXIT /B 1
)
::
SETLOCAL ENABLEDELAYEDEXPANSION
IF ERRORLEVEL 1 (
ECHO Expansion inapplicable
EXIT /B 1
)
CALL:LaunchPowerShellSubProcess
IF NOT ERRORLEVEL 0 EXIT /B 1
CALL:Color Cyan "I write this in Cyan"
CALL:Blue "I write this in Blue"
CALL:Green "And this in green"
CALL:Red -nonewline "And mix Red"
CALL:Yellow "with Yellow"
CALL:Green "And not need to trouble with ()<>&|;,%""^ and so on..."
EXIT /B 0
:Color
ECHO -foregroundcolor %*>\\.\pipe\PowerShellCon_!LOCALV_PID!
ECHO[|SET /P=>NUL
GOTO:EOF
:Blue
ECHO -foregroundcolor Blue %*>\\.\pipe\PowerShellCon_!LOCALV_PID!
ECHO[|SET /P=>NUL
GOTO:EOF
:Green
ECHO -foregroundcolor Green %*>\\.\pipe\PowerShellCon_!LOCALV_PID!
ECHO[|SET /P=>NUL
GOTO:EOF
:Red
ECHO -foregroundcolor Red %*>\\.\pipe\PowerShellCon_!LOCALV_PID!
ECHO[|SET /P=>NUL
GOTO:EOF
:Yellow
ECHO -foregroundcolor Yellow %*>\\.\pipe\PowerShellCon_!LOCALV_PID!
ECHO[|SET /P=>NUL
GOTO:EOF
::
:: Launch a PowerShell child process in the background linked to the console and
:: earing through named pipe PowerShellCon_%PID%
::
:: Parameters :
:: [ PID ] : Console Process ID used as an identifier for the named pipe, launcher PID by default.
:: [ timeout ] : Subprocess max life in seconds, 300 by default. If -1, the subprocess
:: will not terminate while the process %PID% is still alive.
:: Return :
:: 0 if the child PowerShell has been successfully launched and the named pipe is available.
:: 1 if it fails.
:: 2 if we can't get a PID.
:: 3 if PowerShell is not present or doesn't work.
::
:LaunchPowerShellSubProcess
SET LOCALV_PID=
SET LOCALV_TIMEOUT=300
IF NOT "%~1" == "" SET LOCALV_PID=%~1
IF NOT "%~2" == "" SET LOCALV_TIMEOUT=%~2
powershell -command "$_" 2>&1 >NUL
IF NOT "!ERRORLEVEL!" == "0" EXIT /B 3
IF "!LOCALV_PID!" == "" (
FOR /F %%P IN ('powershell -command "$parentId=(Get-WmiObject Win32_Process -Filter ProcessId=$PID).ParentProcessId; write-host (Get-WmiObject Win32_Process -Filter ProcessId=$parentId).ParentProcessId;"') DO (
SET LOCALV_PID=%%P
)
)
IF "!LOCALV_PID!" == "" EXIT /B 2
START /B powershell -command "$cmdPID=$PID; Start-Job -ArgumentList $cmdPID -ScriptBlock { $ProcessActive = $true; $timeout=!LOCALV_TIMEOUT!; while((!LOCALV_TIMEOUT! -eq -1 -or $timeout -gt 0) -and $ProcessActive) { Start-Sleep -s 1; $timeout-=1; $ProcessActive = Get-Process -id !LOCALV_PID! -ErrorAction SilentlyContinue; } if ($timeout -eq 0 -or ^! $ProcessActive) { Stop-Process -Id $args; } } | Out-Null ; $npipeServer = new-object System.IO.Pipes.NamedPipeServerStream('PowerShellCon_!LOCALV_PID!', [System.IO.Pipes.PipeDirection]::In); Try { $npipeServer.WaitForConnection(); $pipeReader = new-object System.IO.StreamReader($npipeServer); while(($msg = $pipeReader.ReadLine()) -notmatch 'QUIT') { $disp='write-host '+$msg+';'; invoke-expression($disp); $npipeServer.Disconnect(); $npipeServer.WaitForConnection(); }; } Finally { $npipeServer.Dispose(); }" 2>NUL
SET /A LOCALV_TRY=20 >NUL
:LaunchPowerShellSubProcess_WaitForPipe
powershell -nop -c "& {sleep -m 50}"
SET /A LOCALV_TRY=!LOCALV_TRY! - 1 >NUL
IF NOT "!LOCALV_TRY!" == "0" cmd /C "ECHO -NoNewLine|MORE 1>\\.\pipe\PowerShellCon_!LOCALV_PID!" 2>NUL || GOTO:LaunchPowerShellSubProcess_WaitForPipe
IF "!LOCALV_TRY!" == "0" EXIT /B 1
EXIT /B 0
Link to my original answer on the same topic.