There’s Sum-thing Happening Here

calculatorI am one of those IT Pros who keeps close tabs on system resources. I like to know what is being used and by what. As you might imagine, a cmdlet like Get-Process, is pretty useful to me. One of the things I’m always checking is how much memory Google Chrome is taking. I don’t mean to pick on Chrome as I derive great benefit from it. But because I keep it open for days at a time I think system resources get a little carried away. So every once in a while I like to see how many Chrome processes are running and how much they are using.

Now, what I really want is a total. I can get that for a single property easy enough using Measure-Object.

But ideally I’d like to get totals for all of the properties I see with Get-Process. So I wrote an advanced PowerShell function called Get-ProcessTotal.

The main part of the function gets all instances of a process and then creates a custom object with a sum total for several properties.

The output also includes the computer name, in case you are querying a remote computer, the total count and also the original process objects in case you need to do something else with them.

That could be sufficient, but all the values would be in bytes and the default display would be a list. Instead I wanted default output like I get with Get-Process. So I took advantage of PowerShell’s extensible type system and created my own format data xml file. Actually what I did was to find the section in the DotNetTypes.format.ps1xml file for process objects, copied it and tweaked it.

My original idea was to have a separate XML file. If I was building a module, that would be the best choice. But this was a single, stand-alone function so I added some logic to create the XML file on first use and add it to my PowerShell session. The XML content is stored as a Here string in the function.

In short, the function first tests to see if there is format data for an object of type My.ProcessTotal. If not, in then tests for the XML file which I’m storing in the WindowsPowerShell folder. If it doesn’t exist, the file is created. In any event once the file exists it is then loaded into PowerShell using Update-FormatData.

How does PowerShell know what type to use? I told it. When I defined my custom object I also gave it a new type name.

Once the function is loaded into my session I can now get a result like this:

The output is just like Get-Process except the values are sums. I also added an option to get an average instead just in case. If you pipe to Get-Member you’ll see the new type definition, as well as the other non-default properties.

There are some other examples in the comment based help. The script also creates an alias so if you don’t want it be sure to comment out the line at the end.

I hope some of you will kick it around and let me know what you think. Or at the very least I hope you picked up a new technique or idea.

Get ACL Information with PowerShell

I got a question in the “Ask Don and Jeff” forum on PowerShell.com that intrigued me. The issue was working with the results of the Get-ACL cmdlet. The resulting object includes a property called Access which is a collection of access rule objects. Assuming you are using this with the file system, these are System.Security.AccessControl.FileSystemAccessRule objects that look like this:

FileSystemRights : ReadAndExecute, Synchronize
AccessControlType : Allow
IdentityReference : BUILTIN\Users
IsInherited : False
InheritanceFlags : ContainerInherit, ObjectInherit
PropagationFlags : None

If I’m understanding the original problem, the poster wanted to identify folders that had a single non-system entry. That is, a folder where someone added a single entry. It doesn’t matter who. So this got me to thinking about a tool that would look at a folder ACL and report on how many access rules were found and then break that count down by system and user. I figured that any rule where the identity reference name included “Builtin”, “NT Authority”, “Everyone” or “Creator Owner” would be considered a system rule. Anything else would be considered a user rule.

In the console, I could run a command like this:


PS S:\> get-acl c:\work | select -expand access | where {$_.identityreference -notmatch "BUILTIN|NT AUTHORITY|EVERYONE|CREATOR OWNER"}

FileSystemRights : FullControl
AccessControlType : Allow
IdentityReference : SERENITY\Jeff
IsInherited : False
InheritanceFlags : None
PropagationFlags : None

Because I ran this interactively, I know what folder has this potential issue. So the next step is to turn this into a tool that will write ACL summary information to the pipeline. Here’s my function, and then I’ll explain a few things. The download version includes comment based help.


Function Get-ACLInfo {

[cmdletbinding()]

Param(
[Parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[ValidateScript({Test-Path $_})]
[Alias('PSPath','Fullname')]
[string[]]$Path="."
)

Begin {
Write-Verbose "Starting $($myinvocation.mycommand)"

#create a format file on the fly
[email protected]"




JDH.ACLInfo

JDH.ACLInfo




50



8


9


7






Path


Owner


TotalACL


SystemACL


UserACL








"@
#create a temp file
$tmpfile=[system.io.path]::GetTempFileName()
#add the necessary file extension
$tmpfile+=".ps1xml"

#pipe the xml text to the temp file
Write-Verbose "Creating $tmpfile"
$xml | Out-File -FilePath $tmpfile

<# update format data. I'm setting error action to SilentlyContinue because everytime you run the function it creates a new temp file but Update-FormatData tries to reload all the format files it knows about in the current session, which includes previous versions of this file which have already been deleted. #>
Write-Verbose "Updating format data"
Update-FormatData -AppendPath $tmpfile -ErrorAction SilentlyContinue

} #Begin

Process {
Foreach ($folder in $path) {
Write-Verbose "Getting ACL for $folder"
#get the folder ACL
$acl=Get-ACL -Path $path

#a regex to get a file path
[regex]$regex="\w:\\\S+"

#get full path from ACL object
$folderpath=$regex.match($acl.path).Value

#get Access rules
$access=$acl.Access

#get builtin and system ACLS
$sysACL=$access | where {$_.identityreference -match "BUILTIN|NT AUTHORITY|EVERYONE|CREATOR OWNER"}

#get non builtin and system ACLS
$nonSysACL=$access | where {$_.identityreference -notmatch "BUILTIN|NT AUTHORITY|EVERYONE|CREATOR OWNER"}

#grab some properties and add them to a hash table.
[email protected]{
Path=$folderpath
Owner=$acl.Owner
TotalACL=$access.count
SystemACL=($sysACL | measure-object).Count
UserACL=($nonSysACL | measure-object).Count
AccessRules=$access
}

#write a new object to the pipeline
$obj=New-object -TypeName PSObject -Property $hash
#add a type name for the format file
$obj.PSObject.TypeNames.Insert(0,'JDH.ACLInfo')
$obj

} #foreach

} #Process

End {
#delete the temp file if it still exists
if (Test-Path $tmpfile) {
Write-Verbose "Deleting $tmpfile"
Remove-Item -Path $tmpFile
}
Write-Verbose "Ending $($myinvocation.mycommand)"
} #end

} #end function

When I run the function here’s what the result looks like:


PS S:\> get-aclinfo | select *

SystemACL : 7
Owner : SERENITY\Jeff
UserACL : 0
AccessRules : {System.Security.AccessControl.FileSystemAccessRule, System.Security.AccessControl.Fi
leSystemAccessRule, System.Security.AccessControl.FileSystemAccessRule, System.Securi
ty.AccessControl.FileSystemAccessRule...}
Path : C:\scripts\
TotalACL : 7

The function takes a path parameter and passes that to Get-ACL.


$acl=Get-ACL -Path $path

I will be using some of the properties of this object in the custom object I’ll eventually write to the pipeline. One thing I want is the full path. Unfortunately, I need to parse that out of the path property. I decided to use a regular expression.


#a regex to get a file path
[regex]$regex="\w:\\\S+"

#get full path from ACL object
$folderpath=$regex.match($acl.path).Value

Next, I need to count the access rule entries and determine which are system and which are user.


#get Access rules
$access=$acl.Access

#get builtin and system ACLS
$sysACL=$access | where {$_.identityreference -match "BUILTIN|NT AUTHORITY|EVERYONE|CREATOR OWNER"}

#get non builtin and system ACLS
$nonSysACL=$access | where {$_.identityreference -notmatch "BUILTIN|NT AUTHORITY|EVERYONE|CREATOR OWNER"}

I like creating new objects with hash tables, which will get even easier in PowerShell 3.0.


#grab some properties and add them to a hash table.
[email protected]{
Path=$folderpath
Owner=$acl.Owner
TotalACL=$access.count
SystemACL=($sysACL | measure-object).Count
UserACL=($nonSysACL | measure-object).Count
AccessRules=$access
}

#write a new object to the pipeline
$obj=New-object -TypeName PSObject -Property $hash

The object shows counts of the different ACL “types” and also includes a property with the full access rules should you want to look at them in more detail. But now for the “scoop of ice cream on the side” part of this function.

In PowerShell 2.0, hash tables are unordered meaning there’s no guarantee what order your properties will be display. Plus, you may want to have more control over how PowerShell formats the resulting objects. The way we handle this is by creating a format file and loading it into the shell with Update-FormatData. I’m not going to go into the mechanics of custom format files here. I know the topic is covered in the Windows PowerShell 2.0: TFM book, and the Month of Lunches books among others.

Now, if I had created a module out of this function I could have packaged it with a separate format ps1xml file. But I had a thought of trying to use a format file “on the fly”. In the Begin script block, I have the XML that would normally go into the format.ps1xml file. I create a temp file and add the xml to it.


#create a temp file
$tmpfile=[system.io.path]::GetTempFileName()
#add the necessary file extension
$tmpfile+=".ps1xml"

#pipe the xml text to the temp file
Write-Verbose "Creating $tmpfile"
$xml | Out-File -FilePath $tmpfile

The format file apparently needs to have .ps1xml file extension so I have to update the file name. Once I have this file, I call Update-FormatData to load it.


Update-FormatData -AppendPath $tmpfile -ErrorAction SilentlyContinue

Normally, I’m not a big fan of turning off errors, but in this case I need to. When you run Update-FormatData, PowerShell reloads all the format files that it knows in this session. The first time I run the function, the temp file is created and loaded. But in the End script block, I delete it.


if (Test-Path $tmpfile) {
Write-Verbose "Deleting $tmpfile"
Remove-Item -Path $tmpFile
}

The next time I run the function, I recreate the format file. But Update-FormatData also looks for the previous temp files which have since been deleted, which normally raises an exception. I’m not saying this approach of “on the fly formatting” is perfect but so far it works for me in these situations. The formatting file takes my custom object and creates a table with all properties except the access rules.

My format file allows for plenty of space for the file path. But you can always tighten things up.

If I want to work with the underlying access rules, I still can.

Download Get-ACLInfo and let me know what you think.