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 provideschmod
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
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
}
}
}
}
}