Powershell pass user input to icacls - windows

Currently I am writing a powershell script to automate some security measures and I have run into a small problem.
icacls myDirectory /inheritance:r /grant:r 'Administrators:F' $myGroup + ':F'
will fail as $myGroup gets tacked onto the icacls call and fails to properly add permissions to both groups. This is on Windows 7 and from the context of powershell.

The parser sees $myGroup + ':F' as three individual arguments. You can use either:
"${myGroup}:F"
or
($myGroup + ':F')
to provide this info to the parser as one argument.

Related

Converting .ps1 to .bat doesn't cause cmd.exe to hit max character length - but why?

Based on a related question and some Googling/testing, it is pretty clear that the maximum length of a command line in cmd.exe is 8,191 characters. However...my script seems to successfully exceed that length, and I can't figure out why it works.
So I have:
A script written/saved as a .ps1
I'm converting it to a .bat using the method from Converting PowerShell to Batch
If I run the .bat - there don't seem to be any issues
If I try to echo the $encoded command or just remove #echo off before running the .bat - it is easy to see that the length/content of $encoded has been truncated...yet it works as expected
I realize this is a bit of a strange question since I'm trying to ascertain some kind of hidden truth about something that works vs. something that is broken, but I'd like to understand why it is working!
Note:
The primary reason I'm creating a .bat for my script is because I have a number of small 'programs' that I need to run on my work computer and share with others. Our company's execution policies don't allow running scripts, and it is much easier for others to use something I've written if it is a .bat since they won't need to understand/use PowerShell. They can just place the .bat in the right place, give it two clicks, and then sit back and relax until something happens.
EDIT: Here's the truncated command in the cmd.exe window. If I convert from Base64 back to readable text, it is clearly missing about half of the script. Yet - I'm not noticing any problems
The 8191-character limit applies to the interactive command line and calls to cmd.exe's CLI (via cmd /c)
It does not apply to commands invoked from batch files - there the limit is close to[1] 32KiB (32,768) characters.
However, it still seems to apply selectively to cmd.exe's internal commands, such as echo; by contrast, a call to an external program such as powershell.exe is not affected (though it is possible for specific external programs to have their own, lower limits).
As an aside:
As clever as the linked method of converting PowerShell scripts to batch files is, the resulting batch files lack support for arguments to be passed through to the PowerShell code, and adding support for that would be impractical (it would require Base64-encoding the arguments too, at invocation time, which isn't possible from a batch file except with the help of non-built-in utilities).
If your script / batch file is written to either require no arguments on invocation or to prompt for them interactively (as in your case), this limitation is not a concern.
However, you may want argument support for any of the following reasons:
To allow calling the batch file from cmd.exe (Command Prompt) with arguments.
To allow calling with arguments from other environments that support passing arguments, notably Task Scheduler and the Windows Run dialog (WinKey-R).
To make the batch file support drag-and-drop (which implicitly passes the dropped files' paths as arguments).
As for the overall reason to wrap PowerShell code in batch files:
Batch files (.cmd, .bat) can be launched directly, as if they were executables, system-wide.
By contrast, this is not supported for PowerShell scripts (.ps1), which from outside PowerShell must be invoked via an explicit call to the PowerShell CLI; note that systems may be configured to categorically prevent execution of .ps1 scripts - see next section.
If you do need argument support in your batch file, the best - but inconvenient - solution is to distribute two files:
the original *.ps1 file...
and a companion batch file, with the same base file name, to be placed in the same directory - which can be invoked from outside PowerShell - whose sole purpose is to invoke the *.ps1 file via powershell.exe with pass-through arguments.
E.g. foo.cmd would accompany foo.ps1, with the following - invariant - content:
#powershell.exe -NoProfile -File "%~dpn0.ps1" %*
Note:
Important: The above assumes that the effective PowerShell execution policy allows execution of script files (.ps1).
If you cannot make this assumption, place -ExecutionPolicy RemoteSigned before -NoProfile above (or, to deactivate all checks, -ExecutionPolicy Bypass), but note that an execution policy based on GPO (Group Policy Objects) may still prevent script execution.
To call PowerShell (Core) 7+ instead, use pwsh.exe instead of powershell.exe.
%~dpn0 expands to the full path of the enclosing batch file itself without its filename extension; appending .ps1 therefore targets the companion PowerShell script in the same directory; run cmd /c call /? for an explanation of the syntax.
%* passes any and all arguments received by the batch file on.
[1] In practice, the limits appear to be: 32,767 characters in batch files, and - lower by 3 chars. - 32,764 for Powershell (both on the command line / via the CLI and in *.ps1 scripts). In PowerShell, the limit may in practice be even lower than that, because PowerShell expands the names of executables it locates via $env:PATH to their full paths in the command lines constructed behind the scenes, which cmd.exe doesn't do.

Windows executable that does nothing

Suppose I'm working in a system that wants to execute something in a workflow step, but I want it to do nothing instead.
Unlike the slightly-related question Dummy command in windows cmd, I'm not in any sort of shell (that I'm aware of). I can only point to an executable and provide parameters.
Is there anything in a typical Windows installation that can be executed as a do-nothing placeholder and returns 0 as its exit code?
I could install a dummy script, but I'd rather avoid doing that to maintain some portability.
C:\Windows\System32\cmd.exe /D /C ""
If there's no shell, then %SYSTEMROOT% is likely not available. For a typical, out-of-the-box installation, the above path is expected. Otherwise, customize to fit your environment.
From the help text:
/C Carries out the command specified by string and then terminates
. . .
/D Disable execution of AutoRun commands from registry (see below)
Following up /C with "" executes nothing. Invoking /D prevents any default scripts from running.
Finally, this returns 0, so it won't interrupt the workflow.

On Windows 10, grant a user rights to create, edit and delete files in the folder C:\VMs, but prevent deletion of the folder itself

I need users to be able to access and modify contents in a folder C:\VMs. I'm using https://ss64.com/nt/icacls.html as a guide. At the bottom of the page there is a list of commands to do exactly what I need, but the first command is not working.
In Powershell I am typing
icacls.exe "C:\VMware" /inheritance:r /grant:r testuser:(OI) (CI) (F)
and am getting the error
The term 'OI' is not recognized as the name of a cmdlet...
I've tried various iterations using different spacing and quotations and the likes but have not had any luck.
The problem is that brackets have a special meaning in Powershell. They must be quoted if they are to be passed as-is to an external program. The article you link to assumes that you are using the legacy command shell, which does not give brackets a special meaning in this context.
One way to make this work is to use the backtick as an escape character:
icacls test /grant Everyone:`(OI`)`(CI`)`(RX`)
Since you are running Windows 10, you can use the stop-parsing symbol instead:
icacls test --% /grant Everyone:(OI)(CI)(RX)
The stop-parsing symbol requires Powershell 3 or later, so it will not work out-of-the-box on a Windows 7 machine, but on Windows 10 you'll be fine.

Execute a URL from a command line without opening a browser in windows

So I'm putting together a batch file to run at startup that executes a small number of processes, one of them is to execute a reboot of a certain program at the end of the set of processes. I've been searching ways how to do this in the command line in windows but I need to be able to do this without opening a browser. What I need is to execute the reboot in the following url without opening a browser at all.
http://192.168.1.100/cgi-bin/reboot
Everything that I've tried has opened a new browser window. I don't want to have to download anything to get this going in windows if that's possible. Thanks for any suggestions.
You know how sometimes the answer to the question you ask is not necessarily the answer you need? I have a vague suspicion this might be one of those times.
If this is a Windows machine you're trying to reboot, you can reboot it remotely without needing to use a CGI script served by the remote host. If the account you're logged in with on the triggering PC also has privileges on the remote PC, you can trigger the reboot with the shutdown command.
shutdown /m \\remotePC /r /t 0
Do shutdown /? in a cmd console for more info. Or if you must authenticate, you can use wmic instead.
wmic /node:remotePC /user:remotePCadmin /password:remotePCpass process call create "shutdown -r -t 0"
In case I was mistaken, here's the answer to the question you asked. The fastest way to execute a remote CGI script would be to use an XMLHTTPRequest with Windows Script Host (VBScript or JScript). Here's an example.
#if (#CodeSection == #Batch) #then
#echo off & setlocal
set "URL=http://192.168.1.100/cgi-bin/reboot"
cscript /nologo /e:jscript "%~f0" "%URL%"
goto :EOF
#end // end batch / begin JScript chimera
var x = WSH.CreateObject("Microsoft.XMLHTTP");
x.open("GET",WSH.Arguments(0),true);
x.setRequestHeader('User-Agent','XMLHTTP/1.0');
x.send('');
while (x.readyState != 4) WSH.Sleep(50);
For what it's worth, you can also parse x.responseText as needed. You can scrape it as flat text, or even evaluate it as a hierarchical DOM object. This answer demonstrates such parsing. And if you just can't get enough, here are more examples.
If you'd rather have something simpler at the expense of efficiency, you can invoke a PowerShell command.
#echo off & setlocal
set "URL=http://192.168.1.100/cgi-bin/reboot"
powershell "ipmo BitsTransfer; Start-BitsTransfer \"%URL%\" \"%temp%\a\""
del /q "%temp%\a"
goto :EOF
You could probably alternatively use Invoke-WebRequest to avoid the temporary file, but Invoke-WebRequest requires PowerShell version 3 or newer. Start-BitsTransfer works with PowerShell version 2, so it should work on more computers. One might also use the [System.Net]::WebRequest .NET class, but it gets a little complicated constructing all the objects needed to proceed beyond fetching the HTTP headers to having the web server serve the web page. If you're curious, it looks something like this:
powershell "[void](new-object IO.StreamReader([Net.WebRequest]::Create(\"%URL%\").GetResponse().GetResponseStream())).ReadToEnd()"
Not exactly what I'd call simple. In hybrid format for easier readability:
<# : batch portion
#echo off & setlocal
set "URL=http://192.168.1.100/cgi-bin/reboot"
powershell -noprofile "iex (${%~f0} | out-string)"
goto :EOF
: end batch / begin PowerShell hybrid chimera #>
$request = [Net.WebRequest]::Create($env:URL)
$response = $request.GetResponse()
$stream = $response.GetResponseStream()
$reader = new-object IO.StreamReader($stream)
[void]$reader.ReadToEnd()
In any case, any PowerShell solution will be a second or two slower than the JScript solution near the top of this answer. powershell.exe takes a second or two to load (indeed, several seconds if it's not been loaded since Windows was last rebooted); whereas cscript.exe fires nearly instantly.
If you cannot download anything, then Windows PowerShell is the best option. You can call it from a PS script file, or directly from the command line in a batch file:
powershell.exe -noprofile -command "Invoke-WebRequest -Uri http://192.168.1.100/cgi-bin/reboot"
You can also consider Curl for that type of process. There is no equivalent in Windows, so it will require a download.
curl http://192.168.1.100/cgi-bin/reboot
Curl will not open a browser window, and has great command line options (See curl -help), and will return error codes for batch file error handling.
I don't think Windows comes with that feature.
Try making a VBscript that will open the browser Windows but will not display anything maybe.
To process URL response direct, from Win CMD:
powershell "Invoke-WebRequest -Uri <url>"
To save server response to file:
powershell "Invoke-WebRequest -Uri <url> -outfile <file.ext>"
.NET solution; from Win CMD:
powershell "[System.Net.Webclient]::new().DownloadString(\"<URL>\")"

Using icacls command in Win7 cmd line to set inheritance to (I)

I am trying to set the ACL/inheritance for a folder (or file) as follows:
icacls /inheritance:r /grant:r "builtin\administrators":(I)(F)
However, this produces an error (icacls doesn't recognize the "(I)" as being valid?).
According to icacls /?, (I) is one of the inheritance rights (along with (OI), (CI), (IO), and (NP)).
When I reset the perms on the object, the icacls command shows:
builtin\administrators:(I)(OI)(CI)(F)
So the '(I)' setting IS 'in there'. I suspect I need to go through some hoops to get this where it needs to be (i.e., builtin\administrators:(I)(F)), but after spending the better part of the day searching for examples and trying various routes, I'm still where I started.
Anyone know the trick to this?

Resources