Category Archives: security

Find Files with WMI and PowerShell

magnifying-glass-text-label-searchFinding files is one of those necessary evils for IT Pros. Sometimes we’re searching for a needle in a haystack. And it gets even more complicated when the haystacks are on 10 or 100 or 1000 remote computers. You might think using Get-ChildItem is your only option. Certainly it works, but if you are searching an entire hard drive it is pretty resource intensive.

Another option is to use WMI and CIM_Datafile class. Don’t let the name fool you. You can use Get-WmiObject in PowerShell 2.0 or 3.0. Every file, as far as I know, is also registered with WMI so all you need to do is query for all instances of the CIM_Datafile class. However, you must be clever. Just like searching an entire drive, searching via WMI can be time consuming. So you need to make your WMI query as specific as possible. To do that you need to know the properties. Here’s what a CIM_Datafile object looks like.

Status : OK
Name : c:\program files (x86)\windows defender\mpclient.dll
__GENUS : 2
__CLASS : CIM_DataFile
__SUPERCLASS : CIM_LogicalFile
__DYNASTY : CIM_ManagedSystemElement
__RELPATH : CIM_DataFile.Name="c:\\program files (x86)\\windows defender\\mpclient.dll"
__PROPERTY_COUNT : 33
__DERIVATION : {CIM_LogicalFile, CIM_LogicalElement, CIM_ManagedSystemElement}
__SERVER : SERENITY
__NAMESPACE : root\cimv2
__PATH : \\SERENITY\root\cimv2:CIM_DataFile.Name="c:\\program files (x86)\\windows d
efender\\mpclient.dll"
AccessMask : 17957033
Archive : True
Caption : c:\program files (x86)\windows defender\mpclient.dll
Compressed : False
CompressionMethod :
CreationClassName : CIM_LogicalFile
CreationDate : 20120725214205.814611-240
CSCreationClassName : Win32_ComputerSystem
CSName : SERENITY
Description : c:\program files (x86)\windows defender\mpclient.dll
Drive : c:
EightDotThreeFileName : c:\program files (x86)\windows defender\mpclient.dll
Encrypted : False
EncryptionMethod :
Extension : dll
FileName : mpclient
FileSize : 662016
FileType : Application Extension
FSCreationClassName : Win32_FileSystem
FSName : NTFS
Hidden : False
InstallDate : 20120725214205.814611-240
InUseCount :
LastAccessed : 20120725214205.814611-240
LastModified : 20120725231905.556000-240
Manufacturer : Microsoft Corporation
Path : \program files (x86)\windows defender\
Readable : True
System : False
Version : 4.0.9200.16384
Writeable : True
Scope : System.Management.ManagementScope
Options : System.Management.ObjectGetOptions
ClassPath : \\SERENITY\root\cimv2:CIM_DataFile
Properties : {AccessMask, Archive, Caption, Compressed...}
SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...}
Qualifiers : {dynamic, Locale, provider, UUID}
Site :
Container :

At a minimum you should limit your query to the drive. Otherwise the WMI query will search ALL drives. If you are searching by path, description or caption, don’t forget that the \ character needs to be escaped, e.g. \\program files (x86)\\windows defender\\. Of course if you know that much already you might as well use Get-Childitem.

For me, the real benefit in using WMI is when I know the file name but don’t know for sure where it might be on a given drive. So I put together an advanced function called Get-CIMFile.


Function Get-CIMFile {
#comment based help is here

[cmdletbinding()]
Param(
[Parameter(Position=0,Mandatory=$True,HelpMessage="What is the name of the file?")]
[ValidateNotNullorEmpty()]
[alias("file")]
[string]$Name,
[ValidateNotNullorEmpty()]
[string]$Drive="C:",
[ValidateNotNullorEmpty()]
[string[]]$Computername=$env:computername,
[switch]$AsJob
)

<#
strip off any trailing characters to drive parameter
that might have been passed.
#>
If ($Drive.Length -gt 2) {
$Drive=$Drive.Substring(0,2)
}

Write-Verbose "Searching for $Name on Drive $Drive on computer $Computername."

<#
Normally you might think to simply split the name on the . character. But
you might have a filename like myfile.v2.dll so that won't work. In a case
like this the extension would be everything after the last . and the filename
everything before.

So instead I'll use the substring method to "split" the filename string.
#>

#get the index of the last .
$index = $name.LastIndexOf(".")
#get the first part of the name
$filename=$Name.Substring(0,$index)
#get the last part of the name
$extension=$name.Substring($index+1)

$filter = "Filename='$filename' AND extension='$extension' AND Drive='$drive'"
Write-Verbose $filter

#get all instances of the file and write the WMI object to the pipeline
Get-WmiObject -Class CIM_Datafile -Filter $filter -ComputerName $Computername -Asjob:$AsJob

} #end Get-CIMFile

The code is documented to explain what is going on so I won’t repeat it here. The function will work anywhere you have WMI access. This version doesn’t handle alternate credentials or other features of Get-WmiObject, which you could add if you want. But with this I can check files on multiple computers. Suppose I’m concerned about a vulnerability like the recent Java problem. Or I need to see if any computers are out of date on a given file. I can run a command like this.


PS Scripts:\> $files = get-cimfile mpclient.dll -comp serenity,novo8
PS Scripts:\> $files | Sort name,CSName | Select Name,Version,CSName

Name Version CSName
---- ------- ------
c:\program files (x86)\windows... 4.0.9200.16384 SERENITY
c:\program files\windows defen... 4.0.9200.16384 NOVO8
c:\program files\windows defen... 4.0.9200.16384 SERENITY
c:\windows\winsxs\amd64_window... 4.0.9200.16384 SERENITY
c:\windows\winsxs\wow64_window... 4.0.9200.16384 SERENITY
c:\windows\winsxs\x86_windows-... 4.0.9200.16384 NOVO8

The command writes the full WMI object to the pipeline so I could filter or format $files however I need.

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

Scripting with PSCredential

I see this question often: how can I pass a parameter value for a PSCredential that might be a credential object or it might be a user name? In the past I’ve used code like this:


begin {
Write-Verbose -Message "Starting $($myinvocation.mycommand)"
write-verbose -Message "Using volume $($volume.toUpper())"
#convert credential to a PSCredential if a string was passed.
if ( $credential -is [system.management.automation.psCredential]) {
Write-Verbose "Using PSCredential for $($credential.username)"
}
ElseIf ($Credential) {
Write-Verbose "Getting PSCredential for $credential"
$Credential=Get-Credential $credential
}
} #Begin

This assumes $Credential is a parameter. But then I realized, why not take advantage of parameter validation? I could use the [ValidateScript()] parameter attribute and insert some code to test the incoming value. If it is already a PSCredential, don’t do anything. But if it is a string, call Get-Credential and use the result.


Param (
[Parameter(Position=0)]
[ValidateNotNullorEmpty()]
[string]$Computername=$env:Computername,
[ValidateScript({
if ($_ -is [System.Management.Automation.PSCredential]) {
$True
}
elseif ($_ -is [string]) {
$Script:Credential=Get-Credential -Credential $_
$True
}
else {
Write-Error "You passed an unexpected object type for the credential."
}
})]
[object]$Credential

When using ValidateScript your code has to return True or False. Or you can also Write and error if you want to customize the exception message a bit. That’s what I’ve done here. With this code I can either use -Credential with a value like jdhitsolutions\administrator or a saved PSCredential object. Let me show you a simple script with this in action, plus I’ll address another common question about using credentials with WMI-based scripts and functions.


#requires -version 2.0

<#
This function demonstrates how you might pass a credential
object as a parameter
#>

Function Get-OSName {
[cmdletbinding()]

Param (
[Parameter(Position=0)]
[ValidateNotNullorEmpty()]
[string]$Computername=$env:Computername,
[ValidateScript({
if ($_ -is [System.Management.Automation.PSCredential]) {
$True
}
elseif ($_ -is [string]) {
$Script:Credential=Get-Credential -Credential $_
$True
}
else {
Write-Error "You passed an unexpected object type for the credential."
}
})]
[object]$Credential

)

#Never write the same line of code more than once if you can avoid it
$wmiCommand="Get-WmiObject -Class Win32_Operatingsystem -Property Caption,CSName -ComputerName $Computername"
Write-Verbose $wmiCommand

if ($Credential) {
#add the credential to the command string
Write-Verbose "Adding credential"
#escape the $ sign so that the command uses the variable name
$wmiCommand+=" -credential `$Script:Credential"
}

Write-Verbose "Creating a scriptblock from the command string"
$sb=[scriptblock]::Create($wmiCommand)

Try {
Write-Verbose "Invoking the command"
Invoke-Command -ScriptBlock $sb -errorAction Stop |
Select @{Name="Computername";Expression={$_.CSName}},
@{Name="OperatingSystem";Expression={$_.Caption}}
}
Catch {
Write-Warning $_.Exception.Message
}
Finally {
Write-Verbose "Finished"
}

} #close function

So the challenge is if I have a credential I need to use a Get-Wmiobject expression that uses it, otherwise run an expression without it. I’m a big believer in avoiding writing the same line of code more than once so I’ll create a command string with my basic WMI command.


$wmiCommand="Get-WmiObject -Class Win32_Operatingsystem -Property Caption,CSName -ComputerName $Computername"

In this example the value for $Computername will be expanded and inserted into the string. If no credential is passed then this is the command I’ll run. But if a credential is passed, then all I need to do is append it to my command string.


$wmiCommand+=" -credential `$Script:Credential"

You must pay attention to a very subtle detail: I am escaping the $ sign in the variable name. I do not want PowerShell to expand the variable. I want the command string to use the variable as variable. That is, if using a credential I need the command to be: Get-WmiObject -Class Win32_Operatingsystem -Property Caption,CSName -ComputerName SERVER01 -credential $Script:Credential”

The last step is to turn this command string into a script block so it can be executed.


$sb=[scriptblock]::Create($wmiCommand)

Armed with a scriptblock I can use Invoke-Command.


Invoke-Command -ScriptBlock $sb -errorAction Stop |
Select @{Name="Computername";Expression={$_.CSName}},
@{Name="OperatingSystem";Expression={$_.Caption}}

The end result is a function that I can run with no credentials. If I use a credential value like jdhitsolutions\administrator, I’ll get prompted for the password from Get-Credential. Or if I pass a saved credential, the function will use it.

These techniques are by no means the only solution but I find them simple to follow and effective.

Friday Fun Add Scripting Signing to the ISE

Today’s fun involves adding a menu item to the PowerShell ISE to make it easy to sign your scripts. I’m not going to go into the details about getting and installing a code signing certificate. I also assume you only have one installed. You can get this certificate by seasrching the CERT: PSDrive.

[cc lang="PowerShell"]
PS S:\> dir cert:\CurrentUser\My -CodeSigningCert

Directory: Microsoft.PowerShell.Security\Certificate::CurrentUser\My

Thumbprint Subject
———- ——-
6372DB82AEB690C620E45675C464254FD58FAA97 CN=Jeffery Hicks
[/cc]

To sign a script use the Set-AuthenticodeSignature cmdlet specifying the file and certificate. With these two pieces of information I wrote a function to be used in the ISE that signs the currently active script.

[cc lang="PowerShell"]
Function Sign-ISEScript {

Param()

Set-StrictMode -Version Latest

#get the certificate
$cert=get-childitem -Path cert:\currentuser\my -CodeSigningCert
if ($cert) {

#save the file if necessary
if (!$psise.currentfile.IsSaved) {
$psise.CurrentFile.Save()
}

#if the file is encoded as BigEndian, resave as Unicode
if ($psise.currentfile.encoding.encodingname -match “Big-Endian”) {
$psise.CurrentFile.Save([Text.Encoding]::Unicode) | Out-Null
}

#save the filepath for the current file so it can be re-opened later
$filepath=$psise.CurrentFile.FullPath

#sign the file
Try {
Set-AuthenticodeSignature -FilePath $filepath -Certificate $cert -errorAction Stop
#close the file
$psise.CurrentPowerShellTab.Files.remove($psise.currentfile) | Out-Null

#reopen the file
$psise.CurrentPowerShellTab.Files.add($filepath) | out-null
}
Catch {
Write-Warning (“Script signing failed. {0}” -f $_.Exception.message)
}

} #if code cert found

else {
Write-Warning “No code signing certificate found.”
}
} #end function
[/cc]

The function saves the script if it needs to be so that it can be properly signed. The other check I have to make is the file encoding. By default when you create a new script in the ISE, the encoding is set to Big-Endian. Unfortunately, you can’t sign scripts with this type of encoding so if the encoding is Big-Endian, the file gets saved as a Unicode file.

[cc lang="PowerShell"]
if ($psise.currentfile.encoding.encodingname -match “Big-Endian”) {
$psise.CurrentFile.Save([Text.Encoding]::Unicode) | Out-Null
}
[/cc]

Now we’re ready to sign the script. But first I grab the full path and save it to a variable because after I sign the script I want to close the file and re-open it so you can see that it has been signed.

[cc lang="PowerShell"]
#save the filepath for the current file so it can be re-opened later
$filepath=$psise.CurrentFile.FullPath

#sign the file
Try {
Set-AuthenticodeSignature -FilePath $filepath -Certificate $cert -errorAction Stop
#close the file
$psise.CurrentPowerShellTab.Files.remove($psise.currentfile) | Out-Null

#reopen the file
$psise.CurrentPowerShellTab.Files.add($filepath) | out-null
}
[/cc]

Once the function is loaded in the ISE, I could run it any time from the command prompt to sign the current file. But I add the script file to my ISE profile and then insert a menu item.

[cc lang="PowerShell"]
#this goes in my ISE profile
. c:\scripts\Sign-ISEScript.ps1

$psISE.CurrentPowerShellTab.AddOnsMenu.submenus.Add(“Sign Script”,{Sign-ISEScript},$null) | Out-Null
[/cc]

Now I have a menu choice under Add-Ons for “Sign Script”. I can click it and the current file will be signed, closed and reopened. If you edit the file again, don’t forget you’ll need to resign the script. Commercial editors like PrimalScript can be configured to automatically sign scripts whenever they are saved. The ISE is much more limited.

Still, I hope you can have some fun with this and maybe even pick up a PowerShell tidbit or two.

Download Sign-ISEScript.ps1.

Architecting the Right Solution for Strong Authentication

esarssa[1] My first project with RealTime Publishers is now available. I wrote a short 3 part series on strong authentication: Architecting the Right Solution for Strong Authentication sponsored by Imprivata.

Synopsis

“Insufficient security is a hidden problem that many businesses are not fully aware of until it is too late. Weak authentication, silos of compliance reporting, a multitude of management tools, and poor security practices contribute to data breaches and compromised systems and leave organizations vulnerable to other pervasive threats. Fortunately, strong authentication systems can address these issues. A combination of consolidated identity management, single sign-on services, and comprehensive compliance reporting can reduce compliance costs, improve security, and remove significant drag on innovation. The Essentials Series: Architecting the Right Solution for Strong Authentication examines ways in which weak authentication hampers business operations, criteria for selecting a strong authentication system, and tips on how to deploy and manage strong authentication systems to control risks and improve the efficiency of business operations. “

 

You can download the chapters individually or as a zip file.  I hope you’ll take a look.