Carbon.FileSystem.psm1
|
using namespace System.Security.AccessControl using namespace System.Collections # Copyright WebMD Health Services # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License #Requires -Version 5.1 Set-StrictMode -Version 'Latest' # Functions should use $moduleRoot as the relative root from which to find # things. A published module has its function appended to this file, while a # module in development has its functions in the Functions directory. $script:moduleRoot = $PSScriptRoot if (-not (Test-Path 'variable:IsWindows')) { $script:IsWindows = $true $script:IsLinux = $false $script:IsMacOS = $false } $psModulesPath = Join-Path -Path $script:moduleRoot -ChildPath 'M' -Resolve Import-Module -Name (Join-Path -Path $psModulesPath -ChildPath 'Carbon.Security\Carbon.Security.psm1' -Resolve) ` -Function @('Get-CPermission', 'Grant-CPermission', 'Revoke-CPermission', 'Test-CPermission') ` -Verbose:$false Import-Module -Name (Join-Path -Path $psModulesPath -ChildPath 'Carbon.Accounts\Carbon.Accounts.psm1' -Resolve) ` -Function @('Resolve-CPrincipal', 'Resolve-CPrincipalName') ` -Verbose:$false Add-Type @' using System; using System.Text; using System.Collections.Generic; using System.Runtime.InteropServices; namespace Carbon.FileSystem { public static class Kernel32 { #region WinAPI P/Invoke declarations [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr FindFirstFileNameW(string lpFileName, uint dwFlags, ref uint StringLength, StringBuilder LinkName); [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool FindNextFileNameW(IntPtr hFindStream, ref uint StringLength, StringBuilder LinkName); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool FindClose(IntPtr hFindFile); [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool GetVolumePathName(string lpszFileName, [Out] StringBuilder lpszVolumePathName, uint cchBufferLength); public static readonly IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1); // 0xffffffff; public const int MAX_PATH = 65535; // Max. NTFS path length. #endregion } } '@ # Store each of your module's functions in its own file in the Functions # directory. On the build server, your module's functions will be appended to # this file, so only dot-source files that exist on the file system. This allows # developers to work on a module without having to build it first. Grab all the # functions that are in their own files. $functionsPath = Join-Path -Path $moduleRoot -ChildPath 'Functions\*.ps1' if( (Test-Path -Path $functionsPath) ) { foreach( $functionPath in (Get-Item $functionsPath) ) { . $functionPath.FullName } } function ConvertTo-CarbonSecurityApplyTo { [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [AllowEmptyString()] [AllowNull()] [ValidateSet('FolderOnly', 'FolderSubfoldersAndFiles', 'FolderAndSubfolders', 'FolderAndFiles', 'SubfoldersAndFilesOnly', 'SubfoldersOnly', 'FilesOnly')] [String] $ApplyTo ) process { $map = @{ 'FolderOnly' = 'ContainerOnly'; 'FolderSubfoldersAndFiles' = 'ContainerSubcontainersAndLeaves'; 'FolderAndSubfolders' = 'ContainerAndSubcontainers'; 'FolderAndFiles' = 'ContainerAndLeaves'; 'SubfoldersAndFilesOnly' = 'SubcontainersAndLeavesOnly'; 'SubfoldersOnly' = 'SubcontainersOnly'; 'FilesOnly' = 'LeavesOnly'; } if (-not $ApplyTo) { return } return $map[$ApplyTo] } } function Get-CNtfsHardLink { <# .SYNOPSIS Retrieves hard link targets from a file. .DESCRIPTION Get-CNtfsHardLink retrieves hard link targets from a file given a file path. This fixes compatibility issues between Windows PowerShell and PowerShell Core when retrieving targets from a hard link. .EXAMPLE Get-CNtfsHardLink -Path $Path Demonstrates how to retrieve a hard link given a file path. #> [CmdletBinding()] param( # The path whose hard links to get/return. Must exist. [Parameter(Mandatory)] [String] $Path ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState if (-not $IsWindows) { $msg = 'The Get-CNtfsHardLink function is only supported on Windows.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } if( -not (Resolve-Path -LiteralPath $Path) ) { return } try { $sbPath = [Text.StringBuilder]::New([Carbon.FileSystem.Kernel32]::MAX_PATH) $charCount = [uint32]$sbPath.Capacity; # in/out character-count variable for the WinAPI calls. # Get the volume (drive) part of the target file's full path (e.g., @"C:\") [void][Carbon.FileSystem.Kernel32]::GetVolumePathName($Path, $sbPath, $charCount) $volume = $sbPath.ToString(); # Trim the trailing "\" from the volume path, to enable simple concatenation # with the volume-relative paths returned by the FindFirstFileNameW() and FindFirstFileNameW() functions, # which have a leading "\" $volume = $volume.Substring(0, $volume.Length - 1); # Loop over and collect all hard links as their full paths. [IntPtr]$findHandle = [IntPtr]::Zero $findHandle = [Carbon.FileSystem.Kernel32]::FindFirstFileNameW($Path, 0, [ref]$charCount, $sbPath) if( [Carbon.FileSystem.Kernel32]::INVALID_HANDLE_VALUE -eq $findHandle) { $errorCode = [Runtime.InteropServices.Marshal]::GetLastWin32Error() $msg = "Failed to find hard links to path ""$($Path | Split-Path -Relative)"": the system error code is ""$($errorCode)""." Write-Error $msg -ErrorAction $ErrorActionPreference return } do { Join-Path -Path $volume -ChildPath $sbPath.ToString() | Write-Output # Add the full path to the result list. $charCount = [uint32]$sbPath.Capacity; # Prepare for the next FindNextFileNameW() call. } while( [Carbon.FileSystem.Kernel32]::FindNextFileNameW($findHandle, [ref]$charCount, $sbPath) ) [void][Carbon.FileSystem.Kernel32]::FindClose($findHandle); } catch { Write-Error -Message $_ -ErrorAction $ErrorActionPreference } } function Get-FileHardLink { <# .SYNOPSIS ***OBSOLETE.*** Use Get-CNtfsHardLink instead. .DESCRIPTION ***OBSOLETE.*** Use Get-CNtfsHardLink instead. .EXAMPLE Get-CNtfsHardLink -Path $Path Demonstrates that you should use `Get-CNtfsHardLink` instead. #> [CmdletBinding()] param( # The path whose hard links to get/return. Must exist. [Parameter(Mandatory)] [String] $Path ) $msg = 'The Get-FileHardLink function is obsolete and will removed in the next major version of ' + 'Carbon.FileSystem. Please use Get-CNtfsHardLink instead.' Write-Warning -Message $msg Get-CNtfsHardLink @PSBoundParameters } function Get-CNtfsPermission { <# .SYNOPSIS Gets the permissions (access control rules) for a file or directory. .DESCRIPTION The `Get-CNtfsPermission` function gets permissions on a file or directory. Permissions returned are the `[Security.AccessControl.FileSystemAccessRule]` objects from the file/directory's ACL. By default, all non-inherited permissions are returned. Pass the path to the file/directory whose permissions to get to the `Path` parameter. To also get inherited permissions, use the `Inherited` switch. To get the permissions a specific identity has on the file/directory, pass that username/group name to the `Identity` parameter. If the identity doesn't exist, or it doesn't have any permissions, no error is written and nothing is returned. .OUTPUTS System.Security.AccessControl.FileSystemAccessRule. .LINK Get-CNtfsPermission .LINK Grant-CNtfsPermission .LINK Revoke-CNtfsPermission .LINK Test-CNtfsPermission .EXAMPLE Get-CNtfsPermission -Path 'C:\Windows' Returns `System.Security.AccessControl.FileSystemAccessRule` objects for all the non-inherited rules on `C:\windows`. .EXAMPLE Get-CNtfsPermission -Path 'C:\Windows' -Inherited Returns `System.Security.AccessControl.RegistryAccessRule` objects for all the inherited and non-inherited rules on `hklm:\software`. .EXAMPLE Get-CNtfsPermission -Path 'C:\Windows' -Idenity Administrators Returns `System.Security.AccessControl.FileSystemAccessRule` objects for all the `Administrators'` rules on `C:\windows`. #> [CmdletBinding()] [OutputType([Security.AccessControl.FileSystemAccessRule])] param( # The path to the file/directory whose permissions (i.e. access control rules) to return. Wildcards supported. [Parameter(Mandatory, ValueFromPipeline)] [String] $Path, # The identity whose permissiosn (i.e. access control rules) to return. By default, all non-inherited # permissions are returned. [String] $Identity, # Return inherited permissions in addition to explicit permissions. [switch] $Inherited ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if (-not $IsWindows) { $msg = 'The Get-CNtfsPermission function is only supported on Windows.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } Get-CPermission @PSBoundParameters } } function Get-CTempPath { <# .SYNOPSIS Gets the path to the current user's temporary directory. .DESCRIPTION The `Get-CTempPath` function gets the path to the current user's temporary directory, as returned by `[IO.Path]::GetTempPath()`, which works across operating systems. The path is not guaranteed to actually exist. To ensure the temp path exists, use the `-Create` switch, and `Get-CTempPath` will ensure the path it returns exists. On Windows, the `System.IO.Path.GetTempPath()` function uses the [GetTempPath2 function](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppath2a), which, for non-system processes, checks for the existence of environment variables in the following order and uses the first path found: * `TMP` * `TEMP` * `USERPROFILE` * The Windows directory For system processes, returns the value of the `SystemTemp` environment variable if it is set, or `C:\Windows\SystemTemp` if it is not. On Linux and macOS, returns the value of the `TMPDIR` environment variable if it is set, or `/tmp/` if it is not. .EXAMPLE Get-CTempPath Demonstrates how to get the path to the current user's temporary directory. #> [CmdletBinding()] param( # If set, and the temp path does not exist, it is created. [switch] $Create ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $path = [IO.Path]::GetTempPath() if ($Create) { Install-CDirectory -Path $path } return $path } function Grant-CNtfsPermission { <# .SYNOPSIS Grants permission on folders and files. .DESCRIPTION The `Grant-CNtfsPermission` functions grants permissions to folders and files. Pass the folder/file path to the `Path` parameter, the user/group name to the `Identity` parameter, and the permissions to the `Permission` parameter. By default, the permissions are applied to the folder and inherited to all its subfolders and files. To control how the permissions are applied, use the `ApplyTo` parameter. If you want permissions to only apply to child files and folders, use the `OnlyApplyToChildFilesAndFolders` switch. By default, an "Allow" permission is granted. To add a "Deny" permission, set the value of the `Type` parameter to `Deny`. All existing, non-inherited permissions for the given identity are removed first. If you want to preserve a user/group's existing permissions, use the `Append` switch. To remove *all* non-inherited permissions except the permission being granted, use the `Clear` switch. The permission is only granted if it doesn't exist. To always grant the permission, use the `Force` switch. To get the permission back as a `[System.Security.AccessControl.FileSystemAccessRule]` object, use the `PassThru` switch. .OUTPUTS System.Security.AccessControl.FileSystemAccessRule. .LINK Get-CNtfsPermission .LINK Revoke-CNtfsPermission .LINK Test-CNtfsPermission .LINK http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx .LINK http://msdn.microsoft.com/en-us/magazine/cc163885.aspx#S3 .EXAMPLE Grant-CNtfsPermission -Identity ENTERPRISE\Engineers -Permission FullControl -Path C:\EngineRoom Grants the Enterprise's engineering group full control on the engine room. Very important if you want to get anywhere. .EXAMPLE Grant-CNtfsPermission -Identity ENTERPRISE\Engineers -Permission FullControl -Path C:\EngineRoom -Clear Grants the Enterprise's engineering group full control on the engine room. Any non-inherited, existing access rules are removed from `C:\EngineRoom`. .EXAMPLE Grant-CNtfsPermission -Identity BORG\Locutus -Permission FullControl -Path 'C:\EngineRoom' -Type Deny Demonstrates how to grant deny permissions on an objecy with the `Type` parameter. .EXAMPLE Grant-CNtfsPermission -Path C:\Bridge -Identity ENTERPRISE\Wesley -Permission 'Read' -ApplyTo ContainerAndSubContainersAndLeaves -Append Grant-CNtfsPermission -Path C:\Bridge -Identity ENTERPRISE\Wesley -Permission 'Write' -ApplyTo ContainerAndLeaves -Append Demonstrates how to grant multiple access rules to a single identity with the `Append` switch. In this case, `ENTERPRISE\Wesley` will be able to read everything in `C:\Bridge` and write only in the `C:\Bridge` directory, not to any sub-directory. #> [Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '')] [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='DefaultAppliesToFlags')] [OutputType([Security.AccessControl.FileSystemAccessRule])] param( # The folder/file path on which the permissions should be granted. [Parameter(Mandatory)] [String] $Path, # The user or group getting the permissions. [Parameter(Mandatory)] [String] $Identity, # The permissions to grant. See # [System.Security.AccessControl.FileSystemRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx) # for the list of rights with descriptions. [Parameter(Mandatory)] [FileSystemRights[]] $Permission, # How to apply the permissions. The default is `FolderSubfoldersAndFiles`. Valid values are: # # * FolderOnly # * FolderSubfoldersAndFiles # * FolderAndSubfolders # * FolderAndFiles # * SubfoldersAndFilesOnly # * SubfoldersOnly # * FilesOnly [Parameter(Mandatory, ParameterSetName='SetAppliesToFlags')] [ValidateSet('FolderOnly', 'FolderSubfoldersAndFiles', 'FolderAndSubfolders', 'FolderAndFiles', 'SubfoldersAndFilesOnly', 'SubfoldersOnly', 'FilesOnly')] [String] $ApplyTo, # Only apply the permissions to files and/or folders within the folder. Don't set this if the Path parameter is # to a file. [Parameter(ParameterSetName='SetAppliesToFlags')] [switch] $OnlyApplyToChildFilesAndFolders, # The type of rule to apply, either `Allow` or `Deny`. The default is `Allow`, which will allow access to the # item. The other option is `Deny`, which will deny access to the item. [AccessControlType] $Type = [AccessControlType]::Allow, # Removes all non-inherited permissions on the item. [switch] $Clear, # Returns an object representing the permission created or set on the `Path`. The returned object will have a # `Path` propery added to it so it can be piped to any cmdlet that uses a path. [switch] $PassThru, # Grants permissions, even if they are already present. [switch] $Force, # When set, adds the permissions as a new access rule instead of replacing any existing access rules. [switch] $Append ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if (-not $IsWindows) { $msg = 'The Grant-CNtfsPermission function is only supported on Windows.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } if (-not $ApplyTo -and (Test-Path -Path $Path -PathType Container)) { $ApplyTo = 'FolderSubfoldersAndFiles' } if ($ApplyTo) { $PSBoundParameters['ApplyTo'] = $ApplyTo | ConvertTo-CarbonSecurityApplyTo } if ($PSBoundParameters.ContainsKey('OnlyApplyToChildFilesAndFolders')) { $PSBoundParameters.Remove('OnlyApplyToChildFilesAndFolders') $PSBoundParameters['OnlyApplyToChildren'] = $OnlyApplyToChildFilesAndFolders } Grant-CPermission @PSBoundParameters } function Install-CDirectory { <# .SYNOPSIS Creates a directory, if it doesn't exist. .DESCRIPTION The `Install-CDirectory` function creates a directory. If the directory already exists, it does nothing. If any parent directories don't exist, they are created, too. To return a `DirectoryInfo` object for the directory, use the `-PassThru` switch. If the path exists and is a file, writes an error. Use the `-Force` switch to delete the file and create the directory in its place. .EXAMPLE Install-CDirectory -Path 'C:\Projects\Carbon' Demonstrates how to use create a directory. In this case, the directories `C:\Projects` and `C:\Projects\Carbon` will be created if they don't exist. #> [CmdletBinding(SupportsShouldProcess)] param( # The path to the directory to create. [Parameter(Mandatory, ValueFromPipeline)] [String] $Path, # If set, returns a `DirectoryInfo` object for the directory. [switch] $PassThru, # If set and the target path exists and is a file, deletes the file and creates the directory in its place. # Otherwise, writes an error that the path exists and is a file. [switch] $Force ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ((Test-Path -LiteralPath $Path -PathType Leaf)) { if (-not $Force) { $msg = "Failed to install directory ""${Path}"" because that path exists and is a file. Use the -Force " + 'switch to replace the file with a directory.' Write-Error $msg -ErrorAction $ErrorActionPreference return } Write-Information "Deleting file ""${Path}""." -InformationAction $InformationPreference Remove-Item -LiteralPath $Path -Force } if (-not (Test-Path -LiteralPath $Path -PathType Container)) { Write-Information "Creating directory ""${Path}""." -InformationAction $InformationPreference New-Item -Path $Path -ItemType 'Directory' -Force | Out-String | Write-Verbose } if ($PassThru -and (Test-Path -LiteralPath $Path)) { Get-Item -LiteralPath $Path } } } function New-CTempDirectory { <# .SYNOPSIS Creates a new temporary directory with a random name. .DESCRIPTION A new temporary directory is created in the current user's temp directory, as returned by `[IO.Path]::GetTempPath()`. The directory's name is created using the `Path` class's [GetRandomFileName method](http://msdn.microsoft.com/en-us/library/system.io.path.getrandomfilename.aspx). To add a custom prefix to the directory name, use the `Prefix` parameter. If you pass in a path, only its name will be used. In this way, you can pass `$MyInvocation.MyCommand.Definition` (PowerShell 2) or `$PSCommandPath` (PowerShell 3+), which will help you identify what scripts are leaving cruft around in the temp directory. .LINK http://msdn.microsoft.com/en-us/library/system.io.path.getrandomfilename.aspx .EXAMPLE New-CTempDirectory Demonstrates how to create a new temporary directory, e.g. `C:\Users\ajensen\AppData\Local\Temp\5pobd3tu.5rn`. .EXAMPLE New-CTempDirectory -Prefix 'Carbon' Demonstrates how to create a new temporary directory with a custom prefix for its name, e.g. `C:\Users\ajensen\AppData\Local\Temp\Carbon5pobd3tu.5rn`. .EXAMPLE New-CTempDirectory -Prefix $MyInvocation.MyCommand.Definition Demonstrates how you can use `$MyInvocation.MyCommand.Definition` in PowerShell 2 to create a new, temporary directory, named after the currently executing scripts, e.g. `C:\Users\ajensen\AppData\Local\Temp\New-CTempDirectory.ps15pobd3tu.5rn`. .EXAMPLE New-CTempDirectory -Prefix $PSCommandPath Demonstrates how you can use `$PSCommandPath` in PowerShell 3+ to create a new, temporary directory, named after the currently executing scripts, e.g. `C:\Users\ajensen\AppData\Local\Temp\New-CTempDirectory.ps15pobd3tu.5rn`. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [CmdletBinding(SupportsShouldProcess)] [OutputType([IO.DirectoryInfo])] param( # A prefix to use, so you can more easily identify *what* created the temporary directory. If you pass in a # path, its name will be used as the prefix. [String] $Prefix ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $tempDir = [IO.Path]::GetRandomFileName() if( $Prefix ) { $Prefix = Split-Path -Leaf -Path $Prefix $tempDir = '{0}{1}' -f $Prefix,$tempDir } $tempDir = Join-Path -Path (Get-CTempPath) -ChildPath $tempDir Install-CDirectory -Path $tempDir -PassThru } function Revoke-CNtfsPermission { <# .SYNOPSIS Revokes *explicit* permissions on folders and files. .DESCRIPTION Revokes all of user/group's *explicit* permissions on a folder or file. Only explicit permissions are considered; inherited permissions are ignored. If the identity doesn't have permission, nothing happens, not even errors written out. .LINK Get-CNtfsPermission .LINK Grant-CNtfsPermission .LINK Test-CNtfsPermission .EXAMPLE Revoke-CNtfsPermission -Identity ENTERPRISE\Engineers -Path 'C:\EngineRoom' Demonstrates how to revoke all of the 'Engineers' permissions on the `C:\EngineRoom` directory. #> [Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '')] [CmdletBinding(SupportsShouldProcess)] param( # The folder or file path on which the permissions should be revoked. [Parameter(Mandatory)] [String] $Path, # The identity losing permissions. [Parameter(Mandatory)] [String] $Identity ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if (-not $IsWindows) { $msg = 'The Revoke-CNtfsPermission function is only supported on Windows.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } Revoke-CPermission @PSBoundParameters } function Set-CNtfsOwner { <# .SYNOPSIS Sets the owner of an NTFS file or directory. .DESCRIPTION The `Set-CNtfsOwner` function sets the owner of an NTFS file or directory. Pass the path of the file or directory to the `Path` parameter. Pass the new owner to the `Identity` parameter. If the file or directory isn't owned by the new owner, its ACL is updated. Otherwise, nothing happens. You can also pipe file system objects to the function in place of passing a path. This function requires administrative privileges. .EXAMPLE Set-CNtfsOwner -Path $Path -Identity $username Demonstrates how to set the owner of a file system object to a specific principal. In this example, the file or directory at `$Path` will be owned by `$username`. .EXAMPLE Get-ChildItem -Path $directory | Set-CNtfsOwner -Identity $username Demonstrates that you can pipe items to `Set-CNtfsOwner` to mass change the owner. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('FullName')] [String] $Path, [Parameter(Mandatory)] [String] $Identity ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState if (-not $IsWindows) { $msg = 'The Set-CNtfsOwner function is only supported on Windows.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } } process { if (-not $IsWindows) { return } if (-not (Test-Path -Path $Path)) { $msg = "Failed to set owner on ""${Path}"" to ""${Identity}"" because that path does not exist." Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $newOwner = Resolve-CPrincipal -Name $Identity if (-not $newOwner) { Write-Error -Message "Principal ""${Identity}"" not found." -ErrorAction $ErrorActionPreference return } $paths = Resolve-Path -Path $Path foreach ($pathItem in $paths) { $acl = Get-Acl -LiteralPath $pathItem $currentOwner = Resolve-CPrincipalName -Name $acl.Owner if ($currentOwner -eq $newOwner.FullName) { Write-Verbose "Principal ""$($newOwner.FullName)"" already owns ""${pathItem}""." return } Write-Information "Changing owner of ""${pathItem}"" from ""${currentOwner}"" to ""$($newOwner.FullName)""." $acl.SetOwner($newOwner.Sid) Set-Acl -LiteralPath $pathItem -AclObject $acl } } } function Set-CTempPath { <# .SYNOPSIS Sets the path to the current user's temp directory. .DESCRIPTION The `Set-CTempPath` function sets the path to the current user's temp directory. Pass the path for the temp directory to the `Path` parameter. On Windows, for non-system processes, sets the `TMP` environment variable to the path. For system processes, sets the `SystemTemp` environment variable. On Linux and macOS, sets the `TMPDIR` environment variable. If path is a relative path, it will be assumed to be relative to PowerShell's current directory (i.e. the return value of `Get-Location`), and converted to an absolute path. Use the `-Create` switch to create the temp path if it doesn't exist. Note that on Windows, if setting the system user's temp path, the directory should only be accessible to the system user: ACL inheritance should be turned off and the only permission granted should be full control to the SYSTEM account. Otherwise, unprivileged users may be able to view sensitive files. .EXAMPLE Set-CTempPath -Path 'C:\MyTemp' Demonstrates how to set the current user's temp path by passing the path to the `Path` parameter. .EXAMPLE Set-CTempPath -Path 'C:\MyTemp' -Create Demonstrates how to ensure the temp path exists by using the `-Create` switch. .EXAMPLE 'C:\MyTemp' | Set-CTempPath Demonstrates that you can pipe the path to `Set-CTempPath`. #> [CmdletBinding(SupportsShouldProcess)] param( # The path to set as the current user's temp path. Can be piped in or passed as an argument. If a relative path # is passed, it is assumed to be relative to PowerShell's current directory (i.e. the return value of # `Get-Location`), and converted to an absolute path. [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [string] $Path, # If set, creates the temp path if it doesn't exist. [switch] $Create ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if (-not [IO.Path]::IsPathRooted($Path)) { $Path = Join-Path -Path (Get-Location) -ChildPath $Path } $Path = [IO.Path]::GetFullPath($Path) $Path = Join-Path -Path $Path -ChildPath ([IO.Path]::DirectorySeparatorChar) if ($Create) { Install-CDirectory -Path $Path } $envVarName = 'TMPDIR' if ($IsWindows) { if ([Security.Principal.WindowsIdentity]::GetCurrent().IsSystem) { $envVarName = 'SystemTemp' } else { $envVarName = 'TMP' } } $currentTempPath = Get-CTempPath if ($currentTempPath -eq $Path) { Write-Verbose "Temp path environment variable ${envVarName} already set to ""${Path}""." return } $action = "Creating" if ((Test-Path -Path "env:${envVarName}")) { $action = "Setting" } $target = "${envVarName} environment variable" $actionMsg = "$($action.ToLowerInvariant()) to ""${Path}""" if ($PSCmdlet.ShouldProcess($target, $actionMsg)) { $msg = "${action} temp path environment variable ${envVarName} to ""${Path}""." Write-Information $msg -InformationAction $InformationPreference [Environment]::SetEnvironmentVariable($envVarName, $Path, [EnvironmentVariableTarget]::Process) } } } function Test-CNtfsPermission { <# .SYNOPSIS Tests if permissions are set on a folder or file. .DESCRIPTION The `Test-CNtfsPermission` function tests if an identity has a permission on a file/folder. Pass the path to check to the `Path` parameter, the user/group name to the `Identity` parameter, and the permission to check for to the `Permission` parameter. If the user/group has the given permission on the given path, the function returns `$true`, otherwise it returns `$false`. Inherited permissions are *not* checked by default. To check inherited permission, use the `-Inherited` switch. By default, the permission check is not exact, i.e. the user may have additional permissions to what you're checking. If you want to make sure the user has *exactly* the permission you want, use the `-Strict` switch. Please note that by default, NTFS will automatically add/grant `Synchronize` permission on an item, which is handled by this function. You can also test how the permission is inherited by using the `ApplyTo` and `OnlyApplyToChildFilesAndFolders` parameters. .OUTPUTS System.Boolean. .LINK Get-CNtfsPermission .LINK Grant-CNtfsPermission .LINK Revoke-CNtfsPermission .LINK http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx .EXAMPLE Test-CNtfsPermission -Identity 'STARFLEET\JLPicard' -Permission 'FullControl' -Path 'C:\Enterprise\Bridge' Demonstrates how to check that Jean-Luc Picard has `FullControl` permission on the `C:\Enterprise\Bridge`. .EXAMPLE Test-CNtfsPermission -Identity 'STARFLEET\Worf' -Permission 'Write' -ApplyTo 'FolderOnly' -Path 'C:\Enterprise\Brig' Demonstrates how to test for inheritance/propogation flags, in addition to permissions. #> [CmdletBinding(DefaultParameterSetName='SkipAppliesToFlags')] param( # The path to a folder/file on which the permissions should be checked. [Parameter(Mandatory)] [String] $Path, # The user or group name whose permissions to check. [Parameter(Mandatory)] [String] $Identity, # The permission to test for: e.g. FullControl, Read, etc. See # [System.Security.AccessControl.FileSystemRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx) # for the list of rights with descriptions. [Parameter(Mandatory)] [FileSystemRights[]] $Permission, # Checks how the permission is inherited. By default, the permission's inheritance is ignored. # # Valid values are: # # * FolderOnly # * FolderSubfoldersAndFiles # * FolderAndSubfolders # * FolderAndFiles # * SubfoldersAndFilesOnly # * SubfoldersOnly # * FilesOnly [Parameter(Mandatory, ParameterSetName='TestAppliesToFlags')] [ValidateSet('FolderOnly', 'FolderSubfoldersAndFiles', 'FolderAndSubfolders', 'FolderAndFiles', 'SubfoldersAndFilesOnly', 'SubfoldersOnly', 'FilesOnly')] [String] $ApplyTo, # Checks that the permissions are only applied to child files and folders. By default, the permission's # inheritnace is ignored. [Parameter(ParameterSetName='TestAppliesToFlags')] [switch] $OnlyApplyToChildFilesAndFolders, # Include inherited permissions in the check. [switch] $Inherited, # Check for the exact permissions and how the permission is applied, i.e. make sure the identity has # *only* the permissions you specify. [switch] $Strict ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if (-not $IsWindows) { $msg = 'The Test-CNtfsPermission function is only supported on Windows.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } if ($PSCmdlet.ParameterSetName -eq 'TestAppliesToFlags') { if ($ApplyTo) { $PSBoundParameters['ApplyTo'] = $ApplyTo | ConvertTo-CarbonSecurityApplyTo } $PSBoundParameters.Remove('OnlyApplyToChildFilesAndFolders') | Out-Null $PSBoundParameters['OnlyApplyToChildren'] = $OnlyApplyToChildFilesAndFolders } Test-CPermission @PSBoundParameters } function Uninstall-CDirectory { <# .SYNOPSIS Removes a directory, if it exists. .DESCRIPTION The `Uninstall-CDirectory` function removes a directory. If the directory doesn't exist, it does nothing. If the directory has any files or sub-directories, you will be prompted to confirm the deletion of the directory and all its contents. To avoid the prompt, use the `-Recurse` switch. If the path to delete is to a file, the function writes an error. Use the `-Force` switch to delete the path even if it is a file. .EXAMPLE Uninstall-CDirectory -Path 'C:\Projects\Carbon' Demonstrates how to remove/delete a directory. In this case, the directory `C:\Projects\Carbon` will be deleted, if it exists. .EXAMPLE Uninstall-CDirectory -Path 'C:\Projects\Carbon' -Recurse Demonstrates how to remove/delete a directory that has items in it. In this case, the directory `C:\Projects\Carbon` *and all of its files and sub-directories* will be deleted, if the directory exists. .EXAMPLE Get-ChildItem -Path 'C:\Projects' -Directory | Uninstall-CDirectory -Recurse Demonstrates that you can pipe paths or directory objects to `Uninstall-CDirectory`. #> [CmdletBinding(SupportsShouldProcess)] param( # The path to the directory to delete. Wildcards *not* supported. [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('FullName')] [String] $Path, # Delete the directory *and* everything under it. [switch] $Recurse, # Delete the directory even if it is a file. [switch] $Force ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ((Test-Path -LiteralPath $Path -PathType Leaf)) { if (-not $Force) { $msg = "Failed to delete directory ""${Path}"" because that path is a file. Use the -Force switch to " + 'delete that path even if it is a file.' Write-Error $msg -ErrorAction $ErrorActionPreference return } Write-Information "Deleting file ""${Path}""." -InformationAction $InformationPreference Remove-Item -LiteralPath $Path -Force return } if ((Test-Path -LiteralPath $Path -PathType Container)) { Write-Information "Deleting directory ""${Path}""." -InformationAction $InformationPreference Remove-Item -LiteralPath $Path -Recurse:$Recurse } } } function Use-CallerPreference { <# .SYNOPSIS Sets the PowerShell preference variables in a module's function based on the callers preferences. .DESCRIPTION Script module functions do not automatically inherit their caller's variables, including preferences set by common parameters. This means if you call a script with switches like `-Verbose` or `-WhatIf`, those that parameter don't get passed into any function that belongs to a module. When used in a module function, `Use-CallerPreference` will grab the value of these common parameters used by the function's caller: * ErrorAction * Debug * Confirm * InformationAction * Verbose * WarningAction * WhatIf This function should be used in a module's function to grab the caller's preference variables so the caller doesn't have to explicitly pass common parameters to the module function. This function is adapted from the [`Get-CallerPreference` function written by David Wyatt](https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d). There is currently a [bug in PowerShell](https://connect.microsoft.com/PowerShell/Feedback/Details/763621) that causes an error when `ErrorAction` is implicitly set to `Ignore`. If you use this function, you'll need to add explicit `-ErrorAction $ErrorActionPreference` to every `Write-Error` call. Please vote up this issue so it can get fixed. .LINK about_Preference_Variables .LINK about_CommonParameters .LINK https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d .LINK http://powershell.org/wp/2014/01/13/getting-your-script-module-functions-to-inherit-preference-variables-from-the-caller/ .EXAMPLE Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Demonstrates how to set the caller's common parameter preference variables in a module function. #> [CmdletBinding()] param ( [Parameter(Mandatory)] #[Management.Automation.PSScriptCmdlet] # The module function's `$PSCmdlet` object. Requires the function be decorated with the `[CmdletBinding()]` # attribute. $Cmdlet, [Parameter(Mandatory)] # The module function's `$ExecutionContext.SessionState` object. Requires the function be decorated with the # `[CmdletBinding()]` attribute. # # Used to set variables in its callers' scope, even if that caller is in a different script module. [Management.Automation.SessionState]$SessionState ) Set-StrictMode -Version 'Latest' # List of preference variables taken from the about_Preference_Variables and their common parameter name (taken # from about_CommonParameters). $commonPreferences = @{ 'ErrorActionPreference' = 'ErrorAction'; 'DebugPreference' = 'Debug'; 'ConfirmPreference' = 'Confirm'; 'InformationPreference' = 'InformationAction'; 'VerbosePreference' = 'Verbose'; 'WarningPreference' = 'WarningAction'; 'WhatIfPreference' = 'WhatIf'; } foreach( $prefName in $commonPreferences.Keys ) { $parameterName = $commonPreferences[$prefName] # Don't do anything if the parameter was passed in. if( $Cmdlet.MyInvocation.BoundParameters.ContainsKey($parameterName) ) { continue } $variable = $Cmdlet.SessionState.PSVariable.Get($prefName) # Don't do anything if caller didn't use a common parameter. if( -not $variable ) { continue } if( $SessionState -eq $ExecutionContext.SessionState ) { Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($variable.Name, $variable.Value) } } } |