Quick Hits: Sending Data to Null

There are times when you do not need to display data in the PowerShell console, but it still wants to show up anyways. A good case is when you are trying to add items to a collection, such as an arraylist.

 
$list = New-Object System.Collections.ArrayList
$list.Add('Something')
$list.Add('Else')

SNAGHTMLff634c9

If you are writing some code that sends data to another command or to a file, you do not want this to pollute the pipeline. The approach is to send this data to a NULL destination and in PowerShell, there are a few different approaches that I commonly see.

  • Out-Null
    • Typically used via the pipeline to send data to a NULL destination; but it does have the –InputObject parameter to use as well.
  • $Null = <code>
    • $Null is an automatic variable that when used to save output, actually gets rid of the output
  • ‘Stuff’ > $Null
    • Same as doing $Null=
  • [void]
    • Common .Net type used in C# that specifies a return value type for a method that does not return a value.

Here is a quick demonstration of each of these in action.

 
$list = New-Object System.Collections.ArrayList
$list.Add('Something') | Out-Null
Out-Null -InputObject $list.Add('Else')
$Null = $list.Add('New')
[void]$list.Add('Here')
$list.Add('Today') > $Null

image

Here we see that all of the return values that normally would be showing up has in fact been sent to NULL and is no longer showing up on the console (and in turn will not pollute the pipeline).

So with these options, which is actually the fastest approach to all of this? Well, lets find out!

 
$Time = (1..1E4 | ForEach {
(Measure-Command {Get-Date | out-null}).TotalMilliseconds
} | Measure-Object -Average).Average
[pscustomobject]@{
Type = '| Out-Null'
TimeMs = [math]::Round($Time,2)
}
$Time = (1..1E4 | ForEach {
(Measure-Command {out-null -InputObject Get-Date}).TotalMilliseconds
} | Measure-Object -Average).Average
[pscustomobject]@{
Type = 'Out-Null -InputObject'
TimeMs = [math]::Round($Time,2)
}
$Time = (1..1E4 | ForEach {
(Measure-Command {Get-Date > $Null}).TotalMilliseconds
} | Measure-Object -Average).Average
[pscustomobject]@{
Type = '> $Null'
TimeMs = [math]::Round($Time,2)
}
$Time = (1..1E4 | ForEach {
(Measure-Command {$Null = Get-Date}).TotalMilliseconds
} | Measure-Object -Average).Average
[pscustomobject]@{
Type = '$Null='
TimeMs = [math]::Round($Time,2)
}
$Time = (1..1E4 | ForEach {
(Measure-Command {[void](Get-Date)}).TotalMilliseconds
} | Measure-Object -Average).Average
[pscustomobject]@{
Type = '[void]'
TimeMs = [math]::Round($Time,2)
}

image

The winner by a nose is [void]. Using $Null (both approaches) is pretty close as well and in fact these two could be interchanged without much notice. The use of Out-Null with the pipeline is the slowest and its InputObject parameter is 3rd.

So with that, we have a few ways to redirect unneeded output without sending data down the pipeline that is not needed.

Posted in powershell | Tagged , , | 4 Comments

More New Stuff in PowerShell V5: A NEW Way to Construct Things

Usually we can build out a new instance of a .Net object we have to use New-Object and pass the required parameters to accomplish this. Or at least that is the most common approach to the subject. We can also use [activator] type as well to build out the same instance with a little bit better performance. Both of these are compatible since at least V2 (I don’t deal with V1, so someone who still has this running can verify in the comments). And even sometimes you can use a hash table with a .Net type to build out the instance as well (works well with WPF and WinForms).

Refresher

We need to find a .Net type that will make for a good demo. For this I will go with Net.Sockets.TCPClient as it allows some extra arguments to supply if needed.

I now need to see what kind of parameters are available with this type. I can find this easily by using my Get-Constructor function that I wrote back here.

 
Get-Constructor Net.Sockets.TcpClient

image

I can see 4 possible constructor possibilities here. For the sake of examples, I will use the last one requiring a hostname and a port to check against.

On a side note, if you wanted to see how many .Net types have constructors, you can give this a run:

[appdomain]::CurrentDomain.GetAssemblies() | ForEach {$_.GetExportedTypes() | Where {
    $_.GetConstructors()
}} | Select -Expand FullName | Sort | Out-Host -Paging 

image

New-Object Approach

This is the most common approach that 99% of everyone in PowerShell uses when they create a .Net instance (or COM object, but I am not focused on COM today). Using New-Object is simple to use, just run the cmdlet, pass the .Net type and any arguments, if required.

 New-Object Net.Sockets.TcpClient –ArgumentList $Env:Computername, 135

 

image

[activator] Approach

This is a lesser known (OK, probably more like almost unknown) approach that you can use to create both a  .Net instance or COM object. This is done by using the [Activator] type accelerator and using the CreateInstance() method.

There are a lot of possible method parameters here, but the one I am focused on is the use of the Type and System.Object[] parameters to add the required values in the creation of the object.

SNAGHTML1f05393f

[Activator]::CreateInstance([Net.Sockets.TcpClient], @($Env:Computername,135))

image

Same as New-Object, I connect to the system and validate that port 135 is open.

What’s NEW

Now for the new approach to performing the same thing as what we have done with New-Object and [Activator]::CreateInstance(). In PowerShell V5 (currently in September Preview at the time of this post), all .Net types have an added New() method which allows you to construct .Net instances more easily. More than just that, you also now have a way to see the possible constructor parameter options as well.

image

Just like with my function, we can see the actual parameter name as well as the type that the parameter is expecting to be passed into it. So, in this case, I will use the same approach as before and supply the required parameters to do a port check.

[Net.Sockets.TcpClient]::New($Env:Computername, 135)

 

image

Perfect! As you can see, I get the exact same output as the previous approaches. So can this be used for anything that has an available constructor? Well, for the most part, you can. I have found at least one instance where there appears to be some sort of issue. For some reason, DateTime throws a casting error when using PSCreateInstanceBinder. I’m not sure why this is happening, but asked the question on the MVP mailing list and will provide an answer when I hear back from someone on whether this is a bug or something else. (08 SEPT 2014): This was confirmed as a bug by the PowerShell team.

image

Performance Testing

The last thing I wanted to see with this is which one is the fastest to build out the .Net instance. I wanted to use DateTime as the type, but since that one has some flawed issues with the New() method, I am going with Net.Sockets.TcpClient and supplying no parameters to make sure the test is as clean as possible in just creating an object that is waiting for some action. So with that I ran a quick check…

{$Time = (1..1E5 | ForEach {
 (Measure-Command {
 New-Object Net.Sockets.TcpClient
 }).TotalMilliseconds
} | Measure-Object -Average).Average
[pscustomobject]@{
 Name = “New-Object”
 Type = 'Net.Sockets.TCPClient'
 Cycles = 100000
 MillisecondsSeconds = [math]::Round($Time,4)
}

$Time = (1..1E5 | ForEach {
 (Measure-Command {
 [Activator]::CreateInstance([Net.Sockets.TcpClient])
 }).TotalMilliseconds
} | Measure-Object -Average).Average
[pscustomobject]@{
 Name = “[Activator]::CreateInstance()”
 Type = 'Net.Sockets.TCPClient'
 Cycles = 100000
 MillisecondsSeconds = [math]::Round($Time,4)
} 

$Time = (1..1E5 | ForEach {
 (Measure-Command {
 [Net.Sockets.TcpClient]::New()
 }).TotalMilliseconds
} | Measure-Object -Average).Average
[pscustomobject]@{
 Name = “[&lt;Type&gt;]::New()”
 Type = 'Net.Sockets.TCPClient'
 Cycles = 100000
 MillisecondsSeconds = [math]::Round($Time,4)
}}.InvokeReturnAsIs() | Format-Table -AutoSize

… and got the following results:

image

The Activator approach has an incredibly small performance difference than with using the New() method. In fact, those two could go either way (and in fact they did on multiple tests). But the bigger thing to notice is that New-Object is slower than both of them. In the big scheme of things, it really isn’t that big of a performance impact, but if you are looking to squeeze as much performance as you can from your scripts, then either New() or [activator]::CreateInstance() would be the way to go. Note that [activator] is good with PowerShell V2 for backwards compatibility on your scripts.

Posted in Uncategorized | Tagged , , , | 2 Comments

More New Stuff in PowerShell V5: Extra PowerShell Auditing

With the latest Preview release of PowerShell V5 July (X86, X64), we get some extra capabilities for auditing PowerShell script tracing. Since PowerShell V3, we have had the capability of Module Logging in PowerShell, meaning that we can track the commands that are being run for specified PowerShell modules in the event logs. As a refresher, I will show you both of the two ways that this can be enabled and what the expected event logs will look like.

Module Loading

This is the manual approach, but allows you to track the execution of the commands in the PowerShell console. This has to be done for each module that you want this enabled for and is only valid for the current session. You can use either Import-Module or Get-Module and list the module names in parenthesis and specify the LogPipelineExecutionDetails = $True. We can quickly look at the Microsoft.PowerShell modules to see if any of those have logging enabled.

Get-Module Microsoft.* | 
Select Name, LogPipelineExecutionDetails

 

image

None of these are enabled…yet. Time to make that change.

Get-Module Microsoft.* | ForEach {
    $_.LogPipelineExecutionDetails = $True
}

image

All right! Now we are set to run some commands and see what happens in the event logs.

Note: Here is how to do it using Import-Module:

(Import-Module ActiveDirectory).LogPipelineExecutionDetails = $True

After some various commands such as Get-ChildItem, we can look at the event logs to see that all of this has been logged

image

Ok, this is a little hard to see unless you click on the image, but what we are looking at is the Windows PowerShell event log and the Pipeline Execution Details (ID 800) which shows not only the commands that were run, but also the parameters and who actually ran the command!

image

Ok, this is PowerShell so obviously I need to do this the ‘proper way’.

Get-WinEvent -FilterHashtable @{LogName='Windows PowerShell';Id ='800'} -MaxEvents 1 | 
Select -Expand Message

 

image

Errors are also logged as well if something happens.

image

Pretty cool, right? I mentioned that there were two ways to accomplish this and that other way is via Group Policy.

Group Policy

You can either set this via Computer and/or User policy under Administrative Templates/Windows Components/Windows PowerShell under Turn On Module Logging.

Once you enable the policy setting, you can then specify the modules that you wish to track the execution of commands.

image

SNAGHTML14756c46

SNAGHTML147767b8

Once that is done and the GPO has been applied to the systems, you will start seeing the new event logs pop in.

One thing to keep in mind is that the logs may start churning rather hard, so you need to make sure that you are archiving the logs to ensure that you do not miss anything important or have some sort of event forwarding capability to send these logs off to a syslog server.

Latest Script Logging Enhancements

So I have talked about all of this stuff that has been around for a while for a reason. The PowerShell team has improved on this logging by now allowing more detailed logging of scriptblocks to the PowerShell ETW log.

There are a couple of Environmental Variables that need to be configured in order to achieve the enhanced logging. Those are:

  • PSLogScriptBlockExecution
    • When this environment variable exists (its value doesn’t matter), Windows PowerShell logs the text of scripts and script blocks the first time they are seen.
  • PSLogScriptBlockExecutionVerbose
    • When this environment variable exists (its value doesn’t matter), Windows PowerShell logs execution details (begin and end) when logged script blocks are invoked.

Group policy would be the idea approach for this. This is available for both User and Computer policies, so it is up to you which one (or both) that you want to use.

SNAGHTML1486ac4a

These events are not  located in the Windows PowerShell event log, but are located in the Microsoft/Windows/PowerShell/Operational ETW log. The following event IDs correspond to the type of logging you enabled:

  • PSLogScriptBlockExecution
    • Event ID 4104
  • PSLogScriptBlockExecutionVerbose
    • Event ID 4105 (Starting scriptblock) and 4106 (Completing scriptblock)

Note: This does log for things ran through the ISE just like it will for the console.

Now I will run a similar command as I did earlier so we can see what else is being logged.

Get-ChildItem -Path C:\DSC -Filter *.mof -Recurse

Now let’s check out those logs.

image

With that command, we kicked off 36 logs. If you notice, most of the logs are a result of setting PSLogScriptBlockExecutionVerbosewith events 4105 and 4106 tracking each scriptblock creation.

image

image

Event 4014 lists the command being run.

image

Nothing that impressive just yet, but notice that we do not have to specify a specific module to track. Now let’s compile a function on the fly and let it run.

Function Do-Something { 
    Param ($Stuff) 
    Write-Output $Stuff
}

We can see under EventID 4104 that it is compiling the scriptblock.

image

And when we run the fuction:

Do-Something -Stuff 123

image

 

Now I am going to use the example from the July ReadMe file to show an obfuscated command and how it is tracked both in the Windows PowerShell log and the ETW logs.

First the PowerShell log:

Build the function out:

function SuperDecrypt {
    param($script)

    $bytes = [Convert]::FromBase64String($script)
                
    ## XOR “encryption”
    $xorKey = 0x42
    for($counter = 0; $counter -lt $bytes.Length; $counter++)
    {
        $bytes[$counter] = $bytes[$counter] -bxor $xorKey
    }

    [System.Text.Encoding]::Unicode.GetString($bytes)
}

Check the PowerShell log:

image

Well, that’s not off to a good start. Now let’s run the function.

$decrypted = SuperDecrypt "FUIwQitCNkInQm9CCkItQjFCNkJiQmVCEkI1QixCJkJlQg=="
Invoke-Expression $decrypted  

 

image

image

Ok, we got a little bit of info only because I was tracking the modules that use Write-Host and Invoke-Expression. If I didn’t have this enabled, well, this wouldn’t have been easy to catch.

Now let’s try this using the enhanced logging:

Build the function:

image

Here we can see the function being created in the ETW logs. Now let’s run it as well.

image

Oh look, it caught the variable being created as well!

Now for the rest of the command.

image

image

It doesn’t give you the pipeline binding that you would see with the PowerShell event log, but it does give you enough information to know what is happening here.

I wanted to do a little more testing just to see what else was logged. Here is similar code but instead I invoked the code as a scriptblock instead of using Invoke-Expression.

{$decrypted}.Invoke()

image

While this would have never been caught through module logging, it was caught here. Although it didn’t give us the contents of $decrypted, it at least showed that something was being invoked.

Last thing to try out: running an encoded command through PowerShell.exe.

$command = '[console]::WriteLine("Something malicious happening!")'
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)

$EncodedCommand produces this:

WwBjAG8AbgBzAG8AbABlAF0AOgA6AFcAcgBpAHQAZQBMAGkAbgBlACgAIgBTAG8AbQBlAHQAaABpAG4AZwAgAG0AYQBsAGkAYwBpAG8AdQBzACAAaABhAHAAcABlAG4AaQBuAGcAIQAiACkA

 

Now I run PowerShell.exe from the Run menu with this encoded command.

powershell.exe -encodedcommand WwBjAG8AbgBzAG8AbABlAF0AOgA6AFcAcgBpAHQAZQBMAGkAbgBlACgAIgBTAG8AbQBlAHQAaABpAG4AZwAgAG0AYQBsAGkAYwBpAG8AdQBzACAAaABhAHAAcABlAG4AaQBuAGcAIQAiACkA

image

It’s right after EventID 40962 that we see the event that shows the encoded command being created prior to running.

And I do get this in the PowerShell event log only if I have Module Logging enabled through Group Policy.

image

As with the PowerShell event log, this will generate A LOT of events, especially if you enabled PSLogScriptBlockExecutionVerbose as it really pumps out the events. But there you go, a couple more ways to audit PowerShell usage that begin in PowerShell V5 July Preview!

Posted in powershell, V5 | Tagged , , , | 7 Comments

More New Stuff in PowerShell V5: Expand and Compress Archive Cmdlets

With the July release of PowerShell V5 Preview (Download links are 1/3 of the way down on the DSC download page), there are some new things that have been added that are worth talking about.

Here are the direct download links for the July Preview of PowerShell V5:

If you recall, I talked about some of the new things in PowerShell V5 Preview here and here but now I am going to go over one of the new items that have been added to this release:

Archive Cmdlets

Finally we have cmdlets which allow us to create a zip archive to zip and unzip files.

  • Compress-Archive

image

From what we can see with the help file on Compress-Archive, there are 2 parameter sets that are based on Path and LiteralPath parameters. We have an –Update parameter which allows us to add to an existing archive file. We can also set the CompressionLevel (Fastest, NoCompression or Optimal (Default Value)) for the archive.

Let’s give it a quick run to see what happens:

Get-ChildItem | 
Compress-Archive -DestinationPath 'C:\users\boe.prox\Desktop\Archive.zip' -Verbose

image

image

Note: Using just a folder without a full path name causes the script to throw errors

image

It still creates an archive, but it seems to move it to the parent folder above my current path. It probably should just use the current path if none is given.

SNAGHTMLa9e79bf

This feels like a bug to me and should handle the current directory instead of shipping it somewhere else and throwing errors. If you think so to, feel free to vote this up: https://connect.microsoft.com/PowerShell/feedbackdetail/view/954121/v5-july-preview-compress-archive-should-default-to-current-directory-if-fully-qualified-path-given-for-destination-path

If you don’t use the –Update parameter and try to add something to an existing archive, an error will be thrown reminding you to use –Update.

image

Pretty much all there is to that. Now let’s check out the second cmdlet for archives.

  • Expand-Archive

image

Again, we have 2 parameter sets for Path and LiteralPath. We just have to supply the archive file and a destination path for the archive folder. What I am curious to see is if I do not supply a DestinationPath which requires a folder name and if the folder that everything is unzipped to will be the name of the archive.

Expand-Archive .\Archive.zip -DestinationPath $Pwd -Verbose

image

It appears that it will not create a folder based on the archive name and will instead attempt to unzip the file to the existing directory. If the file exists, you will see errors stating to use the –Force parameter, which makes sense.

 

Now lets run this and specify an actual folder name (but one that doesn’t exist).

Expand-Archive .\Archive.zip -DestinationPath 'UnzippedArchive' -Verbose

image

 

Well, it didn’t like that at all. I guess we have to specify an existing folder in order for this to work. At first I wasn’t sure how I liked that, but copy-item and move-item do not auto create directories if they do not exist, so I would expect this cmdlet to do the same.

image

image

Well, that is it for checking out the new cmdlets: Compress-Archive and Expand-Archive! I will look to dive into another new addition to PowerShell V5 July Release in the coming days. Stay tuned!

Posted in powershell | Tagged , , , , , , | 6 Comments

Revisiting Get-FileTimestamp with Reflection

Ever since I wrote my function to locate a file’s MFT (ChangeTime) timestamp, I wanted to take on an extra challenge by taking all of the C# code that included using pinvoke and rewrite it using reflection in PowerShell after seeing fellow PowerShell MVP Matt Graeber’s article on using reflection. While there may not have been a need to keep this particular code resident in memory, it will server as a fun exercise in rewriting all of the C# code and avoid the use of Add-Type to compile the code (which does write to disk).

The plan is to piece through each part of code and show the translation from C# to using reflection to create the Structs, Enums and pinvoke methods in memory by showing the C# code followed by the PowerShell code. If you want to check out the code in its entirety, you can find the majority of it on the link that I referenced at the beginning of this article.

The following code is the initial part of the C# code that defines my environment and new class.  Nothing here will be translated over to PowerShell using reflection.

using System;
using System.Runtime.InteropServices; // for DllImport and friends
using Microsoft.Win32.SafeHandles; // for SafeHandle
using System.Collections.Generic; // for ParseFileAttributes helper function (List<FileAttributes> only
using System.IO; // for test main (FileStream) only
using System.Text; // for test main (Encoding) only
using System.Threading; // for test main (Thread.Sleep) only
using System.Diagnostics; // for test main (Trace.Write[Line]) only
public class Nt
{

First off, I am going to build my dynamic assembly and module which will be used the rest of the time to help define other types that will be required at different points.

#region Module Builder
$Domain = [AppDomain]::CurrentDomain
$DynAssembly = New-Object System.Reflection.AssemblyName('TestAssembly')
# Only run in memory by specifying [System.Reflection.Emit.AssemblyBuilderAccess]::Run
$AssemblyBuilder = $Domain.DefineDynamicAssembly(
    $DynAssembly, 
    [System.Reflection.Emit.AssemblyBuilderAccess]::Run
) 
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('TimeStampModule', $False)
#endregion Module Builder

What I’ve done here is creating a new assembly called TestAssembly that is used later to build a dynamic module that is used to build out our required components (structs, enums, pinvoke, etc…).

I then hook into the current AppDomain and use a method called DefineAssembly() to configure the custom assembly (stored in memory by specifying [System.Reflection.Emit.AssemblyBuilderAccess]::Run). Once that is done, I build my dynamic module using DefineDynamicModule() and specify a module name and and whether the symbol information should be emitted.

Up next is where I define a Struct for IO_STATUS_BLOCK which is used to determine the status of using the win32 function,  NTQueryInformationFile.

struct IO_STATUS_BLOCK
{
    internal uint status;
    internal ulong information;
}

And now for the PowerShell reflection approach:

#region STRUCTs

#region IOStatusBlock
#Define STRUCT
$Attributes = 'AutoLayout, AnsiClass, Class, Public, SequentialLayout, Sealed, BeforeFieldInit'
$TypeBuilder = $ModuleBuilder.DefineType('IOStatusBlock', $Attributes, [System.ValueType], 1, 0x40)
[void]$TypeBuilder.DefineField('status', [UInt64], 'Public')
[void]$TypeBuilder.DefineField('information', [UInt64], 'Public')

#Create STRUCT Type
[void]$TypeBuilder.CreateType()
#endregion IOStatusBlock

 

I use my $ModuleBuilder that I defined earlier to build a new Struct using the DefineType() method in which I specify a name for the Struct as well as specifying some attributes to make it more like a Struct (I found out what these attributes were by looking at my previously compiled C# code and using Get-Member against the Struct). From there I take the Type I have created and use the DefineField() method to build out the fields to match what is required of the IO_STATUS_BLOCK. The Define() method requires that I supply a name, a type and the FieldAttribute of the field. Once completed, I simply call CreateType() to complete the build of the type so it is available to me.

image

The next Struct is for FILE_BASIC_INFORMATION and is a little different to build out due to some unique requirements:

[StructLayout(LayoutKind.Explicit)]
struct FILE_BASIC_INFORMATION
{
    [FieldOffset(0)]
    internal long CreationTime;
    [FieldOffset(8)]
    internal long LastAccessTime;
    [FieldOffset(16)]
    internal long LastWriteTime;
    [FieldOffset(24)]
    internal long ChangeTime;
    [FieldOffset(32)]
    internal ulong FileAttributes;
}

Here I have a StructLayout attribute as well as FieldOffsets which define the physical position of the field in the struct. Have on fear though, we can easily put this one together!

#region FileBasicInformation
#Define STRUCT
$Attributes = 'AutoLayout, AnsiClass, Class, ExplicitLayout, Sealed, BeforeFieldInit,public'
$TypeBuilder = $ModuleBuilder.DefineType('FileBasicInformation', $Attributes, [System.ValueType], 8, 0x40)
$CreateTimeField = $TypeBuilder.DefineField('CreationTime', [UInt64], 'Public')
$CreateTimeField.SetOffset(0)
$LastAccessTimeField = $TypeBuilder.DefineField('LastAccessTime', [UInt64], 'Public')
$LastAccessTimeField.SetOffset(8)
$LastWriteTimeField = $TypeBuilder.DefineField('LastWriteTime', [UInt64], 'Public')
$LastWriteTimeField.SetOffset(16)
$ChangeTimeField = $TypeBuilder.DefineField('ChangeTime', [UInt64], 'Public')
$ChangeTimeField.SetOffset(24)
$FileAttributesField = $TypeBuilder.DefineField('FileAttributes', [UInt64], 'Public')
$FileAttributesField.SetOffset(32)

#Create STRUCT Type
[void]$TypeBuilder.CreateType()
#endregion FileBasicInformation

 

Same as before, I use my ModuleBuilder to define the type with specific attributes (I have added ExplicitLayout to match the [StructLayout(LayoutKind.Explicit)] attribute defined in C#) and then start adding the fields. The thing you might notice is that I save each field from DefineField() to a variable. This is because I must then call the SetOffset() method and supply an integer which will define the physical position of the field. This is how I do my FieldOffset!

image

That does it for the Structs! Time to do an Enum.

I only have one Enum to do for FILE_INFORMATION_CLASS. I didn’t need everything, just the following items that are listed below in the C# code.

enum FILE_INFORMATION_CLASS
{
    FileDirectoryInformation = 1,        // 1
    FileBasicInformation = 4,            // 4
    FileHardLinkInformation = 46        // 46   
}

The refactored code for PowerShell is below:

#region ENUMs
$EnumBuilder = $ModuleBuilder.DefineEnum('FileInformationClass', 'Public', [UInt32])
# Define values of the enum
[void]$EnumBuilder.DefineLiteral('FileDirectoryInformation', [UInt32] 1)
[void]$EnumBuilder.DefineLiteral('FileBasicInformation', [UInt32] 4)
[void]$EnumBuilder.DefineLiteral('FileModeInformation', [UInt32] 16)
[void]$EnumBuilder.DefineLiteral('FileHardLinkInformation', [UInt32] 46)

#Create ENUM Type
[void]$EnumBuilder.CreateType()
#endregion ENUMs

Unlike the previous items, I will be calling DefineEnum() vs. DefineType() and supplying the name of the enum, the type attributes and underlying type for the enum. From there I start adding the enum values using DefineLiteral() and giving values for the name of the value name, the underlying type (must be UInt32 because UInt16 will work on PowerShell V3, but V4+ will fail when using the pinvoke method) and the numeric value of the name. Once that has completed, I call the familiar CreateType() method and away we go!

image

Now to hook into the Win32 API using pinvoke and grabbing the NTQueryInformationFile() function.

[DllImport("ntdll.dll", SetLastError = true)]
    static extern IntPtr NtQueryInformationFile(SafeFileHandle fileHandle, 
    ref IO_STATUS_BLOCK IoStatusBlock, 
    IntPtr pInfoBlock, 
    uint length, 
    FILE_INFORMATION_CLASS fileInformation);

 

The PowerShell code is somewhat larger as we have to build everything out, but it is by no means impossible. I will step through by splitting up some parts of the code to better explain what is happening here.

#region DllImport
$TypeBuilder = $ModuleBuilder.DefineType('ntdll', 'Public, Class')

#region NtQueryInformationFile Method
$PInvokeMethod = $TypeBuilder.DefineMethod(
    'NtQueryInformationFile', #Method Name
    [Reflection.MethodAttributes] 'PrivateScope, Public, Static, HideBySig, PinvokeImpl', #Method Attributes
    [IntPtr], #Method Return Type
    [Type[]] @([Microsoft.Win32.SafeHandles.SafeFileHandle], 
    [IOStatusBlock], [IntPtr] ,[UInt64], [FileInformationClass]) #Method Parameters
)

Once again, I use my ModuleBuilder object to define my type. This time I am specifying a name of the dll that the function resides on (I don’t have to do this, but it makes sense to make it something that relates to what I am using) as well as specifying the type attributes.

From there I can begin to define my NTQueryInformationFile method (the win32 function). To do this I use the DefineMethod() method and supply the name of the function, the method attributes (I found this by reviewing the method attributes on the compiled C# code), the return type that the function provides when used (in this case an IntPtr) and lastly I supply the required parameter types that are required for this to actually work. Fortunately, I can get all of this information from the MSDN page. This is a array of Types that consist of a SafeFileHandle, the IOStatusBlock type, an IntPtr, an UInt64 and lastly the FileInformationClass type.

$DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))
$FieldArray = [Reflection.FieldInfo[]] @(
    [Runtime.InteropServices.DllImportAttribute].GetField('EntryPoint'),
    [Runtime.InteropServices.DllImportAttribute].GetField('SetLastError')
)

$FieldValueArray = [Object[]] @(
    'NtQueryInformationFile', #CASE SENSITIVE!!
    $True
)

$SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder(
    $DllImportConstructor,
    @('ntdll.dll'),
    $FieldArray,
    $FieldValueArray
)

 

This piece is where I build out and define the attributes of the DLLImport attribute. In this case, I only need to worry about setting the EntryPoint (which is the actual Win32 function that this will use – AND IT IS CASE SENSITIVE) and the SetLastError field that I can use to get any error messages if this happens to fail.

$PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)
#endregion NtQueryInformationFile Method

[void]$TypeBuilder.CreateType()
#endregion DllImport

 

I add that custom attribute to my method and then create the type.

image

Now I have a working method that will call a Win32 function! But that is not all! I had some extra stuff coded in C# (that really wasn’t needed, but I did it in C# just to learn more about it) that needs to be moved to PowerShell.

public static bool GetFourFileTimes(string path2file,
        out DateTime creationTime, out DateTime lastAccessTime, 
        out DateTime lastWriteTime, out DateTime changeTime, out string errMsg)
{
    bool brc = false;
    creationTime = default(DateTime);
    lastAccessTime = default(DateTime);
    lastWriteTime = default(DateTime);
    changeTime = default(DateTime);
    errMsg = string.Empty;
    IntPtr p_fbi = IntPtr.Zero;
    try
    {
        FILE_BASIC_INFORMATION fbi = new FILE_BASIC_INFORMATION();
        IO_STATUS_BLOCK iosb = new IO_STATUS_BLOCK();
        using (FileStream fs = new FileStream(path2file, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            p_fbi = Marshal.AllocHGlobal(Marshal.SizeOf(fbi));               
            IntPtr iprc = NtQueryInformationFile(fs.SafeFileHandle, ref iosb, p_fbi, 
            (uint)Marshal.SizeOf(fbi), FILE_INFORMATION_CLASS.FileBasicInformation);
            brc = iprc == IntPtr.Zero && iosb.status == 0;
            if (brc)
            {
                brc = false;
                fbi = (FILE_BASIC_INFORMATION)Marshal.PtrToStructure(p_fbi, typeof(FILE_BASIC_INFORMATION));
                creationTime = DateTime.FromFileTime(fbi.CreationTime);
                lastAccessTime = DateTime.FromFileTime(fbi.LastAccessTime);
                lastWriteTime = DateTime.FromFileTime(fbi.LastWriteTime);
                changeTime = DateTime.FromFileTime(fbi.ChangeTime);
                brc = true;
            }
        }
    }
    catch (Exception ex)
    {
        brc = false;
        errMsg = ex.Message;
    }
    finally
    {
        if (p_fbi != IntPtr.Zero) { Marshal.FreeHGlobal(p_fbi); }
    }
    return brc;
}

 

Pay no attention to the GetFourFileTimes() method as it will not be brought over into my PowerShell code. As you can see, there are a few things happening here that I need to migrate but nothing that is incredibly complex.

$fbi = New-Object "FileBasicInformation"
$iosb = New-Object "IOStatusBlock"

$FileStream = [System.IO.File]::Open("C:\Users\proxb\desktop\desktop.ini",'Open','Read','ReadWrite')

$p_fbi = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.Runtime.InteropServices.Marshal]::SizeOf($fbi))

First I create new objects using my newly created Structs. I then open up a file stream to a file of my choosing and making sure that others could access it if needed by supply values for the parameters of the Open() method of the System.Io.File type that ensure the file is not being locked.

I then get the physical location in memory ($p_fbi using AllocHGlobal()) of the FileBasicInformation struct type by first getting its size in bytes using the SizeOf() method (in this case, the size is 64 bytes).

$iprc = [ntdll]::NtQueryInformationFile($FileStream.SafeFileHandle, $iosb, $p_fbi, 
    [System.Runtime.InteropServices.Marshal]::SizeOf($fbi), [FileInformationClass]::FileBasicInformation
)

Here is where I call my NtQueryInformationFile() method with the required parameter values. I supply my SafeFileHandle from the file stream, my IO_STATUS_BLOCK struct that will be updated for review to see if something went wrong, the physical location of the FILE_BASIC_INFORMATION struct type that will also be updated by the method so I can view the timestamps, the size in bytes of the FILE_BASIC_INFORMATION type and finally the FILE_INFORMATION_CLASS type with FileBasicInformation. The returned IntPtr value will be used later on to determine if this was successful.

 

If ($IsOK) {
    # Pull data from unmanaged memory block into a usable object
    $fbi = [System.Runtime.InteropServices.Marshal]::PtrToStructure($p_fbi, [FileBasicInformation])
    [pscustomobject]@{
        FullName = $FileStream.Name
        CreationTime = [datetime]::FromFileTime($fbi.CreationTime)
        LastAccessTime = [datetime]::FromFileTime($fbi.LastAccessTime)
        LastWriteTime = [datetime]::FromFileTime($fbi.LastWriteTime)
        ChangeTime = [datetime]::FromFileTime($fbi.ChangeTime)
    }
} Else {
    Write-Warning "$($Item): $(New-Object ComponentModel.Win32Exception)"
}

 

After running the method, I now determine if everything went according to plan. If not an error will be thrown, otherwise I will continue on my path by marshaling the data ($p_fbi using PtrToStructure()) from the unmanaged memory over to a managed object that I can then look at (my $fbi type I created earlier). Now it is just a matter of converting the timestamps to a more human readable value using the FromFileTime() method and then displaying the results.

image

So there you go! I have tossed out my C# here string that was compiled using Add-Type and instead took the dive into Reflection to accomplish the same thing. While this is something that a very small percentage of people might ever use, it was absolutely a fun time learning more about this!

By the way, I did update my Get-FileTimeStamp function to use Reflection instead of compiled C#. You can download the updated function from the link below.

http://gallery.technet.microsoft.com/scriptcenter/Get-MFT-Timestamp-of-a-file-9227f399

Posted in powershell | Tagged , , , , , , | 1 Comment