Re-acl from text file - powershell

This one is stumping me. I am trying to do this with PowerShell, but it does not have to be...
Basically, I have a text file containing ACLs information for a large directory structure that has been migrated (I realize there is much to be desired from the contents, but we no longer can access the original filesystem, so this is what we have to work with).
I need to generate a batch file to re-acl the new (copied) file system.
So in a nutshell, I need to convert text like this (sorry for the scroll, but I am trying to preserve the line spacing):
Path: \\Share.Domain.com\Directory01$\Subdirectory01
AccessToString : DOMAIN\Group01-Knuckleheads Allow ReadAndExecute, Synchronize
BUILTIN\Administrators
Path: \\Share.Domain.com\Directory02$\Subdirectory01
AccessToString : DOMAIN\Different-Group02 Allow FullControl
BUILTIN\Administrators Allow FullControl
Into new files (or the same files, doesn't matter really) with the content like this:
ICacls "\\Share.Domain.com\Directory01$\Subdirectory01" /Grant "DOMAIN\Group01-Knuckleheads":(OI)(CI)R,X,S /t /c /l /q /inheritance:r
ICacls "\\Share.Domain.com\Directory01$\Subdirectory01" /Grant "BUILTIN\Administrators":(OI)(CI)F /t /c /l /q /inheritance:r
ICacls "\\Share.Domain.com\Directory02$\Subdirectory01" /Grant "DOMAIN\Different-Group02":(OI)(CI)F /c /l /q /inheritance:r
ICacls "\\Share.Domain.com\Directory02$\Subdirectory01" /Grant "BUILTIN\Administrators":(OI)(CI)F /c /l /q /inheritance:r
My semi-pathetic attempt to work this out are still progressing, but I realize that this foe is greater than my mojo:
#$ACLs = Get-Content C:\Scripts\Test\AndTest.txt
#ForEach-Object ($ACL in $ACLs)
#{
#Figure out how to break strings into variables....
#}
#Declare Var - Need to populate from the imported text file
$FilePath = "\\Share.Domain.com\Directory01$\Subdirectory01"
$GroupName = "DOMAIN\Domain Admins"
$TestPerm = "ReadAndExecute"
If ($TestPerm = "FullControl"){$Perms = "F"}
Elseif ($TestPerm = "ReadAndExecute"){$Perms = "RX"}
Elseif ($TestPerm = "Modify"){$Perms = "M"}
Elseif ($TestPerm = "Deny"){$Perms = "D"}
Elseif ($TestPerm = "Read"){$Perms = "R"}
Elseif ($TestPerm = "Write"){$Perms = "W"}
cls
#Build icacls string -Test Output
Write-Host "ICacls ""$FilePath"" /Grant ""$GroupName"":(OI)(CI)$Perms /t /c /l /q /inheritance:r"
#Write icacls batch file
#Out-File "C:\Scripts\Test\re-acl.cmd" "ICacls ""$FilePath"" /Grant ""$GroupName"":(OI)(CI)$Perms /t /c /l /q /inheritance:r"
I realize that there is a bunch of work to do here & I am just starting to figure this out. For example, I am thinking I need to list the permissions into an array and build the string as such. There are also bound to be special permissions etc...
But for now, I am trying to figure out how to import the text file and then break it down into variables... Like I said, I am working in Powershell, but it really can be anything... VB or Python perhaps?
Big thanks in advance!

Ok, I think most of your issue is parsing the text, so that's mainly what I helped with here. I did build the strings, and if you have more permissions that you want to include you should be able to figure out how to add them to the switch. For this we will want to read the entire text file in as a large multi-line string. Then we split it up into chunks based on the keyword "Path:". Then for each record I get the path out as a string, grab the permissions, and parse out accounts, allow/deny, and individual accesses. Then I convert the accesses to short versions accepted by icacls, and build the arguments out as a formatted string.
I output to the host, but once you are satisfied that it looks right to you, you can remove the Write-Host " and the trailing " and it will just execute it (assuming icacls is in the same folder that you're in, or in the PATH environment variable).
$Text = Get-Content C:\Scripts\Test\AndTest.txt -Raw
$Records = $Text -split "(?s)(Path:.*?)(?=Path:|$)"|?{$_}
#Loop through each file/ACL
ForEach($Record in $Records){
$FilePath = ($Record -split "[\r\n]+")[0].Substring(6)
$PermRecords = ($Record -split "(?s)AccessToString : "|Select -skip 1) -split "[\r\n]+"|?{$_}
$Perms = $PermRecords|?{$_ -match "(.*\\.*?)\s+(Allow|Deny)(.*)$"}|%{[pscustomobject]#{'Account'=$Matches[1].Trim();'Type'=$Matches[2];'Perms'=$Matches[3].Trim()}}
#Loop through each perm for the current file
$Perms | %{
#Convert friendly names to abbreviations
$ShortPerms = ''
Switch -regex ($_.Perms){
"FullControl" {$ShortPerms = "F";Continue}
"ReadAndExecute" {$ShortPerms += "RX,"}
"Synchronize" {$ShortPerms += "S,"}
"Modify" {$ShortPerms += "M,"}
"Read(?=,|$)" {$ShortPerms += "R,"}
"Write" {$ShortPerms += "W,"}
}
$ShortPerms = $ShortPerms.TrimEnd(',')
$Arguments = '"{0}" /{4} "{1}":(OI)(CI)({2}) /t /c /l /q /inheritance:r' -f $FilePath, $_.Account, $ShortPerms,$(If($_.Type -eq 'Allow'){'Grant'}else{'Deny'})
write-host "& ICAcls $Arguments"
}
}
If the -Raw argument doesn't work for you, you can work around that with
(Get-Content C:\Scripts\Test\AndTest.txt) -join "`r`n"
Let me know if you have questions or issues.

Related

xcopy show only when files have been copied

I have made a script using xcopy that generates a csv log file with the date to check that my copies are done.
I would like to be able to display only when it has copied files, and not display anything when there are 0 files copied.
How can I do this?
if I didn't make myself clear let me know
Thanks :)
Here is my code:
$Logfile = "C:\Users\Name\Documents\Power\"
Function LogWrite
{
Param ([string]$logstring)
Add-content $Logfile -value $logstring and
}
function Get-TimeStamp
{
return "[{0:dd/MM/yy} {0:HH:mm:ss}]" -f (Get-Date)
}
xcopy /s /f /y /b /d C:\Users\Name\Documents\SourceBack C:\Users\Name\Documents\DestBack
>> xcopy.csv
Write-Output "last copied file(s) on $(Get-TimeStamp)" | Out-file
C:\Users\Name\Documents\Power\xcopy.csv -append
I think this is what you're looking for.
The output of xcopy is redirected to a temporary file. If that file contains only a single line (e.g. 0 File(s) copied), no further action is taken.
Otherwise, the output and an additional line with the timestamp is added to your CSV file.
At the end, regardless of the outcome, the temporary file is removed.
$Logfile = "C:\Users\Name\Documents\Power\xcopy.csv"
$TempFile = New-TemporaryFile
try {
Start-Process xcopy `
-ArgumentList '/s','/f','/y','/b','/d','C:\Users\Name\Documents\SourceBack','C:\Users\Name\Documents\DestBack' `
-RedirectStandardOutput $TempFile.FullName `
-Wait
$ProcessOutput = #(Get-Content $TempFile)
if ($ProcessOutput.Length -gt 1) {
($ProcessOutput + "last copied file(s) on $(Get-Date -Format '[dd/MM/yy HH:mm:ss]')") -join "`n" | Add-Content $Logfile
}
} finally {
Remove-Item $TempFile -Force
}
Remarks:
I removed the Get-TimeStamp function, as it was only used once and doesn't add a lot of benefit in that case.
I also removed the LogWrite function as it isn't used in your sample code, and it contains a syntax error (stray and).
You're appending a line to a CSV file (last copied file(s) …), which is bound to cause issues when you try to parse that file later on.
Update
You're never too old to learn, and it seems that Start-Process isn't necessary at all. For more information, see here and here.
$Logfile = "C:\Users\Name\Documents\Power\xcopy.csv"
[string[]] $Output = & xcopy /s /f /y /b /d C:\Users\Name\Documents\SourceBack C:\Users\Name\Documents\DestBack 2>&1
if ($ProcessOutput.Length -gt 1) {
($ProcessOutput + "last copied file(s) on $(Get-Date -Format '[dd/MM/yy HH:mm:ss]')") -join "`n" | Add-Content $Logfile
}

Robocopy didn't recognize options when launched from PowerShell

I'm trying to use the PowerShell cmdlet Invoke-Expression to launch RoboCopy.
In the script below, RoboCopy worked fine when the option was simply '.' but as soon as the option '/MIR' was added I got this "Invalid Parameter #3" error.
It seems that RoboCopy is having problems parsing '/MIR' and has choked on the forward slash in the option. I've tried using all sort of escaping characters to no avail!
# Source & Destination paths
#
[string]$srcPath = 'C:\folderSrc'
[string]$desPath = 'C:\folderDes'
# Example 1
# ----------
# This works - note how $option1 contains only '*.*'
#
[string]$option1 = '*.*'
[string]$line = 'RoboCopy $srcPath $desPath $option1'
Invoke-Expression "$line"
# Example 2:
# ----------
# This doesn't work - after '/MIR' is added to the option, RoboCopy seems to choke on the forward slash in '/MIR'
#
[string]$option2 = '*.* /MIR'
[string]$line = 'RoboCopy $srcPath $desPath $option2'
Invoke-Expression "$line"
I found that this (using double InvokeExpression) worked:
[string]$srcPath = 'C:\folderSrc'
[string]$desPath = 'C:\folderDes'
[string]$option = '*.* /MIR'
[string]$line = 'Invoke-Expression "RoboCopy $srcPath $desPath $option"'
Invoke-Expression "$line"
But couldn't explain why this (using single Invoke-Expression) also works:
[string]$srcPath = 'C:\folderSrc'
[string]$desPath = 'C:\folderDes'
[string]$option = '*.*'
[string]$line = 'RoboCopy $srcPath $desPath $option'
Invoke-Expression "$line"
Note that the sole difference in the 2 scenarios is the $option variable:
'*.*' vs. '*.* /MIR'
Inconsistency like this is utterly demoralizing...
Powershell doesn't expand $variables when using single quotes.
Use double quotes here:
[string]$line = "RoboCopy $srcPath $desPath $option1"
And it might make better sense to not use Invoke-Expression
RoboCopy.exe $srcPath $desPath *.* /MIR
Should work

Powershell with Robocopy and Arguments Passing

I'm trying to write a script that uses robocopy. If I were just doing this manually, my command would be:
robocopy c:\hold\test1 c:\hold\test2 test.txt /NJH /NJS
BUT, when I do this from powershell, like:
$source = "C:\hold\first test"
$destination = "C:\hold\second test"
$robocopyOptions = " /NJH /NJS "
$fileList = "test.txt"
robocopy $source $destination $fileLIst $robocopyOptions
I get:
-------------------------------------------------------------------------------
ROBOCOPY :: Robust File Copy for Windows
-------------------------------------------------------------------------------
Started : Fri Apr 10 09:20:03 2015
Source - C:\hold\first test\
Dest - C:\hold\second test\
Files : test.txt
Options : /COPY:DAT /R:1000000 /W:30
------------------------------------------------------------------------------
ERROR : Invalid Parameter #4 : " /NJH /NJS "
However, if I change the robocopy command to
robocopy $source $destination $fileLIst /NJH /NJS
everything runs successfully.
So, my question is, how can I pass a string as my robocopy command options (and, in a larger sense, do the same for any given external command)
Start robocopy -args "$source $destination $fileLIst $robocopyOptions"
or
robocopy $source $destination $fileLIst $robocopyOptions.split(' ')
Use the arrays, Luke. If you specify an array of values, PowerShell will automatically expand them into separate parameters. In my experience, this is the most reliable method. And it doesn't require you to mess with the Start-Process cmdlet, which is in my opinion is overkill for such tasks.
This trick is from the best article I've seen on the PowerShell behavior towards external executables: PowerShell and external commands done right.
Example:
$source = 'C:\hold\first test'
$destination = 'C:\hold\second test'
$robocopyOptions = #('/NJH', '/NJS')
$fileList = 'test.txt'
$CmdLine = #($source, $destination, $fileList) + $robocopyOptions
& 'robocopy.exe' $CmdLine
You can't use a string to pass options in that way because when you write
robocopy $source $destination $fileList $robocopyOptions
PowerShell will evaluate the last variable ($robocopyOptions) as a single string and it will quote it. This means robocopy will get "/NJH /NHS" (single string, quoted) on its command line. (Obviously not the intent.)
For details on how to work around these kinds of issues, see here:
http://windowsitpro.com/powershell/running-executables-powershell
The article includes the following function:
function Start-Executable {
param(
[String] $FilePath,
[String[]] $ArgumentList
)
$OFS = " "
$process = New-Object System.Diagnostics.Process
$process.StartInfo.FileName = $FilePath
$process.StartInfo.Arguments = $ArgumentList
$process.StartInfo.UseShellExecute = $false
$process.StartInfo.RedirectStandardOutput = $true
if ( $process.Start() ) {
$output = $process.StandardOutput.ReadToEnd() `
-replace "\r\n$",""
if ( $output ) {
if ( $output.Contains("`r`n") ) {
$output -split "`r`n"
}
elseif ( $output.Contains("`n") ) {
$output -split "`n"
}
else {
$output
}
}
$process.WaitForExit()
& "$Env:SystemRoot\system32\cmd.exe" `
/c exit $process.ExitCode
}
}
This function will let you run an executable in the current console window and also let you build an array of string parameters to pass to it.
So in your case you could use this function something like this:
Start-Executable robocopy.exe $source,$destination,$fileList,$robocopyOptions
Putting the options in separate arguments worked for me. Using Robocopy for copying excluding any CSV files.
$roboCopyPath = $env:ROBOCOPY_PATH
$otherLogsPath = [System.IO.Path]::Combine($basePath, "Logs-Other")
$atrTestResults = [System.IO.Path]::Combine($Release, $BuildNumber)
$ResultsSummary = [System.IO.Path]::Combine($basePath, "Result")
$robocopyOptions = #("/log:$otherLogsPath\robocopy.log", '/xf', '*.csv')
$CmdLine = #($atrTestResults, $ResultsSummary) + $robocopyOptions
&$roboCopyPath $CmdLine

Executing external command from PowerShell is not accepting a parameter

I am executing the following code attempting to execute the 7z.exe command to unzip files.
$dir contains the user input of the path to the zip file which can contain spaces of course! And $dir\temp2 below is a directory that I previously created.
Get-ChildItem -path $dir -Filter *.zip |
ForEach-Object {
$zip_path = """" + $dir + "\" + $_.name + """"
$output = " -o""$dir\temp2"""
&7z e $zip_path $output
}
When I execute it I get the following from 7z.exe:
7-Zip [64] 9.20 Copyright (c) 1999-2010 Igor Pavlov 2010-11-18
Processing archive: C:\test dir\test.zip
No files to process
Files: 0
Size: 0
Compressed: 50219965
If I then copy the value from $zip_path and $output to form my own cmd line it works!
For example:
7z e "c:\test dir\test.zip" -o"c:\test output"
Now, I can reproduce the same message "no files to process" I get when I execute within PowerShell by using the following cmd in cli.
7z e "c:\test dir\test.zip" o"c:\test output"
So, it seems that PowerShell is removing the dash char from my -o option. And yes, it needs to be -o"C:\test output" and not -o "c:\test output" with 7z.exe there is no space between the -o parameter and its value.
I am stumped. Am I doing something wrong or should I be doing this a different way?
I can never get Invoke-Expression (alias = &) to work right either, so I learned how to use a process object
$7ZExe = (Get-Command -CommandType Application -Name 7z )
$7ZArgs = #(
('-o"{0}\{1}"' -f $dir, $_.Name),
('"{0}\{1}"' -f $dir, 'temp2')
)
[Diagnostics.ProcessStartInfo]$7Zpsi = New-Object -TypeName:System.Diagnostics.ProcessStartInfo -Property:#{
CreateNoWindow = $false;
UseShellExecute = $false;
Filename = $7ZExe.Path;
Arguments = $7ZArgs;
WindowStyle = 'Hidden';
RedirectStandardOutput = $true
RedirectStandardError = $true
WorkingDirectory = $(Get-Location).Path
}
$proc = [System.Diagnostics.Process]::Start($7zpsi)
$7ZOut = $proc.StandardOutput
$7ZErr = $proc.StandardError
$proc.WaitForExit()
I was able to duplicate the exact issue and tried numerous combinations escaping the -o switch and escaping quotes " and what not.
But as one answer mentioned Sysinternals, and I used Process Monitor to find out the format it was passing to 7z.exe. Things that work on a plain commandline doesn't work inside PowerShell the same way.
For example, if I tried to construct parameters inside PowerShell just like cmdline it would fail. I.e., -o"C:\scripts\so\new folder" doesn't work. But if you include the -o switch inside quotes then PowerShell passes the string "-oC:\scripts\so\new folder" which 7z.exe is happy to accept. So I learned that 7z.exe would accept both the formats such as
"C:\Program Files\7-zip\7z.exe" e "C:\scripts\so\new folder.zip" -o"C:\scripts\so\new folder"
and
"C:\Program Files\7-zip\7z.exe" e "C:\scripts\so\new folder.zip" "-oC:\scripts\so\new folder"
And both examples contain spaces in them.
[string]$pathtoexe = "C:\Program Files\7-Zip\7z.exe"
$dir = "C:\scripts\so"
$output = "$dir\new folder"
Get-ChildItem -path $dir -Filter *.zip | % {
[array]$marguments = "e",$_.FullName,"-o$output";
& $pathtoexe $marguments
}
Another approach in PowerShell V3 is to escape the PowerShell parsing feature. You can use the --% command to tell PowerShell to stop parsing any more commands like this.
$zipfile = "C:\scripts\so\newfolder.zip"
$destinationfolder = "C:\scripts\so\New Folder"
[string]$pathtoexe = "C:\Program Files\7-Zip\7z.exe"
& $pathtoexe --% e "C:\scripts\so\newfolder.zip" -o"C:\scripts\so\new folder"
Using the --% syntax, you type commands just like you would type them on the command line. I tested this logic, and it extracts files to the destination folder.
To learn more about --%, check PS> help about_parsing.
The issue with this approach is after --% it is not possible to include a variable. The solution to this issue is to just include the --% as another string variable and pass it like this. And this approach is similar to the commandline approach which wasn't working originally.
[string]$pathtoexe = "C:\Program Files\7-Zip\7z.exe"
$dir = "C:\scripts\so"
$output = "$dir\new folder"
Get-ChildItem -path $dir -Filter *.zip | % {
$zipfile = $_.FullName;
[string]$formatted = [System.String]::Concat("e ", """$zipfile"""," -o""$output""");
[string]$stopparser = '--%';
& $pathtoexe $stopparser $formatted;
}
Using the excellent Process Explorer from the Windows Sysinternals suite I was able to observe some very interesting behavior. I simplified your command line a little as seen below:
dir -Path $dir -Filter *.zip |
select FullName |
% { & 7za.exe e $_ "-o$dir\tmp" }
This was actually invoking the following command line according to Process Explorer:
C:\temp\7za.exe #{FullName="C:\temp\test.zip"} -oC:\temp\test
Telling PowerShell to expand the FullName property forces it out of the hashmap and treats it as a regular string which 7-Zip can deal with:
dir -Path $dir -Filter *.zip |
select -ExpandProperty FullName |
% { & 7za.exe e $_ "-o$dir\tmp" }
There may still be other issues like dealing with spaces in file names that I really didn't consider or account for, but I thought it was worth adding a note that PowerShell (v2 in this case) wasn't quite passing the parameters as you might expect.

How to execute powershell commands (not from ps1 file) from cmd.exe

I am trying to execute powershell if-else from cmd.
For example to check the number of files, that has "temp" in its name in D: drive,I used,
if(($i=ls D:\* | findstr /sinc:Temp).count -ne 0 ) {Write-Host $i}
This works fine from PS windows
But if want to do the same from cmd, How do i do it?
I tried
powershell -noexit if(($i=ls D:\* | findstr /sinc:Temp).count -ne 0 ) {Write-Host $i}
which did not work unfortunately.
Just put the command in double quotes:
powershell "if(($i=ls D:\* | findstr /sinc:Temp).count -ne 0 ) {Write-Host $i}"
I also think you don't need the -NoExit switch here. This switch prevents powershell from exiting after running the command. If you want to return back to cmd, remove this switch.
I know this doesn't answer how to run the command(others have covered it already), but why would you want to combine cmd and powershell, when both can do the job alone?
Ex powershell:
#Get all files on D: drive with temp in FILEname(doesn't check if a foldername was temp)
Get-ChildItem D:\ -Recurse -Filter *temp* | where { !$_.PSIsContainer } | foreach { $_.fullname }
#Check if fullpath includes "temp" (if folder in path includes temp, all files beneath are shown)
Get-ChildItem D:\ -Recurse | where { !$_.PSIsContainer -and $_.FullName -like "*temp*" } | foreach { $_.fullname }
Ex cmd:
#Get all files with "temp" in filename
dir d:\*temp* /s /a:-d /b
Just another solution for you problem without using powershell:
dir /b D:\*Temp* | find /v /c "::"
This will print just the number of files or folders on D: that have "Temp" in their names. The double-colon here is just a string that should not be in the output of dir /b, so find /v /c "::" counts all lines of the output of dir /b.
#utapyngo's double quote solution works.
Also another way for #utapyngo's another way to make it in cmd:
dir /b D:\* | find /c "*Temp*"
And to Bill: there should not be a opening double quote before & in your first code, I guess?
powershell -noexit "& "C:\........\run_script.ps1"
see these -- http://poshoholic.com/2007/09/27/invoking-a-powershell-script-from-cmdexe-or-start-run/
http://www.leeholmes.com/blog/2006/05/05/running-powershell-scripts-from-cmd-exe/
or for V2
Powershell.exe -File C:\.........\run_script.ps1