Posts Tagged ‘FileSystem’

Have you ever tried to use PowerShell (or .NET) to mess with file or folder permissions and wondered what the ‘Synchronize’ right means? It pops up all over the place, like on existing ACEs:

what_does_synchronize_mean_example
And on new ACEs that you create (even if you don’t include it):

synchronize_2

If you try to check permissions using the ACL Editor, you won’t see it anywhere. Here’s the ACE for ‘Users’ from the ‘C:\powershell’ folder shown in the first screenshot above:

synchronize_3_acl_editor

So, what is this mysterious right, and why does PowerShell/.NET insist on showing it everywhere? Let’s start with the definition from MSDN:

The right to use the object for synchronization. This enables a thread to wait until the object is in the signaled state. Some object types do not support this access right.

The first time I read that, I didn’t think it sounded all that important. It turns out, though, that it’s critical for working with files and folders.

Before I explain a little bit more about why that right shows up, let’s briefly cover what makes up an access control entry’s access mask. It’s a 32-bit integer, which means that, theoretically, there are 32 different rights that can be controlled (32 bits means 32 different on/off switches). In practice, you don’t get that many rights, though. No matter what type of object you’re working with (file, folder, registry key, printer, service, AD object, etc), those 32-bits are broken down like this:

  • Bits 0-15 are used for object specific rights. These rights differ between object types, e.g., bit 1 for a file means ‘CreateFiles’, for a registry key means ‘SetValue’, and for an AD object means ‘DeleteChild’.
  • Bits 16-23 are used for “Standard access rights”. These rights are shared among the different types of securable objects, e.g., bit 16 corresponds to the right to delete the object, and it means the same thing for files, folders, registry keys, etc. As far as I know, only bits 16-20 in this range do anything.
  • Bit 24 controls access to the SACL.
  • Bits 25-27 are reserved and not currently used.
  • Bits 28-31 are “Generic access rights”. They are a shorthand way of specifying four common access masks: read, write, execute, and all (full control). These bits are translated into a combination of object specific and standard access rights, and the translation differs depending on the type of object the ACE belongs to.

The ‘Synchronize’ right is controlled by bit 20, so it’s one of the standard access rights:

PS> [math]::Log([System.Security.AccessControl.FileSystemRights]::Synchronize, 2)
20

If you manage to remove the right (or if you explicitly deny it), bad things will happen. For folders, you won’t be able to see child items. For files, you won’t be able to view the contents. It turns out some very important Win32 APIs require that right to be granted, at least for file and folder objects. You get a hint of it from this MSDN page:

Note that you cannot use an access-denied ACE to deny only GENERIC_READ or only GENERIC_WRITE access to a file. This is because for file objects, the generic mappings for both GENERIC_READ or GENERIC_WRITE include the SYNCHRONIZE access right. If an ACE denies GENERIC_WRITE access to a trustee, and the trustee requests GENERIC_READ access, the request will fail because the request implicitly includes SYNCHRONIZE access which is implicitly denied by the ACE, and vice versa. Instead of using access-denied ACEs, use access-allowed ACEs to explicitly allow the permitted access rights.

I couldn’t do a good job of translating the actual definition of ‘Synchronize’ earlier, but I think I can translate this paragraph. It’s saying that you can’t create an access denied ACE for just GENERIC_READ or just GENERIC_WRITE as they are defined, because each of those sets of rights include ‘Synchronize’, and you’d effectively be denying both sets of rights. GENERIC_READ (bit 31) and GENERIC_WRITE (bit 30) are two of the four “Generic access rights” mentioned above. When they’re translated/mapped to their object-specific rights, they make up a combination of bits 0-20 of the access mask (object specific and standard rights).

Once translated, GENERIC_READ is very similar to [FileSystemRights]::Read, and GENERIC_WRITE is very similar to [FileSystemRights]::Write. From the same MSDN page, here’s a list of the object specific and standard rights that make up the generic rights (the [FileSystemRights] equivalents are listed in parenthesis):

  • GENERIC_READ
    • FILE_READ_ATTRIBUTES (ReadAttributes)
    • FILE_READ_DATA (ReadData)
    • FILE_READ_EA (ReadExtendedAttributes)
    • STANDARD_RIGHTS_READ (ReadPermissions)
    • SYNCHRONIZE (Synchronize)
  • GENERIC_WRITE
    • FILE_APPEND_DATA (AppendData)
    • FILE_WRITE_ATTRIBUTES (WriteAttributes)
    • FILE_WRITE_DATA (WriteData)
    • FILE_WRITE_EA (WriteExtendedAttributes)
    • STANDARD_RIGHTS_WRITE (ReadPermissions)
    • SYNCHRONIZE (Synchronize)

The [FileSystemRights] enumeration has values for Read and Write that almost match what is defined above. Since PowerShell coerces strings into enumerations, and enumerations will attempt to show you combined flags where possible, let’s take a look at how those rights are seen when they’re cast as a FileSystemRights enumeration:

synchronize_4_generic_to_filesystemrights

Hopefully that makes sense. It’s showing that GENERIC_READ in [FileSystemRights] translates to ‘Read, Synchronize’, which means that GENERIC_READ is not the same as [FileSystemRights]::Read since ‘Read’ doesn’t include ‘Synchronize’. GENERIC_WRITE and [FileSystemRights]::Write are almost the same, except [FileSystemRights]::Write is also missing ‘ReadPermissions’ in addition to ‘Synchronize’.

So, why don’t the generic rights translate to the same numeric values for [FileSystemRights]? It goes back to the warning from the MSDN page above: if you want to deny ‘Read’ or ‘Write’ only, you have to remove the ‘Synchronize’ right first. The ACL editor does this, and it doesn’t give you any control over the ‘Synchronize’ right: if you create a new ACE it will determine whether or not the right is added, and it never shows it to you. The creators of the file/folder access control .NET classes didn’t get that luxury. Each ACE has a numeric access mask, and that access mask needs to be translated with a flags enumeration. If the ‘Synchronize’ bit is set, then the flags enumeration string is going to show it, and vice versa. So, they did the next best thing: they pulled ‘Synchronize’ from the combined ‘Read’ and ‘Write’ rights in the [FileSystemRights] enumeration, and made sure that creating a new allow ACE or audit rule automatically adds the ‘Synchronize’ right, and creating a new deny ACE removes it. If an application wants to hide the ‘Synchronize’ right from the end user, that’s fine, but the underlying .NET object will show it if it’s present.

I hope that makes sense and clears that up. If not, please leave a comment where something needs to be explained a little better, and I’ll try to expand on it some more.

There’s a new version of my PowerShellAccessControl module available in the Script Center Repository. It’s got a lot of new stuff in it, so go check it out. I’m going to just show you some random commands to run (and I’ll post a screenshot or two).

The update brings the ability to audit/modify SACLs (object auditing). It also simplifies getting security descriptors for several objects, including services, printers, WMI namespaces, and more (even file/folder and registry entries). PowerShell v2 is also supported (let me know if you find anything that doesn’t work when using v2). It’s still lacking the functions I’ve been working on to actually save the security descriptors back out. Don’t worry, though, because those are coming. I’m also working on other ways for the module to get its hands on more security descriptors.

For now, though, I think it does a pretty good job of letting you audit your ACLs for almost anything you’d want to (and if it doesn’t directly support the object, you can still use New-AdaptedSecurityDescriptor if you know the SDDL or binary form of the SD). You can also use New-AccessControlEntry to create file, folder, and registry ACEs to use with Get-Acl and Set-Acl. That by itself saves several lines of code.

Anyway, go download the module, then run through some of these demo scripts line by line:

Working with services:

# Must be run as admin b/c of GetSecurityDescriptor WMI method

# Get BITs service SD
$SD = Get-Service bits | Get-SecurityDescriptor

# Check out the default formatting
$SD

# And as a list:
$SD | fl

# Show ACEs (access and audit) for services that start with the letter 'B'
Get-Service b* | Get-AccessControlEntry

# Show auditing ACEs for services that start with the letter 'B'
Get-Service b* | Get-AccessControlEntry -AclType Audit

# Get BITs service SD (again)
$SD = Get-Service bits | Get-SecurityDescriptor

# Give users the ability to Start and Stop it:
$SD.AddAccessRule((New-AccessControlEntry -ServiceAccessRights Start, Stop -Principal Users))
# Audit that
$SD.AddAuditRule((New-AccessControlEntry -ServiceAccessRights Start, Stop -Principal Users -AuditSuccess))

# Look to make sure those entries are there:
$SD | fl
#or
$SD.Access
$SD.Audit

# Since there's no Set-SecurityDescriptor yet, do this if you want to save
# SD (you have to remove -WhatIf to make it permanent)
$Win32SD = $SD | ConvertTo-Win32SecurityDescriptor -ValueOnly  # Use -LegacyWmiObject if you're going to use WMI cmdlets
Get-CimInstance Win32_Service -Filter "Name='bits'" | Invoke-CimMethod -MethodName SetSecurityDescriptor -WhatIf -Arguments @{
    Descriptor = $Win32SD
}

Working with other objects:

# Printers
Get-WmiObject Win32_Printer | Get-SecurityDescriptor

# Printer access ACE:
$ACE = New-AccessControlEntry -PrinterRights ManageDocuments -Principal Users

# Logical share:
Get-CimInstance Win32_LogicalShareSecuritySetting | Get-SecurityDescriptor

# WSMan:
dir wsman: -Recurse | ? { $_.Name -eq "SDDL" } | Get-SecurityDescriptor

# Folder:
get-item c:\windows | Get-SecurityDescriptor
dir c:\windows -Directory | Get-AccessControlEntry -AceNotInherited
dir c:\windows -Directory | Get-AccessControlEntry -IdentityReference Administrators

# WMI namespace:
$SD = Get-CimInstance __SystemSecurity -Namespace root/cimv2 | Get-SecurityDescriptor

# Add an access ACE that also applies to all child namespaces:
$SD.AddAccessRule((New-AccessControlEntry -WmiNamespaceRights RemoteEnable -Principal Users -AppliesTo Object, ChildContainers))

# View the modified ACL:
$SD.Access
 
# Get new SDDL:
$SD.Sddl
 
# Get new binary form:
$SD.GetSecurityDescriptorBinaryForm()

# Remember, WMI namespace SD hasn't been modified for real, just in in-memory instance of SD

Audit all WMI namespace rights:

function Get-ChildNamespace {

    param(
        [string] $Namespace = "root",
        [int] $Level = 1
    )

    # Decrement level (if argument wasn't supplied, you'll only get
    # the direct chidren)
    $Level--

    Get-WmiObject __Namespace -Namespace $Namespace | select -exp name | ForEach-Object {

        [PsCustomObject] @{
            FullName = "$Namespace\$_"
            Name = $_
            Namespace = $Namespace
        }

        # Negative numbers mean recurse forever
        if ($Level) {
            & $MyInvocation.MyCommand -Namespace "$Namespace\$_" -RecurseLevel $Level
        }
    }
}

# Store SDs for all namesapces in $WmiNsSD (gwmi)
$WmiSDs = Get-ChildNamespace -Level -1 | 
    select -exp fullname | 
    % {"root"}{ $_ } | 
    sort | 
    % { Get-WmiObject -EnableAllPrivileges __SystemSecurity -Namespace $_ } | 
    Get-SecurityDescriptor

# Just show with default formatting:
$WmiSDs

# Or show with ACEs expanded as their own objects
$WmiSDs | Get-AccessControlEntry


# Only show ACEs that aren't inherited:
$WmiSDs | Get-AccessControlEntry -AceNotInherited

And here is a screenshot showing the default formatting after calling ‘Get-Service b* | Get-AccessControlEntry’:

Get-Service b* | Get-AccessControlEntry

Get-Service b* | Get-AccessControlEntry

And one after calling ‘Get-Service bits | Get-SecurityDescriptor’

Get-Service | Get-SecurityDescriptor

Get-Service | Get-SecurityDescriptor

There are a lot of examples in the comment based help for the functions, too. If you have any issues/suggestions, please let me know.

In my previous post, I briefly mentioned that the VersionInfo.FileVersion property of a file object might not be accurate. I didn’t really give any examples to back that up, though. I’d like to show you how you can see this issue for yourself. First, run this so you can follow along in the examples:

$Culture = (Get-Culture).Name
$Files = dir C:\Windows\system32 -File | select Name, @{
        N="FileVersionString"; E={
            $String = $_.VersionInfo.FileVersion
            if ($String -match "(\d+\s*(\.|,)\s*){1,3}\d+") {
                $String = $Matches[0] -replace ",", "."
                $String = $String -replace "\s*", ""
                $String = ([version] $String).ToString()
            }
            $String
        }}, @{
        N="FileVersion";E={
            "{0}.{1}.{2}.{3}" -f $_.VersionInfo.FileMajorPart, 
                $_.VersionInfo.FileMinorPart, 
                $_.VersionInfo.FileBuildPart, 
                $_.VersionInfo.FilePrivatePart}
        }, @{
        N="MUIExists";E={
            Test-Path ("{0}\{1}\{2}.mui" -f $_.PSParentPath, $Culture, $_.Name)
        }}

This is a simple set of commands that gets all of the files from the system32 directory. For each file, it tries to get the file version information. The property named ‘FileVersionString’ takes the VersionInfo.FileVersion and does the following to it:
1. It removes any text as long as the string starts with something that resembles a version (spaces are allowed between the sections, and a comma or a decimal point can be used to separate the parts)
2. It replaces any commas with decimal points
3. It removes any spaces that might be left
4. It temporarily casts it to a [version] type, then back to a string. The idea here is to get rid of extra leading zeros, e.g., 9.00.7600.45000 becomes 9.0.7600.45000

The ‘FileVersion’ property is a string computed the way that I think is the best way to get a file version: combining the FileMajorPart, FileMinorPart, FileBuildPart, and FilePrivatePart numbers. The ‘MUIExists’ property is a Boolean property that tells you whether or not the file in question has a MUI resource file associated with it. The logic for this is very simple, and there may be some errors. Notice that the first line is getting the culture of the system (‘en-US’ on my computer).

Now lets look at the results:


# Show how many files were in folder:
PS> $Files.Count
3345

# Show how many files in folder have a VersionInfo.FileVersion string:
PS> ($Files | where { $_.FileVersionString }).Count
2944

# Get the files that have version strings that don't match the version numbers:
PS> $FilesWithNonMatchingVersions = $Files | 
    where { $_.FileVersionString -and $_.FileVersionString -ne $_.FileVersion }

# Show how many files have version information mismatches:
PS> $FilesWithNonMatchingVersions.Count
176

# Show how many files that have conflicting version information that have MUI 
# resources, and how many don't:
PS> $FilesWithNonMatchingVersions | group MUIExists -NoElement

Count Name                     
----- ----                     
  162 True                     
   14 False                    

There are 3345 files in the system32 directory on the machine I ran that on. Out of those, 2944 have FileVersion strings. Out of those, 176 have a mismatch between the VersionInfo.FileVersion property and the file version that was computed by combining the four version parts.

Out of those 176, 162 have a MUI resource file, and 14 don’t. The 14 that don’t are files where the version strings simply don’t match. The version that Explorer would show for those files is the version computed using the four version parts.

176 files in the system32 directory are showing the VersionInfo.FileVersion property from a different file than you think they are (it’s actually coming from the file’s MUI resource). That’s about 5.5% of the files that have a version in the system32 directory (on a Windows 7 system that I ran this on, it was 478 out of 2480, or just over 19%).

If you run these commands, you’ll get an example of a single random file (NOTE: this will copy a file into your temp directory):

$RandomFileName = $FilesWithNonMatchingVersions | ? { $_.MUIExists } | 
    Get-Random | select -exp Name
$SourceFile = "c:\windows\system32\$RandomFileName"
$MuiFile = "c:\windows\system32\$Culture\$RandomFileName.mui"
$DestFile = "$env:temp\$RandomFileName"
Copy-Item $SourceFile $DestFile
Get-Item $SourceFile, $MuiFile, $DestFile | select -exp VersionInfo | 
    fl FileName, FileVersion, FileM*Part, FileBuildPart, FilePrivatePart

And here are the results on the machine I used:


FileName        : C:\windows\system32\WMPhoto.dll
FileVersion     : 6.3.9600.16384 (winblue_rtm.130821-1623)
FileMajorPart   : 6
FileMinorPart   : 3
FileBuildPart   : 9600
FilePrivatePart : 16474

FileName        : C:\windows\system32\en-US\WMPhoto.dll.mui
FileVersion     : 6.3.9600.16384 (winblue_rtm.130821-1623)
FileMajorPart   : 6
FileMinorPart   : 3
FileBuildPart   : 9600
FilePrivatePart : 16384

FileName        : C:\Users\me\AppData\Local\Temp\WMPhoto.dll
FileVersion     : 6.3.9600.16474 (winblue_gdr.131122-1506)
FileMajorPart   : 6
FileMinorPart   : 3
FileBuildPart   : 9600
FilePrivatePart : 16474

Notice how on the first object the FileVersion doesn’t match the version if you combine the four numeric parts? Also, did you notice how the third object is showing matching versions? The binary file actually contains the proper information, but the API call that gets the file information is checking to see if there is MUI file, and if there is, it uses the FileVersion from the MUI file instead of the file you asked for. The file information from the first and third objects are from the exact same file, just in different directories. When the file is in the system32 directory, it has a MUI file associated with it. When it’s moved to the temp folder, it doesn’t. The four numeric file version parts are actually pulled from the real file whether or not there is a MUI file associated with it.

So, if you want accurate file versions (as accurate as Explorer will show you), I suggest using the four file version parts from the VersionInfo object: FileMajorPart, FileMinorPart, FileBuildPart, FilePrivatePart.

An example of doing that can be found here.

I used to think that getting a file version in PowerShell was a fairly simple process:


PS> (Get-Item c:\windows\system32\mshtml.dll).VersionInfo.FileVersion
11.00.9600.16410 (winblue_gdr.130923-1706)

There are two issues with that method, though:
1. That property can contain text along with the version
2. It turns out that in Windows Vista and higher, that property might not be coming from the file you think it is.

Issue #1 just means that the property is only good if you’re visually checking the version, or if no comparisons with other versions are necessary.

Issue #2 is the big one for me. What appears to be going on is that the FileVersion string is considered a non-fixed part of the version information, and it is pulled from the MUI resource file (if one exists). This file contains the language specific settings, and it looks like it only gets updated when the major or minor file version of associated file changes. If the private file version changes, it doesn’t look like the MUI file is changed.

That means that the FileVersion property has the potential to give you information that isn’t completely up to date. Critical patches from MS usually just update vulnerable code, so there’s no need to change language-specific things in the MUI file.

So, how can you get accurate file version information? Well, you can combine other properties from the VersionInfo object to create an accurate file version:


PS> $VersionInfo = (Get-Item C:\Windows\System32\mshtml.dll).VersionInfo
PS> $FileVersion = ("{0}.{1}.{2}.{3}" -f $VersionInfo.FileMajorPart, 
    $VersionInfo.FileMinorPart, 
    $VersionInfo.FileBuildPart, 
    $VersionInfo.FilePrivatePart)
PS> $FileVersion
11.0.9600.16476

That’s still pretty simple, but it takes a lot more typing. Because of that extra typing, though, your version is now in a known format, i.e., ‘x.x.x.x’. And, if you cast it to a [version] type, you can compare versions as if they were numbers:


PS> [version] $FileVersion -gt "11.0.9600.16410"
True

There is one thing to note here:
Version comparisons between different formats, e.g., 1.2.3 and 1.2.3.0, might not return what you’re expecting. See the remarks section here. Here are some examples of what I’m talking about:


# Gotcha #1
PS> [version] "1.2.3" -eq [version] "1.2.3.0"
False

# Gotcha #2
PS> [version] "1.2.3" -lt [version] "1.2.3.0"
True

# Example of different formats working as you would expect
PS> [version] "1.2.3" -gt [version] "1.2.2.15"
True

# Another example of different formats working
PS> [version] "1.3" -gt [version] "1.2.2.15"
True

As long as you keep your version formats the same, though, the comparison operators will work exactly as you would expect them to. I recommend keeping them in the ‘x.x.x.x’ format.

Something else about the comparisons: In the examples, casting the second string to a [version] isn’t actually necessary. When a comparison operation occurs in PowerShell, the type of the first object is used for the comparisons. That means that the second string in the examples is automatically converted to a [version], even if you don’t tell PowerShell to do it. If you run this command, you’ll get an error:


PS> [version] "1.2.3.4" -gt "potato"
Could not compare "1.2.3.4" to "potato". Error: "Cannot convert value "potato" to type "System.Version". Error: "Version string portion was too short or too long.""
At line:1 char:1
+ [version] "1.2.3.4" -gt "potato"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : ComparisonFailure

I just put a string there, but PowerShell tried to make it a [version] object. It doesn’t hurt to always explicitly use the type (you could even argue that it’s a best practice), but it’s not necessary if you’re trying to cut down on the characters you have to type. Just make sure that the first object is actually a [version] type.

Like I said before, the biggest drawback to combining the four File*Part properties is how much typing it requires. So, I decided to make PowerShell always get the file version for me, and automatically add it to a file object:

# PowerShell must be at least v3 to do this:
Update-TypeData -TypeName System.IO.FileInfo -MemberType ScriptProperty -MemberName PSFileVersion -Value {
    if ($this.VersionInfo.FileVersion) {
        [version] ("{0}.{1}.{2}.{3}" -f $this.VersionInfo.FileMajorPart, 
            $this.VersionInfo.FileMinorPart, 
            $this.VersionInfo.FileBuildPart, 
            $this.VersionInfo.FilePrivatePart)
    }    
}

That method requires version 3.0. If you have 2.0, you should be able to get it to work, though. The difference is that you have to save an XML file, and then use the Update-TypeData cmdlet to read that file. Here’s the XML file:

<Types>
    <Type>
        <Name>System.IO.FileInfo</Name>
        <Members>
            <ScriptProperty>
                <Name>PSFileVersion</Name>
                <GetScriptBlock>
                    if ($this.VersionInfo.FileVersion) {
                        [version] ("{0}.{1}.{2}.{3}" -f $this.VersionInfo.FileMajorPart, 
                            $this.VersionInfo.FileMinorPart, 
                            $this.VersionInfo.FileBuildPart, 
                            $this.VersionInfo.FilePrivatePart)
                    }    
                </GetScriptBlock>
            </ScriptProperty>
        </Members>
    </Type>
</Types>

And here’s the command:

# Replace $PathToXmlFile with proper path:
Update-TypeData -AppendPath $PathToXmlFile

This works on my machine when forcing PowerShell to run version 2, but I haven’t tested it on a machine that truly has PowerShell v2 installed.

Now, when you use Get-Item, Get-ChildItem, or anything that returns a [System.IO.FileInfo] object, the object will have a PSFileVersion property. Here are some examples of this functionality:


# Use Get-ChildItem to display file names and versions:
PS> dir C:\Windows\system32 -Filter msh*.dll | ? psfileversion | select name, psfileversion

Name          PSFileVersion  
----          -------------  
mshtml.dll    11.0.9600.16476
MshtmlDac.dll 11.0.9600.16384
mshtmled.dll  11.0.9600.16384
mshtmler.dll  11.0.9600.16384


# Same as before, but this shows the accurate System.Version version and the inaccurate VersionInfo.FileVersion:
PS> dir C:\Windows\system32 -Filter msh*.dll | ? psfileversion | select name, psfileversion, @{N="FileVersionString";E={$_.VersionInfo.FileVersion}}

Name          PSFileVersion   FileVersionString                         
----          -------------   -----------------                         
mshtml.dll    11.0.9600.16476 11.00.9600.16410 (winblue_gdr.130923-1706)
MshtmlDac.dll 11.0.9600.16384 11.00.9600.16384 (winblue_rtm.130821-1623)
mshtmled.dll  11.0.9600.16384 11.00.9600.16384 (winblue_rtm.130821-1623)
mshtmler.dll  11.0.9600.16384 11.00.9600.16384 (winblue_rtm.130821-1623)


# Comparisons:
PS> (Get-Item C:\Windows\System32\mshtml.dll).PSFileVersion -gt "11.0.9600.16410"
True
PS> (Get-Item C:\Windows\System32\mshtml.dll).PSFileVersion -lt "11.0.9600.16410"
False


# List all of the files with versions under system32:
dir C:\Windows\System32 | ? psfileversion | select name, psfileversion | Out-GridView

You can find this on the Script Repository here.