Set-XFileOwner: PowerShell function for chown

Filed under: PowerShell

PowerShell. It runs on Windows, Mac, and Linux. Several modules run on Windows don’t exist on Mac or Linux yet. Many modules will be hard to port over.

The next few years will be exciting. Will the community try to create same API cross-platform modules, or will it create os specific modules?

I see value in having the same cmdlet calls in all environments when possible, but they are painful to make. If Microsoft decides to port over more of the Xamarin/Mono stuff like Mono.Posix into the dotnet core, it may alleviate that. Till then, it may make more sense to wrap the command line utilities.

Things I’ve learned while writing Set-XFileOwner

I’ve spent a few hours over the last few days creating PowerShell functions for chmod and chown. I’ve learned a few things:

  • Windows has Posix features builtin, but some like “Group” only exist for backward compatibility.
  • Windows permissions are painful.
  • icalcs.exe is a powerful tool that provides chmod like functionality.
  • A file or directory object owner is often the same as the group.
  • If a user created a file and an owner is a group like BUILTIN\Administrators, then the user is usually the primary group for the file or directory.

I have to wonder if Windows Posix features will become lit. WSL 2 will run on a Linux kernel on Windows 10. Microsoft now bundles ssh.exe, curl.exe, and tar.exe in Windows 10.

Set-XFileOwner alpha

Windows Example

Set-XFileOwner "BUILTIN\Administrator:BUILTIN\Users" -R "c:\tools"

Linux Example

Set-XFileOwner "www-data:www-data" -R "c:\tools"

Set-XFileOwner Code

Source exists on Github

function Set-XFileOwner() {
    [CmdletBinding(SupportsShouldProcess = $true)]
    Param(
        [ValidateNotNullOrEmpty()]
        [Parameter(ValueFromRemainingArguments = $true)]
        [String] $Path,

        [ValidateNotNullOrEmpty()]
        [Parameter(Position = 0)]
        [String] $Owner,

        [String] $Group,

        [Alias("R")]
        [switch] $Recurse
    )

    PROCESS {
        # From Gz-Core module
        $IsAdmin = Test-UserIsAdministrator 
        if(!$IsAdmin) {
            throw "Set-XFileOwner requires root or admin rights";
        }

        if($Path.EndsWith("*")) {
            throw "Path may not end with '*'"
        }
        $primary = Get-Item $Path -EA SilentlyContinue



        if(!$primary) {
            Write-Warning "Could not locate $Path"
            return;
        }

        if($Owner.Contains(":")) {
            $parts = $Owner.Split(":")
            $Owner = $parts[0]
            $Group = $parts[1]
        }



        $children = @();
        if($Recurse.ToBool()) {
           $children = Get-ChildItem $Path -Recurse -EA SilentlyContinue
        }

        $items = @();
        $items += $primary

        if($children)
        {
            if($children -is [Array]) {
                foreach($item in $children) {
                    $items += $item 
                }
            } else {
                $items += $children
            }
        }

        # From Gz-Core module
        if(Test-OsPlatform "Mac", "Linux") {

            $cmd = "chown"
            $splat = @();
            if(![String]::IsNullOrWhiteSpace($Group)) {
                $splat += "${Owner}:${Group}"
            } else {
                $splat += $Owner
            }

            if($Recurse.ToBool()) {
                $splat += "-R"
            }

            $splat += "$Path"

            if($PSCmdlet.ShouldProcess("$cmd $([string]::Join(" ", $splat))")) {
                & $cmd @splat 
            }

        } else {
            $ntOwner = New-Object  System.Security.Principal.NTAccount($Owner)
            $ntGroup = $ntOwner
            $g = $Owner
            if(![string]::IsNullOrWhiteSpace($Group)) {
                if($Group -ne $Owner) {
                    $ntGroup =  New-Object  System.Security.Principal.NTAccount($Group)
                    $g = $Group;
                }
            }


            if($PSCmdlet.ShouldProcess("SetOwner($Owner),SetGroup($g) on $Path")) {
                foreach($item in $items) {

                    $acl = Get-Acl $item.FullName
                    $acl.SetOwner($ntOwner)
                    $acl.SetGroup($ntGroup)  

                    Set-Acl $item.FullName -AclObject $acl 
                }
            }
        }
    }
}

Nerdy Mishka