Starting a batch file from PowerShell with arguments - windows

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.

Related

Creating a cmd script to save bitlocker numeric id to ad

I am trying to create a bat file to run cmd code to save bitlockers numeric id to ad
the code I got that far is
#echo off
title bitlocker to AD.
echo Bitlocker to ActiveDirectory
pause
powershell -Command manage-bde -protectors -get c:
powershell -Command manage-bde -protectors c: -id {<numericalpassword>}
echo 1)Exit
set input=
set /p input= Choice
if %input%==2 goto Exit if NOT goto Start 2```
`
But what I got when I run it is:
[![what really happened][1]][1]
[1]: https://i.stack.imgur.com/uM3b9.png
basically it cannot recognize the "<numericalpassword>"
How to I get the numerical password id as a string that I can push to the second line?
I have tested in my environment
how to i get the numerical password id as a string that i can push to
the second line ?
You can use the below command to get the numerical password id as a string variablee :
$key = ((manage-bde -protectors -get c:) | Select-String -SimpleMatch "ID: ")[1] -replace "ID:","" -replace " ",""
Now you can use this variable in the second line as follows :
manage-bde -protectors -adbackup c: -id $key
Also, you can write the powershell script for the two powershell commands and run the script in the bat file.
Use the below powershell script :
$key = ((manage-bde -protectors -get c:) | Select-String -SimpleMatch "ID: ")[1] -replace "ID:","" -replace " ",""
manage-bde -protectors -adbackup c: -id $key
Save this script in your local and use this line in your bat file :
PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& 'path-to-your-powershell-script'"
Instead of
powershell -Command manage-bde -protectors -get c:
powershell -Command manage-bde -protectors c: -id {<numericalpassword>}
I'm getting this when I try to run:
PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& 'path-to-your-powershell-script'"
Output:
C:\Users>$key = ((manage-bde -protectors -get c:) | Select-String -SimpleMatch "ID: ")[1] -replace "ID:","" -replace " ",""
'$key' is not recognized as an internal or external command,
operable program or batch file.

Windows powershell error when modifying windows service parameter

I am trying to update one of the parameters of Windows Service (kubelet) by using this link
1. $regkey = "HKLM\SYSTEM\CurrentControlSet\Services\kubelet"
2. $name = "ImagePath"
3. $(reg query ${regkey} /v ${name} | Out-String) -match "(?s)${name}.*(C:.*kubelet\.exe.*)"
4. $kubelet_cmd = $Matches[1] -replace "--image-pull-progress-deadline=.* ","" -replace "\r\n"," "
5. reg add ${regkey} /f /v ${name} /t REG_EXPAND_SZ /d "${kubelet_cmd} --image-pull-progress-deadline=40m "
Step #3 output
PS C:\Users\Administrator> $(reg query ${regkey} /v ${name} | Out-String) -match "(?s)${name}.*(C:.*kubelet\.exe.*)"
True
Output of Steps 4 and 5
PS C:\Users\Administrator> $kubelet_cmd = $Matches[1] -replace "--image-pull-progress-deadline=.* ","" -replace "\r\n"," "
PS C:\Users\Administrator> reg add ${regkey} /f /v ${name} /t REG_EXPAND_SZ /d "${kubelet_cmd} --image-pull-progress-deadline=40m "
ERROR: Invalid syntax.
Type "REG ADD /?" for usage.
I am not sure which part of reg add is causing this error. Kindly let me know what am I doing wrong
EDIT
Output of $kubelet_cmd is given below
PS C:\Users\Administrator> echo $kubelet_cmd
C:\ProgramData\Kubernetes\kubernetes\node\bin\kubelet.exe --windows-service --v=6 --log-dir=C:\ProgramData\Kubernetes\logs\kubelet --cert-dir=C:\var\lib\kubelet\pki --cni-bin-dir=C:\ProgramData\Kubernetes\cni --cni-conf-dir=C:\ProgramData\Kubernetes\cni\config --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --hostname-override=WIN --pod-infra-container-image=mcr.microsoft.com/k8s/core/pause:1.2.0 --enable-debugging-handlers --cgroups-per-qos=false --enforce-node-allocatable="" --logtostderr=false --network-plugin=cni --resolv-conf="" --cluster-dns="10.96.0.10" --cluster-domain=cluster.local --feature-gates=
It appears some of the attributes have double-quotes which is causing the issue. But, not sure how to overcome this when executing Step #5
I resolved the steps by editing Windows registry
Opened Windows registry
Navigated to Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\kubelet hive
Right-click --> Modify on attribute ImagePath (on the right pane)
Added the parameter --image-pull-progress-deadline=50m
Restarted kubelet service using services.msc

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"

Start multiple exe with arguments

I have around 5000 folders each containing a dos executable and required files.
Currently I am using a for loop to call the below code. it takes a long time to execute one by one as each execution takes around 5 seconds.
Is there an option where I can execute all the exe files at the same time ?
Any ideas?
Thanks
I tried using
start "" 1/ddd.exe input.dat
start "" 2/ddd.exe input.dat
start "" 3/ddd.exe input.dat
.
.
.
in a batch file. input.dat has the arguments to pass on to the exe. but the exe opens up a new window and its not taking the arguments. first argument is "2" run to certain part of the exe and second any number to exit the program after it has finished.
You tried
for /D %%a in (*) do (
echo processing: %%a
start /B "Name" cmd.exe "cd %%a & ddy.exe < parameters.txt"
)
I would prefer start /D "%%a" /min "Name" cmd.exe /c "ddy.exe < parameters.txt". /B causes them to use the same console and they may block each other. /D sets the working folder (no need for cd), /min minimizes the windows to keep your screen clean.
And don't forget /c with the cmd command (without, you get no parallel processes).
As a whole:
for /D %%a in (*) do (
echo processing: %%a
start /D "%%a" /min "Name" cmd.exe /c "ddy.exe < parameters.txt"
)
This is a -very- minimalistic script to run N commands at a time from a list. If you are on a supported Windows system, it will have PowerShell.
There is no error checking or proper help information. It writes stdout to the specified log file, but does nothing with the exit code from the command. If something fails, it would need to be identified from the log file.
To use this, put the following code into the file Invoke-JobList.ps1
Create a .csv file with the commands you want to run and a different log file name for each command. The log file name cannot be the same for multiple commands. If you have 5000 commands to process, you will probably need to write a script/program to produce it.
I provided a sample .csv file and a batch file that I used for testing. You do not need to use to.bat.
=== Get-Content .\Invoke-JobList.ps1
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[string[]]$jobFile
,[Parameter(Mandatory=$false)]
[int]$nConcurrent = 2
)
$jobs = Import-Csv -Path $jobFile
$jobHash = #{}
$nJobsRunning = 0
foreach ($job in $jobs) {
if ($nJobsRunning -lt $nConcurrent) {
Write-Verbose -Message "starting command $($job.command)"
$j = Start-Job -ScriptBlock ([ScriptBlock]::Create($job.command))
$jobHash[$j] = $job.logfile
$nJobsRunning++
}
while ($nJobsRunning -ge $nConcurrent) {
# wait for one or more jobs to state Completed
$jobsRunning = Get-Job
foreach ($jobRun in $jobsRunning) {
if (($null -ne $jobHash[$jobRun]) -and ($jobRun.State -eq 'Completed')) {
Receive-Job -Job $jobRun | Out-File -FilePath $jobHash[$jobRun]
Remove-Job -Job $jobRun
$jobHash.Remove($jobRun)
$nJobsRunning--
}
}
}
}
Write-Verbose -Message $($nJobsRunning.ToString() + " remaining jobs")
# Wait for all remaining jobs to complete
while ($nJobsRunning -gt 0) {
$jobsRunning = Get-Job
foreach ($jobRun in $jobsRunning) {
if (($null -ne $jobHash[$jobRun]) -and ($jobRun.State -eq 'Completed')) {
Receive-Job -Job $jobRun | Out-File -FilePath $jobHash[$jobRun]
Remove-Job -Job $jobRun
$jobHash.Remove($jobRun)
$nJobsRunning--
}
}
}
=== Get-Content .\joblist3.csv
command,logfile
C:\src\jobs\to.bat 10,ss-001.txt
C:\src\jobs\to.bat 10,ss-002.txt
C:\src\jobs\to.bat 10,ss-003.txt
C:\src\jobs\to.bat 10,ss-004.txt
C:\src\jobs\to.bat 10,ss-005.txt
C:\src\jobs\to.bat 10,ss-006.txt
C:\src\jobs\to.bat 10,ss-007.txt
=== Get-Content .\to.bat
#ECHO OFF
SET "TO=%1"
IF "%TO%" == "" (SET "TO=5")
REM Cannot use TIMEOUT command
ping -n %TO% localhost
EXIT /B 0
Invoke it with parameters.
.\Invoke-JobList.ps1 -jobFile joblist3.csv -nConcurrent 3 -Verbose

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"
}

Resources