powershell set-acl for multiple computers provided by a csv - powershell

I want to change the permission of a folder on multiple pcs, provided in a csv file. the csv doesn't have a header, just the computernames.
problem is, that it does not import the pc names. i can't use a txt file
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$file = import-csv -path "$dir\pc.csv"
foreach($pc in $file) {
try {
$acl = get-acl -path "\\$pc\c$\Program Files (x86)\testfolder"
$new = "users","full","ContainerInherit,ObjectInherit","None","Allow"
$accessRule = new-object System.Security.AccessControl.FileSystemAccessRule $new
$acl.SetAccessRule($accessRule)
$acl | Set-Acl "\\$pc\c$\Program Files (x86)\testfolder"
Write-Output $([string](get-date) + "`t $pc success") | out-file -append -filepath "$dir\acl_success.log"
}
catch {Write-Output $([string](get-date) + "`t $pc failed") | out-file -append -filepath "$dir\acl_failed.log"
}
}
Is it possible to use a invoke-command setting the folder acl using the provided csv file?

You say "i can't use a txt file", but what you describe IS a txt file. No header, just pc names each on its own line.
Also, your script would greatly benefit if you would indent the code.
Try
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
# set ErrorAction to 'Stop' in order to catch errors
$oldErrorAction = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
Get-Content -Path "$dir\pc.csv" | ForEach-Object {
$pc = $_ # capture this for when we hot the catch block
$path = "\\$pc\c$\Program Files (x86)\testfolder"
try {
$acl = Get-Acl -LiteralPath $path
$accessrule = [System.Security.AccessControl.FileSystemAccessRule]::new('Users', 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')
$acl.SetAccessRule($accessRule)
$acl | Set-Acl -LiteralPath $path
# write to both the console and to file
("{0}`t{1} success" -f (Get-Date).ToString(), $pc) | Add-Content -LiteralPath "$dir\acl_success.log" -PassThru
}
catch {
("{0}`t{1} failed" -f (Get-Date).ToString(), $pc) | Add-Content -LiteralPath "$dir\acl_success.log" -PassThru
}
}
# restore previous ErrorAction
$ErrorActionPreference = $oldErrorAction
Using Invoke-Command:
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$script = {
# set ErrorAction to 'Stop' in order to catch errors
$oldErrorAction = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
# you're now running this on the remote pc, so use local path
$path = "C:\Program Files (x86)\testfolder"
try {
$acl = Get-Acl -LiteralPath $path
$accessrule = [System.Security.AccessControl.FileSystemAccessRule]::new('Users', 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')
$acl.SetAccessRule($accessRule)
$acl | Set-Acl -LiteralPath $path
# output the message
"{0}`t{1} success" -f (Get-Date).ToString(), $env:COMPUTERNAME
}
catch {
"{0}`t{1} failed" -f (Get-Date).ToString(), $env:COMPUTERNAME
}
# restore previous ErrorAction
$ErrorActionPreference = $oldErrorAction
}
$allPCs = #(Get-Content -Path "$dir\pc.csv") # force it to be an array, evenif just one pc name in the file
# run the scriptblock on the $allPCs array
$result = Invoke-Command -ComputerName $allPCs -ScriptBlock $script
$result | Where-Object { $_ -match 'success' } | Add-Content -Path "$dir\acl_success.log" -PassThru
$result | Where-Object { $_ -match 'failed' } | Add-Content -Path "$dir\acl_failed.log" -PassThru

Instead of this:
$file = import-csv -path "$dir\pc.csv"
Use:
$file = get-content "$dir\pc.csv"

Related

For loop with list not working as expected Powershell

I'm trying to create a powershell script which checks a log file for lines of text and if the line exists restarts a service and resets/archives the log. I got it working before with 1 "checkstring" if you will, but I've been struggling to get it to work with a list of strings. Could anyone help me figure out where I'm going wrong?
This is the code I'm currently using:
$serviceName = "MySQL80"
$file = "test.txt"
$pwd = "C:\tmp\"
$checkStrings = New-Object System.Collections.ArrayList
# Add amount of checkstrings
$checkStrings.add("Unhandled error. Error message: Error retrieving response.")
$checkStrings.add("Unhandled error. Error message: Error retrieving response. Second")
$logName = "ServiceCheck.log"
$backupFolder = "Archive"
$logString = (Get-Date).ToString("ddMMyyyyHHmmss"), " - The service has been reset and the log moved to backup" -Join ""
Set-Location -Path $pwd
if(Test-Path -Path $file) {
if(Test-Path -Path $backupFolder) {
} else {
New-Item -Path $pwd -Name $backupFolder -ItemType "director"
}
foreach ($element in $checkStrings) {
$containsWord = $fileContent | %{$_ -match $element}
if ($containsWord -contains $true) {
Restart-Service -Name $serviceName
$backupPath = $pwd, "\", $backupFolder, "\", $date, ".log" -join ""
$currentFile = $pwd, "\", $file -join ""
Copy-Item $currentFile -Destination $backupPath
Get-Content $currentFile | select-string -pattern $checkString -notmatch | Out-File $currentFile
if(Test-Path -Path $logName) {
Add-Content $logName $logString
} else {
$logString | Out-File -FilePath $logName
}
}
}
}

Set-SFTPFile upload full directory

I need to upload full directory (with recourcive folders) to the server by sftp
#SSH
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$Credentials = New-Object System.Management.Automation.PSCredential($User, $secpasswd)
$sftpSession = New-SFTPSession -ComputerName $HostIP -Credential $Credentials
#Folders Paths
$FilePath = Get-ChildItem -Path uploading-folder | Select-Object -ExpandProperty FullName
$SftpPath = "/home/new-folder"
# Upload the file to the SFTP path
Set-SFTPFile -SessionId ($sftpSession).SessionId -LocalFile $FilePath -RemotePath $SftpPath -Overwrite
#Disconnect all SFTP Sessions
Get-SFTPSession | % { Remove-SFTPSession -SessionId ($_.SessionId) }
But i cant upload folders inside folders (and filse in them). How can I upload full folder with files and folders inside it?
Posh does not seem to support recursive operations. You have to code it on your own.
Quoting Reddit post SFTP - How to upload an entire folder/directory instead of one file at a time?:
# Get a recursive list of files in the target folder ($path).
# Change the include to your needs,
# and remove the -Force if you don't want hidden files included
$path = "./Recursion test/"
$uploadFiles = Get-ChildItem $path -Recurse -Include "*" -Force
$session = New-SFTPSession (your args here)
$remoteFolder = Get-SFTPCurrentDirectory $sessoin.Index
Set-SFTPDirectoryPath $session.Index -Path $destinationPath
foreach ($item in $uploadFiles) {
if ($item.GetType -eq [System.IO.DirectoryInfo]) {
New-SFTPDirectory $sessionIndex $item
}
else {
$localFolder = $item.PSPath | Split-Path | Split-Path -Leaf
if ($localFolder -ne $remoteFolder) {
Set-SFTPCurrentDirectory $session.Index $localFolder
$remoteFolder = $localFolder
}
Set-SFTPFile $session.Index $item.Name
}
}
Or use another SFTP library that supports recursive operations.
Code :
$folders = #('D:\Shares\folderone\*.pdf','D:\Shares\foldertwo\*.pdf')
foreach ($folder in $folders) {
$files = Get-ChildItem $folder
foreach ($file in $files) {
Set-SFTPItem -SessionId $SFTPSession.SessionId -Path $file -Destination /upload -Verbose
}
}

check if a Deny permission already exists to a directory or not in PowerShell

I have been writing a script
Workflow:
Get list all of fixed disks ( except cdrom , floppy drive , usb drive)
to check if a path exists or not in PowerShell
to check if a Deny permission already exists to a directory or not in PowerShell
Set deny permission for write access for users
My question are :
1- After file exist control like below , also I want to check if a Deny permission already exists to a directory. ("$drive\usr\local\ssl")
If(!(test-path $path))
{
New-Item -ItemType Directory -Force -Path $path
}
2- there are about 1000 machines. How can I improve this script ?
Thanks in advance,
script :
$computers = import-csv -path "c:\scripts\machines.csv"
Foreach($computer in $computers){
$drives = Get-WmiObject Win32_Volume -ComputerName $computer.ComputerName | Where { $_.drivetype -eq '3'} |Select-Object -ExpandProperty driveletter | sort-object
foreach ($drive in $drives) {
$path = "$drive\usr\local\ssl"
$principal = "users"
$Right ="Write"
$rule=new-object System.Security.AccessControl.FileSystemAccessRule($Principal,$Right,"Deny")
If(!(test-path $path))
{
New-Item -ItemType Directory -Force -Path $path
}
try
{
$acl = get-acl $folder
$acl.SetAccessRule($rule)
set-acl $folder $acl
}
catch
{
write-host "ACL failed to be set on: " $folder
}
#### Add-NTFSAccess -Path <path> -Account <accountname> -AccessType Deny -AccessRights <rightstodeny>
}
}
The first thing I noticed is that in your code, you suddenly use an undefined variable $folder instead of $path.
Also, you get the drives from the remote computer, but set this $path (and try to add a Deny rule) on folders on your local machine:
$path = "$drive\usr\local\ssl"
where you should set that to the folder on the remote computer:
$path = '\\{0}\{1}$\usr\local\ssl' -f $computer, $drive.Substring(0,1)
Then, instead of Get-WmiObject, I would nowadays use Get-CimInstance which should give you some speed improvement aswell, and I would add some basic logging so you will know later what happened.
Try this on a small set of computers first:
Note This is assuming you have permissions to modify permissions on the folders of all these machines.
$computers = Import-Csv -Path "c:\scripts\machines.csv"
# assuming your CSV has a column named 'ComputerName'
$log = foreach ($computer in $computers.ComputerName) {
# first try and get the list of harddisks for this computer
try {
$drives = Get-CimInstance -ClassName Win32_Volume -ComputerName $computer -ErrorAction Stop |
Where-Object { $_.drivetype -eq '3'} | Select-Object -ExpandProperty driveletter | Sort-Object
}
catch {
$msg = "ERROR: Could not get Drives on '$computer'"
Write-Host $msg -ForegroundColor Red
# output a line for the log
$msg
continue # skip this one and proceed on to the next computer
}
foreach ($drive in $drives) {
$path = '\\{0}\{1}$\usr\local\ssl' -f $computer, $drive.Substring(0,1)
$principal = "users"
$Right = "Write"
if (!(Test-Path -Path $path -PathType Container)) {
$null = New-Item -Path $path -ItemType Directory -Force
}
# test if the path already has a Deny on write for the principal
$acl = Get-Acl -Path $path -ErrorAction SilentlyContinue
if (!$acl) {
$msg = "ERROR: Could not get ACL on '$path'"
Write-Host $msg -ForegroundColor Red
# output a line for the log
$msg
continue # skip this one and proceed to the next drive
}
if ($acl.Access | Where-Object { $_.AccessControlType -eq 'Deny' -and
$_.FileSystemRights -band $Right -and
$_.IdentityReference -like "*$principal"}) {
$msg = "INFORMATION: Deny rule already exists on '$path'"
Write-Host $msg -ForegroundColor Green
# output a line for the log
$msg
}
else {
$rule = [System.Security.AccessControl.FileSystemAccessRule]::new($Principal, $Right, "Deny")
# older PS versions use:
# $rule = New-Object System.Security.AccessControl.FileSystemAccessRule $Principal, $Right, "Deny"
try {
$acl.AddAccessRule($rule)
Set-Acl -Path $path -AclObject $acl -ErrorAction Stop
$msg = "INFORMATION: ACL set on '$path'"
Write-Host $msg -ForegroundColor Green
# output a line for the log
$msg
}
catch {
$msg = "ERROR: ACL failed to be set on: '$path'"
Write-Host $msg -ForegroundColor Red
# output a line for the log
$msg
}
}
}
}
# write the log
$log | Set-Content -Path "c:\scripts\SetAccessRuleResults.txt" -Force

Powershell script executed on each file in a folder?

I currently have a powershell script, which print out some information regarding the files which passed in as argument..
The command for executing the script, it done as such:
.\myscript.ps1 -accessitem C:\folder
I want to apply the script on all files and folder on the drive C:, is it possible i for loop to list all files, and pass the path as argument for the script?
The script:
[CmdletBinding()]
Param (
[Parameter(Mandatory=$True,Position=0)]
[String]$AccessItem
)
$ErrorActionPreference = "SilentlyContinue"
If ($Error) {
$Error.Clear()
}
$RepPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$RepPath = $RepPath.Trim()
$str = $AccessItem -replace ':',''
$str = $AccessItem -replace '/','.'
$FinalReport = "$RepPath\"+$str+".csv"
$ReportFile1 = "$RepPath\NTFSPermission_Report.txt"
If (!(Test-Path $AccessItem)) {
Write-Host
Write-Host "`t Item $AccessItem Not Found." -ForegroundColor "Yellow"
Write-Host
}
Else {
If (Test-Path $FinalReport) {
Remove-Item $FinalReport
}
If (Test-Path $ReportFile1) {
Remove-Item $ReportFile1
}
Write-Host
Write-Host "`t Working. Please wait ... " -ForegroundColor "Yellow"
Write-Host
## -- Create The Report File
$ObjFSO = New-Object -ComObject Scripting.FileSystemObject
$ObjFile = $ObjFSO.CreateTextFile($ReportFile1, $True)
$ObjFile.Write("NTFS Permission Set On -- $AccessItem `r`n")
$ObjFile.Close()
$ObjFile = $ObjFSO.CreateTextFile($FinalReport, $True)
$ObjFile.Close()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($ObjFSO) | Out-Null
Remove-Variable ObjFile
Remove-Variable ObjFSO
If((Get-Item $AccessItem).PSIsContainer -EQ $True) {
$Result = "ItemType -- Folder"
}
Else {
$Result = "ItemType -- File"
}
$DT = Get-Date -Format F
Add-Content $ReportFile1 -Value ("Report Created As On $DT")
Add-Content $ReportFile1 "=================================================================="
$Owner = (Get-Item -LiteralPath $AccessItem).GetAccessControl() | Select Owner
$Owner = $($Owner.Owner)
$Result = "$Result `t Owner -- $Owner"
Add-Content $ReportFile1 "$Result `n"
(Get-Item -LiteralPath $AccessItem).GetAccessControl() | Select * -Expand Access | Select IdentityReference, FileSystemRights, AccessControlType, IsInherited, InheritanceFlags, PropagationFlags | Export-CSV -Path "$RepPath\NTFSPermission_Report2.csv" -NoTypeInformation
Add-Content $FinalReport -Value (Get-Content $ReportFile1)
Add-Content $FinalReport -Value (Get-Content "$RepPath\NTFSPermission_Report2.csv")
Remove-Item $ReportFile1
Remove-Item "$RepPath\NTFSPermission_Report2.csv"
Invoke-Item $FinalReport
}
If ($Error) {
$Error.Clear()
}
I would prefer a outside command doing this, as workings of the script should not be altered, it it used for single file testing..
There are two ways to do this:
Add -Recurse Flag to the script
Run the script on each directory
I'm going with option two since the script looks complicated enough that I don't want to touch it.
$path_to_script = "C:\path\to\myscript.ps1"
$start_directory = "C:\folder"
# Call Script on Parent Directory
& "$path_to_script" -AccessItem "$start_directory"
# Call Script on any Child Directories within the "$start_directory"
foreach($child in (ls "$start_directory" -Recurse -Directory))
{
$path = $child.FullName
& "$path_to_script" -AccessItem "$path"
}
Basically, I'm calling the script on the parent directory and any sub-directories within the parent directory.

PowerShell Move-Item $filename

I searched, i googled.. about to smash my head on the table
how come this will not work?
move-Item $path$file $targetdir
it gives me an error
Move-Item : An object at the specified path C:\Repository\test.csv
does not exist.
now if i debug this and i output using
write-output move-Item $path$file $targetdir
and take that output and paste it (file name with path and destination) it works!
and trust me the file is there. =\
Code below
$path = 'C:\test\'
$TimeStamp = Get-Date -Format "MM-dd-yyyy_hh-mm-ss"
$LogFile = Get-Date -Format "MM_dd_yyyy"
$targetdir = "C:\test\Uploaded\"
#Get-ChildItem -path $path\* -Include *.csv | foreach-object {$_.Fullname} | Format-Table name -hidetableheaders | Out-File $path\list.txt
Get-ChildItem -path $path\* -Include *.csv | Format-Table name -hidetableheaders | Out-File $path\list2.txt
get-content C:\test\list2.txt | where {$_ -ne ""} | out-file C:\test\list.txt
Remove-Item C:\test\list2.txt
$list = get-content C:\test\list.txt
foreach ($file in $list)
{
$ftp = "ftp://REMOVED/$file"
"ftp url: $ftp"
$webclient = New-Object System.Net.WebClient
$uri = New-Object System.Uri($ftp)
"Uploading $file..."
$succeeded = $true;
& {
trap { $script:succeeded = $false; continue }
$webclient.UploadFile($uri, $path+$file)
}
if ($succeeded)
{
echo $file 'Was successfully uploaded!' $Timestamp >> logfile$LogFile.log
move-Item -path $path$file -destination $targetdir
#test-path $path$file
}
else
{
echo $file 'Was not successfully uploaded, will retry later' $Timestamp >> logfile$LogFile.log
}
}
exit
Basics are:
Test-Path before you move it (file and destination)
Move the file, ensure you have permission (force it to move)
so:
echo $targetdir
echo "$path$file"
if (!(Test-Path $targetdir)) {
New-Item -ItemType directory $targetdir
}
if(Test-Path "$path$file") {
Move-Item "$path$file" $targetdir -Force
} else {
echo "file does not exist"
}
If you loop over a collection you have to use the ".FullName" property of the object:
Get-ChildItem $path | ForEach-Object { Move-Item $_.FullName $targetdir -Force }
Does the target directory already exist? I believe Move-Item will fail if the target directory doesn't exist. If that's the case, you can simply test for existence of the directory beforehand and then create as necessary.
If (!(Test-Path -Path $targetdir)) {
New-Item -ItemType directory -Path $targetdir
}
This worked for me. Thank you #TheMadTechnician. Hopes this helps everyone
$TimeStamp = Get-Date -Format "MM-dd-yyyy_hh-mm-ss"
$LogFile = Get-Date -Format "MM_dd_yyyy"
$path='C:\test\'
$targetDir = 'C:\test\Uploaded\'
$fileList = Get-ChildItem $path*.csv
If(!(Test-Path $TargetDir)){New-Item -ItemType Directory -Path $TargetDir|Out-Null}
$fileList | Select -ExpandProperty Name | Out-File 'C:\test\list.txt'
$list = get-content C:\test\list.txt
foreach ($file in $list)
{
$ftp = "ftp://REMOVED/$file"
"ftp url: $ftp"
$webclient = New-Object System.Net.WebClient
$uri = New-Object System.Uri($ftp)
"Uploading $file..."
$succeeded = $true;
& {
trap { $script:succeeded = $false; continue }
$webclient.UploadFile($uri, $path+$file)
}
if ($succeeded)
{
echo $file 'Was successfully uploaded!' $Timestamp >> logfile$LogFile.log
move-Item -path $path$file -destination $targetdir$Timestamp"_"$file
#test-path $path$file
}
else
{
echo $file 'Was not successfully uploaded, will retry later' $Timestamp >> logfile$LogFile.log
}
}
exit
How about this then:
ForEach($File in $List){
Join-Path $path $file | Move-Item -Dest $Targetdir
}
Edit: Also... your creation of list.txt, it bothered me so I had to comment. Format-Table should be used for formatting text, not for selecting a value to output to a file. There's a better way to do that, consider this alternative:
Get-ChildItem "$path*.csv" | Select -ExpandProperty Name | Out-File $pathlist.txt
Since you say that $path = 'C:\test\' you are adding extra backslashes in there that may cause issues for some commands.
Edit2: Ok, if that doesn't work, why not work with the files themselves instead of outputting to a file, importing from that file, and then working with things.
$path='c:\test\'
$TargetDir = 'c:\test\NewDir'
$FileList = Get-ChildItem $path*.csv
If(!(Test-Path $TargetDir)){New-Item -ItemType Directory -Path $TargetDir|Out-Null}
$FileList | Move-Item -Destination $TargetDir
Then if you really want a list of those file names just pipe $FileList to Select and then to Out-File
$FileList | Select -ExpandProperty Name | Out-File 'C:\Test\list.txt'
Here, look through this and see if there's anything you like. I made a few changes, such as declaring paths at the beginning for everything, I moved the WebClient object creation outside of the loop, and changed how things are displayed on screen. Plus I skip the entire exporting to text file and re-importing it.
$path = 'C:\test'
$ftpaddr = 'ftp://ftp.example.com/uploads'
$TimeStamp = Get-Date -Format "MM/dd/yyyy hh:mm:ss tt"
$LogFile = Get-Date -Format "MM_dd_yyyy"
$LogDir = "C:\Test\Logs"
If(!(test-path $LogDir)){New-Item -ItemType Directory -Path $LogDir | Out-Null}
$targetdir = 'C:\test\Uploaded'
If(!(test-path $targetdir)){New-Item -ItemType Directory -Path $targetdir | Out-Null}
$list = Get-ChildItem -path $path\* -Include *.csv
$webclient = New-Object System.Net.WebClient
"ftp url: $ftpaddr"
foreach ($file in ($list|select -ExpandProperty Name))
{
$uri = New-Object System.Uri(("$ftpaddr/$file"))
Write-Host "Uploading $file... " -NoNewline -ForegroundColor White
$succeeded = $true
& {
trap { $script:succeeded = $false; continue }
$webclient.UploadFile($uri, "$Path\$file")
}
if ($succeeded)
{
Write-Host "Success!" -ForegroundColor Green
"$Timestamp`t$File was successfully uploaded!" | Out-File "$logdir\logfile$LogFile.log" -Append
move-Item -path "$path\$file" -destination $targetdir
}
else
{
Write-Host "Failed! Will retry later." -ForegroundColor Red
"$Timestamp`t$File was not successfully uploaded, will retry later" | Out-File "$logdir\logfile$LogFile.log" -Append
}
}