Changing NTFS security on user with fullcontrol to modify - acl

I have thousands of folders I need to change users with Fullcontrol access to modify access. The following is a list of what I have:
A script that changes NTFS perms:
$acl = Get-Acl "G:\Folder"
$acl | Format-List
$acl.GetAccessRules($true, $true, [System.Security.Principal.NTAccount])
#second $true on following line turns on inheritance, $False turns off
$acl.SetAccessRuleProtection($True, $True)
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule("Administrators","FullControl", "ContainerInherit, ObjectInherit", "None", "Allow")
$acl.AddAccessRule($rule)
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule("My-ServerTeam","FullControl", "ContainerInherit, ObjectInherit", "None", "Allow")
$acl.AddAccessRule($rule)
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule("Users","Read", "ContainerInherit, ObjectInherit", "None", "Allow")
$acl.AddAccessRule($rule)
Set-Acl "G:\Folder" $acl
Get-Acl "G:\Folder" | Format-List
A text file with the directories and users that need to be changed from fullcontrol to modify.
I can always create a variable for the path and/or username and create a ForEach loop, but I'm not sure how to change the users that exist in the ACL for each folder to Modify, but keep the Admin accounts as full control. Any help would be appreciated.

Went another route and got what I needed. I'm not surprised noone tried to help me on this one.... it was tough. I'll post the scripts for the next person who has this issue.
There are two scripts. The first I obtained from the internet and altered a bit. The second script launches the first with the parameters required to automate.
First Script Named SetFolderPermission.ps1:
param ([string]$Path, [string]$Access, [string]$Permission = ("Modify"), [switch]$help)
function GetHelp() {
$HelpText = #"
DESCRIPTION:
NAME: SetFolderPermission.ps1
Sets FolderPermissions for User on a Folder.
Creates folder if not exist.
PARAMETERS:
-Path Folder to Create or Modify (Required)
-User User who should have access (Required)
-Permission Specify Permission for User, Default set to Modify (Optional)
-help Prints the HelpFile (Optional)
SYNTAX:
./SetFolderPermission.ps1 -Path C:\Folder\NewFolder -Access Domain\UserName -Permission FullControl
Creates the folder C:\Folder\NewFolder if it doesn't exist.
Sets Full Control for Domain\UserName
./SetFolderPermission.ps1 -Path C:\Folder\NewFolder -Access Domain\UserName
Creates the folder C:\Folder\NewFolder if it doesn't exist.
Sets Modify (Default Value) for Domain\UserName
./SetFolderPermission.ps1 -help
Displays the help topic for the script
Below Are Available Values for -Permission
"#
$HelpText
[system.enum]::getnames([System.Security.AccessControl.FileSystemRights])
}
<#
function CreateFolder ([string]$Path) {
# Check if the folder Exists
if (Test-Path $Path) {
Write-Host "Folder: $Path Already Exists" -ForeGroundColor Yellow
} else {
Write-Host "Creating $Path" -Foregroundcolor Green
New-Item -Path $Path -type directory | Out-Null
}
}
#>
function SetAcl ([string]$Path, [string]$Access, [string]$Permission) {
# Get ACL on FOlder
$GetACL = Get-Acl $Path
# Set up AccessRule
$Allinherit = [system.security.accesscontrol.InheritanceFlags]"ContainerInherit, ObjectInherit"
$Allpropagation = [system.security.accesscontrol.PropagationFlags]"None"
$AccessRule = New-Object system.security.AccessControl.FileSystemAccessRule($Access, $Permission, $AllInherit, $Allpropagation, "Allow")
# Check if Access Already Exists
if ($GetACL.Access | Where {$_.IdentityReference -eq $Access}) {
Write-Host "Modifying Permissions For: $Access on directory: $Path" -ForeGroundColor Yellow
$AccessModification = New-Object system.security.AccessControl.AccessControlModification
$AccessModification.value__ = 2
$Modification = $False
$GetACL.ModifyAccessRule($AccessModification, $AccessRule, [ref]$Modification) | Out-Null
} else {
Write-Host "Adding Permission: $Permission For: $Access"
$GetACL.AddAccessRule($AccessRule)
}
Set-Acl -aclobject $GetACL -Path $Path
Write-Host "Permission: $Permission Set For: $Access on directory: $Path" -ForeGroundColor Green
}
if ($help) { GetHelp }
if ($Access -AND $Permission) {
SetAcl $Path $Access $Permission
}
The next script calls the first script and adds the needed parameters. A CSV containing 2 columns with the folders and usernames with full control.
$path = "C:\Scripts\scandata\TwoColumnCSVwithPathandUserwithFullControl.csv"
$csv = Import-csv -path $path
foreach($line in $csv){
$userN = $line.IdentityReference
$PathN = $line.Path
$dir = "$PathN"
$DomUser = "$userN"
$Perm = "Modify"
$scriptPath = "C:\Scripts\SetFolderPermission.ps1"
$argumentList1 = '-Path'
$argumentList2 = "$dir"
$argumentList3 = '-Access'
$argumentList4 = "$DomUser"
$argumentList5 = '-Permission'
$argumentList6 = "$Perm"
Invoke-Expression "$scriptPath $argumentList1 $argumentList2 $argumentList3 $argumentList4 $argumentList5 $argumentList6"

Related

Powershell: How to repeat a condition until a valid value is passed?

I am working on a script that clones a directory structure (excluding files) to a new directory. Here's my code so-far:
Function Copy-DirectoryStructure{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[string]$Path
)
# Test whether the path is exactly a drive letter
if($Path -match '^[a-zA-Z]:\\$' -or $Path -match '^[a-zA-Z]:$'){
throw "Path cannot be at the root of a drive. Aborting."
}
# Check if the path is valid
if(Test-Path -Path $Path -PathType Container){
# Create the destination path format
$DestinationPath = (get-item $Path).FullName + ' Folder Copy'
# Test if our destination already exists.
# If so, prompt for a new name
if(Test-Path -Path $DestinationPath -PathType Container){
Write-Warning "The destination path already exists."
$NewFolderName = Read-Host -Prompt "Input the name of the new folder to be created."
$DestinationPath = (get-item $Path).parent.FullName + '\' + $NewFolderName
}
# Begin copy
robocopy $Path $DestinationPath /e /xf *.* | Out-Null
} else {
throw "Invalid directory was passed. Aborting."
}
}
Copy-DirectoryStructure -Path "C:\Users\[username]\Desktop\Test"
The relevant part is here:
# Create the destination path format
$DestinationPath = (get-item $Path).FullName + ' Folder Copy'
# Test if our destination already exists.
# If so, prompt for a new name
if(Test-Path -Path $DestinationPath -PathType Container){
Write-Warning "The destination path already exists."
$NewFolderName = Read-Host -Prompt "Input the name of the new folder to be created."
$DestinationPath = (get-item $Path).parent.FullName + '\' + $NewFolderName
}
Right now it prompts the user to input a new name if $DestinationPath already exists and then proceeds to the robocopy. But what if the user inputs a folder name that ALSO already evaluates to an existing path?
I want to handle this scenario gracefully and re-prompt for a new path until the user enters a destination path that doesn't already exist.
I have no idea how to do this.
I know this is an extreme edge-case, but I want to make my code as safe as possible.
Any help is greatly appreciated.
Remove the interactive bits from the function completely - it should simply fail on destination path existing - and instead give the user a way to explicitly pass an alternative destination path:
Function Copy-DirectoryStructure{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[string]$Path,
[Parameter(Mandatory=$false)]
[string]$DestinationPath
)
$ErrorActionPreference = 'Stop'
# Test whether the path is exactly a drive letter
if($Path -match '^[a-zA-Z]:\\$' -or $Path -match '^[a-zA-Z]:$'){
throw "Path cannot be at the root of a drive. Aborting."
}
# Check if the path is valid
if(-not(Test-Path -Path $Path -PathType Container)){
throw "Path must describe an existing directory"
}
# Create the destination path format
if(-not $PSBoundParameters.ContainsKey('DestinationPath'))
{
$DestinationPath = (Get-Item $Path).FullName + ' Folder Copy'
}
# Test that the destination isn't an existing file system item
if(Test-Path -Path $DestinationPath){
throw "The destination path already exists."
}
# If we've reached this point all validation checks passed, begin copy
robocopy $Path $DestinationPath /e /xf *.* | Out-Null
}
Now that the function predictably fails, we can use error handling to handle the retry-logic outside the function:
while($true){
$path = Read-Host "Give a target path!"
try {
Copy-DirectoryStructure -Path $path
break
}
catch {
if($_ -match 'The destination path already exists.'){
$destPath = Read-Host "Wanna try an alternative destination path (input NO to start over)"
if($destPath -ceq 'NO'){
continue
}
Copy-DirectoryStructure -Path $path
break
}
}
}

How can you pass the HashTable as a parameter to the function and export the data into the csv?

I have build the PowerShell module function which captures the data such as:
Start Date
Start Time
Server Name
User (who executes the script
Technology
Script Name
Script Path
Execution Time (# of seconds took the script from start to end)
Error Message (if there is any error message in the script)
The above points provides data for each of the script, I have enabled the module and calling out the function in each of my script. However, there are some scripts which provides build data and some scripts which don't. For example, there is one script which enables MS Project and MS Visio license to the end users and it gives me the data on # of Project license assigned and # of Visio license assigned.
I am trying to find the way on how to capture the build data from some of my scripts. I want to capture the data with headers and merge them with my HashTable which is in my module script. Altogether, I want to export the data to the csv file.
I am trying to understand what I am missing? I used param method but couldn't output any data.
Here is my PowerShell module code
function Get-Info(){
$date = (Get-Date -f "MM_dd_yyyy_hh_mm")
#TimeStamp
$reportDate = (Get-Date -f "MM/dd/yyyy")
$reportTime = (Get-Date -f "hh:mm:ss tt")
#Server
$serverName = $env:COMPUTERNAME
#user
$user = $env:UserName
#script name
$scriptName = split-path $MyInvocation.PSCommandPath -Leaf
#script path
$scriptPath = split-path $MyInvocation.PSCommandPath
#service account
$serviceAccount = $tenantAdmin
#measure execution time (start to finish)
[timespan]$executionTime = Measure-Command {0..10000 | ForEach-Object {$i++} }
$runTime = $executionTime.Milliseconds
#Technology Type
$technology = Split-Path (Split-Path $scriptPath -Parent) -Leaf
#error handling
If($errorMessage -eq $null){
$errorMessage = "None"
}
if ($errorMessage -ne $null){
$errorMessage = $ErrorMessage
}
$fileName = $scriptName.Trim(".ps1")
$Hashtable = #()
$Hashtable += New-Object psobject -Property #{
"Script Name" = $scriptName;
"Execution Date" = $reportDate;
"Execution Time" = $reportTime;
"Service Account" = $serviceAccount;
"User" = $user;
"Server Name" = $serverName;
"Script Path" = $scriptPath;
"RunTimeMS" = $runTime;
"Error Message" = $errorMessage;
"Technology" = $technology;
}
if($myHashTable -eq $null){
$Hashtable | Export-Csv "D:\MyFileName\$fileName$date.csv" -NoTypeInformation
}
if($myHashTable -ne $null){
$finalTable = $Hashtable + $myHashTable
$finalTable | Export-Csv "D:\MyFileName\$fileName$date.csv" -NoTypeInformation
}

Powershell not returning correct value

As some background, this should take an excel file, and convert it to PDF (and place the PDF into a temporary folder).
E.g. 'C:\Users\gjacobs\Desktop\test\stock.xlsx'
becomes
'C:\Users\gjacobs\Desktop\test\pdf_merge_tmp\stock.pdf'
However, the new file path does not return correctly.
If I echo the string $export_name from within the function, I can see that it has the correct value: "C:\Users\gjacobs\Desktop\test\pdf_merge_tmp\stock.pdf".
But once $export_name is returned, it has a different (incorrect value): "C:\Users\gjacobs\Desktop\test\pdf_merge_tmp C:\Users\gjacobs\Desktop\test\pdf_merge_tmp\stock.pdf".
function excel_topdf{
param(
$file
)
#Get the parent path
$parent = Split-Path -Path $file
#Get the filename (no ext)
$leaf = (Get-Item $file).Basename
#Add them together.
$export_name = $parent + "\pdf_merge_tmp\" + $leaf + ".pdf"
echo ($export_name) #prints without issue.
#Create tmp dir
New-Item -Path $parent -Name "pdf_merge_tmp" -ItemType "Directory" -Force
$objExcel = New-Object -ComObject excel.application
$objExcel.visible = $false
$workbook = $objExcel.workbooks.open($file, 3)
$workbook.Saved = $true
$xlFixedFormat = “Microsoft.Office.Interop.Excel.xlFixedFormatType” -as [type]
$workbook.ExportAsFixedFormat($xlFixedFormat::xlTypePDF, $export_name)
$objExcel.Workbooks.close()
$objExcel.Quit()
return $export_name
}
$a = excel_topdf -file 'C:\Users\gjacobs\Desktop\test\stock.xlsx'
echo ($a)
The issue you're experiencing is caused by the way how PowerShell returns from functions. It's not something limited to New-Item cmdlet. Every cmdlet which returns anything would cause function output being altered with the value from that cmdlet.
As an example, let's take function with one cmdlet, which returns an object:
function a {
Get-Item -Path .
}
$outputA = a
$outputA
#### RESULT ####
Directory:
Mode LastWriteTime Length Name
---- ------------- ------ ----
d--hs- 12/01/2021 10:47 C:\
If you want to avoid that, these are most popular options (as pointed out by Lasse V. Karlsen in comments):
# Assignment to $null (or any other variable)
$null = Get-Item -Path .
# Piping to Out-Null
Get-Item -Path . | Out-Null
NOTE: The behavior described above doesn't apply to Write-Host:
function b {
Write-Host "bbbbbb"
}
$outputB = b
$outputB
# Nothing displayed
Interesting thread to check if you want to learn more.

How to create a Function using Powershell

I need help with the code below. I want the script to perform the following: prompt the user for an AD group name, if the group name is found, export the group members to a CSV file. one of the requirements is that I must include a function statement. Thank you in advance.
The code works if I use a variable like the following example: $groupsusers = Get-ADGroup -Identity $nameofgroup, instead of the function statement.
However, I don't want to use a variable, I want to implement a function statement.
$prompt = "Enter A Group Name"
do
{
$nameofgroup = Read-Host $prompt
}
until(!$(dsquery Group-Object $nameofgroup; $prompt = "Group
'$nameofgroup' was not found, try again"))
$nameofgroup = Read-Host $prompt
function GetGroupInfoToCsv (#what parameters go here?){
ForEach-Object{
$settings = #{ Group = $_.DistinguishedName; Member = $null }
$_| Get-ADGroupMember |
ForEach-Object{
$settings.Member = $_.DistinguishedName
New-Object PsObject -Property $settings
}
}
}
GetGroupInfoToCsv | Export-Csv .\GroupMembers.csv -NoTypeInformation
You could combine the asking for user input and returning the info into the same function. Something like this:
function Get-GroupMembers {
$prompt = "Enter A Group Name. Press Q to quit"
# create an endless loop
while ($true) {
Clear-Host
$answer = Read-Host $prompt
# if the user has had enough, exit the function
if ($answer -eq 'Q') { return }
# try and find one or more AD groups using the answer as (part of) the name
$group = Get-ADGroup -Filter "Name -like '*$answer*'"
# if we have found something, exit the while loop and start enumerating the members
if ($group) { break }
$prompt = "Group '$answer' was not found, try again. Press Q to quit"
}
# you only get here if Get-ADGroup found one or more groups
$group | ForEach-Object {
# output a PSObject with the properties you are after
$members = $_ | Get-ADGroupMember
foreach ($member in $members) {
[PsCustomObject]#{
'Group' = $_.DistinguishedName
'Member' = $member.DistinguishedName
}
}
}
}
# call the function
$groupinfo = Get-GroupMembers
# test if the function returned anything.
# the user could have cancelled of the group had no members to output
if ($groupinfo) {
Write-Host "Adding $($groupinfo.Count) items to the CSV file"
# without -Append, you would overwrite your CSV file..
$groupinfo | Export-Csv .\GroupMembers.csv -NoTypeInformation -Append
}
else {
Write-Host 'Nothing found..'
}
As you can see, I have changed the function name so it complies with the Verb-Noun convention in PowerShell.

Powershell WebClient DownloadFile Exception Illegal Characters in Path

I am trying to download zip files from an FTP site, based off retrieving a directory list to find file names.
Download Portion:
$folderPath='ftp://11.111.11.11/'
$target = "C:\Scripts\ps\ftpdl\"
Foreach ($file in ($array | where {$_ -like "data.zip"})) {
$Source = $folderPath+$file
$Path = $target+$file
#$Source = "ftp://11.111.11.11/data.zip"
#$Path = "C:\Scripts\ps\ftpdl\data.zip"
$source
Write-Verbose -Message $Source -verbose
$path
Write-Verbose -message $Path -verbose
$U = "User"
$P = "Pass"
$WebClient2 = New-Object System.Net.WebClient
$WebClient2.Credentials = New-Object System.Net.Networkcredential($U, $P)
$WebClient2.DownloadFile( $source, $path )
}
If I use the commented out and define the string it downloads correctly. But if I run it as shown I receive the exception error illegal characters in path. Interestingly enough, there is a difference between write-verbose and not.
Output when run as shown:
ftp://11.111.11.11/data.zip
data.zip
C:\Scripts\ps\ftpdl\data.zip
data.zip
Exception calling "DownloadFile" with "2" .........
Output when run with hard coded path & source
ftp://11.111.11.11/data.zip
VERBOSE: ftp://11.111.11.11/data.zip
C:\Scripts\ps\ftpdl\data.zip
VERBOSE: C:\Scripts\ps\ftpdl\data.zip
And the file downloads nicely.
Well, of course once I post the question I figured it out. My $array contained `n and `r characters. I needed to find and replace both of them out.
$array=$array -replace "`n",""
$array=$array -replace "`r",""