Release of Windows Management Framework 3.0 – Beta (Includes PowerShell 3.0, WMI & WinRM)

http://www.microsoft.com/download/en/details.aspx?id=28998

Advertisements

Get-MailboxOwner

I used to have an old vbscript from Glen’s website that I modified so that it returns the username of the mailbox owner.

I didn’t notice until recently but it turns out that Glen has provided a version of his script in powershell as well as a GUI version.

With powershell V2 and the ActiveDirectory module things are even much easier. I’ve added also two features:

  • The ability to use wildcards in the MailboxName script parameter.
  • The ability to pass a username (SamAccountName) to the script instead of a mailboxname so that it enumerates all mailboxes rights and shows the mailboxnames where the given username has full control. I know it may really be time consuming depending on how many objects you have in your Active Directory. So, I’ve added a progress bar while searching.

Pls note also that I’ve hardcoded the maximum number of AD objects returned by the LDAP query to 10000. You may need to modify it…

#Requires -Version 2.0

<#
 
.SYNOPSIS    
    Search for mailbox owner by using a mailboxname or a username
 
.DESCRIPTION  
    Search for mailbox owner by using a mailboxname or a username

 
.PARAMETER MailboxName
    Displays who is the current owner on the mailbox

.PARAMETER UserName
    Displays which mailbox the UserName has been granted full control

.EXAMPLE    
    .\Get-MailboxOwner.ps1 -UserName $env:username

    Returns the mailbox names where the current user has full control

.EXAMPLE    
    .\Get-MailboxOwner.ps1 -MailboxName "test*"

    Returns the mailbox names and their owner. 
    Wildcard are allowed in the mailbox name.

.NOTES    
    Name: Get-MailboxOwner
    Author: Emin Atac
    DateCreated: 28/02/2012
 
.LINK    
    https://p0w3rsh3ll.wordpress.com

INPUTS
System.String

OUTPUTS
System.Management.Automation.PSCustomObject

#>


[CmdletBinding(DefaultParameterSetName='MBX', SupportsTransactions=$false)]
param(
   [Parameter(ParameterSetName='MBX', Mandatory=$true, ValueFromPipeline=$true, Position=0)]
   [system.string]${MailboxName},

   [Parameter(ParameterSetName='User', Mandatory=$true, ValueFromPipeline=$true, Position=0)]
   [system.string]${UserName}
)



function ConvertTo-Sid
{
<#
    
.SYNOPSIS    
    Translate a user name to a SID

.DESCRIPTION  
    Translate a user name to a SID

.PARAMETER Sid
    Provide a username

.NOTES    
    Name: ConvertTo-Sid
    Author: thepowershellguy
        
.LINK    
    http://thepowershellguy.com/blogs/posh/archive/2007/01/23/powershell-converting-accountname-to-sid-and-vice-versa.aspx
     
.EXAMPLE
    (ConvertTo-Sid "Domain\Administrator").Value
    Get the SID of the Active Directory Domain admininstrator

.EXAMPLE
    ConvertTo-Sid "Administrator"
    Get the SID of the local administrator account
#>

param(
[parameter(Mandatory=$true,Position=0)][system.string]$NtAccount = $null
)
 begin
 {
    $obj = new-object system.security.principal.NtAccount($NTaccount)
 } 
 process
 {
    try
    {
        $obj.translate([system.security.principal.securityidentifier])
    }
    catch
    {
        # To remove the silent fail, uncomment next line
        # $_
    }
 } 
 end
 {
 }
}

# First we load the Active Directory module as it's required
if ( (Get-Module -Name ActiveDirectory).Name -ne "ActiveDirectory")
{
Write-Host -ForegroundColor Yellow -Object "Attempting to load Active Directory module for Powershell"
Import-Module -Name ActiveDirectory -ErrorAction SilentlyContinue
    if ( (Get-PSDrive -PSProvider ActiveDirectory -ErrorAction SilentlyContinue).Name -ne "AD")
    {
     Write-Host -ForegroundColor Red -Object "Active Directory module for Powershell not loaded. Aborting"
     exit
    }
}

# Get the Primary Domain controller
$PDC  = (Get-ADDomainController  -Service 1 -Discover).Hostname

$results = @()

switch ($PsCmdlet.ParameterSetName)
{ 
    MBX {

        # Get the mailbox
        $all = Get-ADUser -Filter {(mailnickname -like $MailboxName)} -Properties mailnickname,msExchMailboxSecurityDescriptor -SearchScope 2 -Server "$PDC"
        
        foreach ($item in $all)
        {
            # The user who created the mailbox: $item.msExchMailboxSecurityDescriptor.Owner
            foreach ($ace in $item.msExchMailboxSecurityDescriptor.Access)
            {
                if ($ace.ActiveDirectoryRights -band [System.DirectoryServices.ActiveDirectoryRights]::CreateChild)
                {
                    # Make sure we have only user who are domain members
                    if ((ConvertTo-Sid $ace.IdentityReference.Value).Value -match "S-1-5-21-")
                    {
                        # We can skip inherited domain users
                        if (-not($Ace.IsInherited))
                        {
                            $obj = New-Object -TypeName PSObject -Property @{
                                    MailboxName = $item.mailnickname
                                    Owner = $ace.IdentityReference.Value
                                }
                            # Add the object to our array
                            $results += $obj
                        }
                    }
                }
            }
        }

    }
    User {
        # Get the mailbox
        $all = Get-ADUser -Filter {(mailnickname -like "*")} -Properties mailnickname,msExchMailboxSecurityDescriptor -SearchScope 2 -Server "$PDC" -ResultSetSize 10000
        $count = 0
        foreach ($item in $all)
        {
            # It's a resource consuming task so let's indicate some progress
            $count++
            Write-Progress -activity "Seaching username" -status "Percent added: " -PercentComplete (($count/$all.Count)*100)
            foreach ($ace in $item.msExchMailboxSecurityDescriptor.Access)
            {
                if ($ace.ActiveDirectoryRights -band [System.DirectoryServices.ActiveDirectoryRights]::CreateChild)
                {
                    if (-not($Ace.IsInherited))
                    {
                        if (($ace.IdentityReference.Value) -match $UserName)
                        {
                                $obj = New-Object -TypeName PSObject -Property @{
                                        MailboxName = $item.mailnickname
                                        Owner = $UserName
                                    }
                                # Add the object to our array
                                $results += $obj
                         }
                    }
                }
            }
        }
    }
}

return $results

Doing some maths with powershell…

I’ve been doing sometimes some mathematics in the powershell console and got surprised by the result when I typed:

116.40-112.375
4,02500000000001

Then I did

116.40-112.37
4,03

So to obtain the correct result, I had to change one of the [double] to a [decimal] type:

 116.40d-112.375
4,025
# or
116.40-112.375d
4,025

Is this normal ? Expected ?

I came accross this article: http://thepowershellguy.com/blogs/posh/archive/2007/04/02/44-7-integer-of-double.aspx. But there isn’t a clear explanation of what’s going on.

I finally found the answer is Bruce Payette book’s Powershell In Action

The tendency is to characterize PowerShell as a dynamically typed language, but a better description is that PowerShell is a type-promiscuous language (sounds salacious doesnโ€™t it?). By type-promiscuous, we mean that PowerShell will expend a tremendous amount of effort trying to turn what you have into what you need with as little work on your part as it can manage.
[…]
In practice, the interpreter is very careful about making sure its transformations are reasonable and that no information is unexpectedly lost. This is particularly important when dealing with numeric calculations. In PowerShell, you can freely mix and match different types of numbers in expressions. You can even include strings in this mix. PowerShell converts everything as needed as long as there is no loss in precision without specific guidance from the user.

PSH3 CTP2 Malware Analysis Module based on virustotal.com API v2

I’ve been using virustotal for years on a daily basis to submit suspect files detected by our security compliance audit script (or missed by a desktop antivirus software). We’ve been filling up an excel spreadsheet with the incident related information and especially what’s the malware name and what was the virustotal URL that gave us this (crucial) identification piece of info. Now I’d like to automate the reporting and I came up the other day with a new idea while reading the automation capabilities provided by virustotal.com. Before doing a monthly or a weekly report, it could be nice to (re)analyse the suspicious files we’ve been collected. So I started reading the new V2 public API documentation on this page: https://www.virustotal.com/documentation/public-api/v2/ and a few hours later I’ve finished writing a malware analysis module for powsershell V3.
If you want to give it a try, the first thing to do is to signup on the virustotal.com page to get your API key.
Then to install it, you do:

new-item "$env:userprofile\documents\windowspowershell\modules\VT Malware Analysis" -type directory
copy ".\VT Malware Analysis.ps*" "$env:userprofile\documents\windowspowershell\modules\VT Malware Analysis"
Import-Module -Name "VT Malware Analysis"
$APIkey = "the_API_key_you_got_from_VT_after_signup"
# List the functions provided by the module
Get-Command -Module "VT Malware Analysis"
# Get help on a command
Get-Help Send-MaliciousURLSample -full

Here is the ‘VT Malware Analysis.psd1’ file content

@{
    ModuleVersion = '1.0.0'
    Author='Emin Atac'
    Copyright='Emin Atac'    
    Description='VT Malware Analysis is a module to help you interact with the V2 API provided by virustotal.com'
    Guid='bcbcd41b-619b-4f73-904e-6e530f752362'
    RootModule='VT Malware Analysis.psm1'
    PowerShellVersion = '3.0'
} 

and the ‘VT Malware Analysis.psm1’ file content


#region  Functions

function Send-MalwareFileSample
{
[cmdletbinding(DefaultParameterSetName='',SupportsTransactions=$false)]
param(
    [Parameter(Mandatory=$true,ParameterSetName='',ValueFromPipeline=$true,Position=0)]
    [system.array]$File=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=1)]
    [System.Management.Automation.SwitchParameter]$VerboseMode = $false,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=2)]
    [System.Management.Automation.SwitchParameter]$Intensive = $false,
    
    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=3)]
    [System.URI]$Proxy=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=4)]
    [System.Management.Automation.PSCredential]$ProxyCredential=$null
)
# Store the start time
$BigStart = (Get-Date)
if ($APIkey -eq $null)
{
    Write-Host -ForegroundColor Gray -Object ("Define first a `$APIkey variable with the key you got from virustotal, see help")
    break
}
# Prepare a hashtable for splatting
$otherparams = @{}
if ($Proxy)
{
    $otherparams += @{Proxy = $Proxy}
    if ($ProxyCredential)
    {
       $otherparams += @{ProxyCredentials = $ProxyCredential}
    }
}
# Initialize empty arrays
$arResponses = @()
$arSamples = @()
# Loop for all submitted input File object
foreach ($item in $File)
{
    # First check if it's type is already a system.IO.FileInfo
    if ($item -is [system.IO.Fileinfo])
    {
        if ($item.length -lt 20MB)
        {
            # We can immediately add it to our samples array
            Write-Verbose -Message "Adding $($item.Fullname) to malware array" -Verbose:$VerboseMode
            $arSamples += $item
        } else {
            Write-Host -ForegroundColor Yellow -Object ("$($item.Fullname) will not be checked as its size exceeds 20B")
        }
    } else {
        # If strings were typed as input
        if (Test-Path -Path $item)
        {
            # As it exists, we can add it to the array is its size is less than 20MB
            if ((Get-Item -Path $item).length -lt 20MB)
            {
                $arSamples += Get-item -Path $item
            } else {
                Write-Host -ForegroundColor Yellow -Object ("$item will not be checked as its size exceeds 20B")
            }
        } else {
            # Let us know that we can't find it 
                # if ($Verbose) { Write-Host -ForegroundColor Yellow -Object ("$item was not found")}
                Write-Verbose -Message ("$item was not found") -Verbose:$VerboseMode
        }
    }
}
# Initialize variables
$count = $i = 0
$Start =  (Get-Date)
# Now loop for each element we have in our array
foreach ($item in $arSamples)
{
    # Show some progress activity...
    $i++
    Write-Progress -activity "Dealing with file: $($item.fullname)" -status "Percent added: " -PercentComplete (($i/$arSamples.Count)*100)
    # Read the file content as byte array
    $bytear = Get-Content $item.Fullname -ReadCount 0 -Encoding byte
    # Encode in iso-8859-1
    $filedata = [System.Text.Encoding]::GetEncoding("iso-8859-1").GetString($bytear)
    # Get a random GUID
    $boundary = [System.Guid]::NewGuid().ToString();
    # Use a Here-string to build the content of the multipart/form-data
    $body = @"
--$boundary
Content-Disposition: form-data; name="apikey"

$APIkey
--$boundary
Content-Disposition: form-data; name="file"; filename="$($item.Fullname)"
Content-Type: application/octet-stream

$filedata
--$boundary--
"@
    if ($Intensive)
    {
        # Skip, as we don't care about API limits
        Write-Verbose -Message "Continuing as intensive switch was specified" -Verbose:$VerboseMode
    } else {
        $count++
        $End = New-Timespan $Start (Get-Date)
        if ($End -le (New-TimeSpan -Minutes 1))
        {
            # If the elapsed time if less than a minute, we'll check how many operations we did
            if ($count -le 4)
            {
                Write-Verbose -Message "Continuing as count is $count and the 1 minute limit was not reached yet" -Verbose:$VerboseMode
                # We did not reach yet the maximum of 4 operations per minute
            } else {
                # We have to wait as only 4 operations of any kind is allowed per minute
                $Waitfor = ((New-TimeSpan -Minutes 1) - $End).Seconds
                Write-Verbose -Message "Waiting for $Waitfor seconds as max count of $count reached and 1 minute not yet elapsed" -Verbose:$VerboseMode
                Start-Sleep -Seconds  $Waitfor
                # Reset the count and the start date
                $count = 1
                $Start = (Get-Date)
            } # end of less than 4 operations
        } else {
            # More than a minute elapsed, we can safely reset the count and the start
            $count = 1
            $Start = (Get-Date)
        } # end of less than a minute
    } # end of Intensive
    # Here we go !
    $webresponse = $null
    try
    {
        # Invoke-RestMethod returns a PSCustom object
        $webresponse = Invoke-RestMethod -Method Post -Uri ("https://www.virustotal.com/vtapi/v2/file/scan") -ContentType "multipart/form-data; boundary=$boundary" -Body $body @otherparams -ErrorAction Stop
    } 
    catch
    {
        Write-Host -ForegroundColor Red -Object ("The following error occured while posting request: " + $_)
    }
    # Add the response to an array
    if ($webresponse -ne $null)
    {
        $arResponses += New-Object -TypeName PSObject -Property @{            
            Resource = $webresponse.Resource
            Code = $webresponse.response_code
            Filename = $item.Fullname
        }
        # We can convert it to JSON to display it if verbose mode was enabled
        if ($VerboseMode)
        {
            $webresponse | ConvertTo-Json
        }
        Start-Sleep -Seconds 2
    } else {
        Write-Host -ForegroundColor Red -Object ("The response of the web request is null for some reasons")
    }
}
# Calculate how much time elapsed
$BigEnd = New-Timespan $BigStart (Get-Date)                    
Write-Verbose -Message ("Operation completed in: $('{0}' -f $BigEnd)") -Verbose:$VerboseMode
# Finally return our array
return $arResponses
<#
    
.SYNOPSIS    
    Send a malware sample to be analysed by virustotal
   
.DESCRIPTION  
    Send a malware file using the public API v2 of virustotal.com
    The file is sent by forming a multipart/form-data post web request over https.

.PARAMETER File
    The path of the file to be submitted to virustotal.com

.PARAMETER VerboseMode
    Switch parameter that will activate a verbose console output

.PARAMETER Intensive
    Switch to override the default limit of 4 requests of any nature per minute if you have the private API

.PARAMETER Proxy
    Set the proxy address to use (optional)

.PARAMETER ProxyCredential
    Set the credentials to be used with the proxy address (optional)
     
.LINK    
    https://p0w3rsh3ll.wordpress.com

.EXAMPLE
    Send-MalwareFileSample -File .\eicar.com
    Upload the file eicar.com to virustotal.com
     
.EXAMPLE
    Get-item  .\eicar.com | Send-MalwareFileSample
    Upload the file eicar.com to virustotal.com

.EXAMPLE
    Get-item  .\eicar.com | Send-MalwareFileSample -Proxy "http://my.internal.proxy.address"
    Upload the file eicar.com to virustotal.com using the internal http://my.internal.proxy.address

.NOTES    
    Name: Send-MalwareFileSample
    Author: Emin Atac
    DateCreated: 24/02/2012

#>
} # end of Send-MalwareFileSample function

Function Get-MalwareFileReport
{
[cmdletbinding(DefaultParameterSetName='',SupportsTransactions=$false)]
param(
    [Parameter(Mandatory=$true,ParameterSetName='',ValueFromPipeline=$true,Position=0)]
    [system.array]$InputObject=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=1)]
    [System.Management.Automation.SwitchParameter]$VerboseMode = $false,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=2)]
    [System.Management.Automation.SwitchParameter]$Intensive = $false,
    
    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=3)]
    [System.URI]$Proxy=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=4)]
    [System.Management.Automation.PSCredential]$ProxyCredential=$null
)
# Store the start time
$BigStart = (Get-Date)
if ($APIkey -eq $null)
{
    Write-Host -ForegroundColor Gray -Object ("Define first a `$APIkey variable with the key you got from virustotal, see help")
    break
}
# Prepare a hashtable for splatting
$otherparams = @{}
if ($Proxy)
{
    $otherparams += @{Proxy = $Proxy}
    if ($ProxyCredential)
    {
       $otherparams += @{ProxyCredentials = $ProxyCredential}
    }
}
# Initialize empty arrays
$arResponses = @()
$arSamples = @()
# Make sure that the InputObject has all the properties we expect
foreach ($item in $InputObject)
{
    if ($item.Code -eq 1)
    {
        # If the item was indeed present and it could be retrieved it will be 1.
        $arSamples += $item
    } elseif ($item.Code -eq -2) {
        # If the requested item is still queued for analysis it will be -2.
        Write-Host -ForegroundColor Yellow -Object ("The item $($item.Filename) is still queued for analysis")
    } elseif ($item.Code -eq 0) {
        # if the item you searched for was not present in VirusTotal's dataset this result will be 0.
        Write-Host -ForegroundColor Red -Object ("The item $($item.Filename) was not found in the VT dataset")
    } else {
        Write-Host -ForegroundColor Red -Object ("Unknown response from API: $($item.Code)")
    }
}
# Initialize variables
$count = $i = 0
# Now that we are sure that we have only system.io.fileinfo objects in our arSamples array
$Start =  (Get-Date)
foreach ($item in $arSamples)
{
    # Show some progress activity...
    $i++
    Write-Progress -activity "Dealing with file: $($item.Filename)" -status "Percent added: " -PercentComplete (($i/$arSamples.Count)*100)
    # Get a random GUID
    $boundary = [System.Guid]::NewGuid().ToString();
    # Use a Here-string to build the content of the multipart/form-data
    $respbody = @"
--$boundary
Content-Disposition: form-data; name="apikey"

$APIkey
--$boundary
Content-Disposition: form-data; name="resource"

$($item.resource)
--$boundary--
"@
    Write-Debug -Message $respbody
    if ($Intensive)
    {
        # Skip, as we don't care about API limits
        Write-Verbose -Message "Continuing as intensive switch was specified" -Verbose:$VerboseMode
    } else {
        $count++
        $End = New-Timespan $Start (Get-Date)
        if ($End -le (New-TimeSpan -Minutes 1))
        {
            # If the elapsed time if less than a minute, we'll check how many operations we did
            if ($count -le 4)
            {
                Write-Verbose -Message "Continuing as count is $count and the 1 minute limit was not reached yet" -Verbose:$VerboseMode
                # We did not reach yet the maximum of 4 operations per minute
            } else {
                # We have to wait as only 4 operations of any kind is allowed per minute
                $Waitfor = ((New-TimeSpan -Minutes 1) - $End).Seconds
                Write-Verbose -Message "Waiting for $Waitfor seconds as max count of $count reached and 1 minute not yet elapsed" -Verbose:$VerboseMode
                Start-Sleep -Seconds  $Waitfor
                # Reset the count and the start date
                $count = 1
                $Start = (Get-Date)
            } # end of less than 4 operations
        } else {
            # More than a minute elapsed, we can safely reset the count and the start
            $count = 1
            $Start = (Get-Date)
        } # end of less than a minute
    } # end of Intensive
    # Here we go !
    $webresponse = $null
    try
    {
        # Invoke-RestMethod returns a PSCustom object
        $webresponse = Invoke-RestMethod -Method Post -Uri ("https://www.virustotal.com/vtapi/v2/file/report") -ContentType "multipart/form-data; boundary=$boundary" -Body $respbody @otherparams -ErrorAction Stop
    } 
    catch
    {
        Write-Host -ForegroundColor Red -Object ("The following error occured while posting request: " + $_)
    }
    # Add the response to an array
    if ($webresponse -ne $null)
    {
        $fullURL = [system.URI]$webresponse.permalink
        $linkar = @($fullURL.Segments)
        $browsableURL = $fullURL.Scheme + "://" + $fullURL.Host + (-join ($linkar[0..($linkar.Count-2)]))
        # $browsableURL
        $arResponses += New-Object -TypeName PSObject -Property @{            
            ScanDate = [datetime]$webresponse.scan_date
            Total = $webresponse.total
            Positives = $webresponse.positives
            Resource = $webresponse.Resource
            Code = $webresponse.response_code
            Filename = $item.Filename
            Link = $browsableURL
            Scans = $webresponse.scans
        }
        Start-Sleep -Seconds 2
        # We can convert it to JSON to display it if verbose mode was enabled
        if ($VerboseMode)
        {
            $webresponse | ConvertTo-Json
        }
    } else {
        Write-Host -ForegroundColor Red -Object ("The response of the web request is null for some reasons")
    } # end of if webresponse is null
} # end of foreach
# Calculate how much time elapsed
$BigEnd = New-Timespan $BigStart (Get-Date)                    
Write-Verbose -Message ("Operation completed in: $('{0}' -f $BigEnd)") -Verbose:$VerboseMode
# Finally return our array
return $arResponses
<#
    
.SYNOPSIS    
    Retrieve the report of a malware sample already sent to virustotal
   
.DESCRIPTION  
    Retrieve the report of a previously submitted sample file by using the public API v2 of virustotal.com
    The report is retrieved by forming a multipart/form-data post web request over https.

.PARAMETER InputObject
    It represents a PSCustom object that has 3 properties: a filename, a resource and a response from the API when submitted

.PARAMETER VerboseMode
    Switch parameter that will activate a verbose console output

.PARAMETER Intensive
    Switch to override the default limit of 4 requests of any nature per minute if you have the private API

.PARAMETER Proxy
    Set the proxy address to use (optional)

.PARAMETER ProxyCredential
    Set the credentials to be used with the proxy address (optional)
     
.LINK    
    https://p0w3rsh3ll.wordpress.com

.EXAMPLE
    Send-MalwareFileSample -File .\eicar.com | Get-MalwareFileReport
    Upload the file eicar.com to virustotal.com and retrieve its report
     
.EXAMPLE
    Get-item  .\eicar.com | Send-MalwareFileSample | Get-MalwareFileReport
    Upload the file eicar.com to virustotal.com and retrieve its report

.NOTES    
    Name: Get-MalwareFileReport
    Author: Emin Atac
    DateCreated: 24/02/2012

#>     
} # end of Get-MalwareFileReport function

function Send-MaliciousURLSample
{
[cmdletbinding(DefaultParameterSetName='',SupportsTransactions=$false)]
param(
    [Parameter(Mandatory=$true,ParameterSetName='',ValueFromPipeline=$true,Position=0)]
    [system.array]$URL=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=1)]
    [System.Management.Automation.SwitchParameter]$VerboseMode = $false,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=2)]
    [System.Management.Automation.SwitchParameter]$Intensive = $false,
    
    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=3)]
    [System.URI]$Proxy=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=4)]
    [System.Management.Automation.PSCredential]$ProxyCredential=$null
)
# Store the start time
$BigStart = (Get-Date)
if ($APIkey -eq $null)
{
    Write-Host -ForegroundColor Gray -Object ("Define first a `$APIkey variable with the key you got from virustotal, see help")
    break
}
# Prepare a hashtable for splatting
$otherparams = @{}
if ($Proxy)
{
    $otherparams += @{Proxy = $Proxy}
    if ($ProxyCredential)
    {
       $otherparams += @{ProxyCredentials = $ProxyCredential}
    }
}
# Initialize empty arrays
$arResponses = @()
# Initialize variables
$count = $i = 0
$Start =  (Get-Date)
foreach ($item in $URL)
{
    # Show some progress activity...
    $i++
    Write-Progress -activity "Dealing with file: $item" -status "Percent added: " -PercentComplete (($i/$URL.Count)*100)
    # Get a random GUID
    $boundary = [System.Guid]::NewGuid().ToString();
    # Use a Here-string to build the content of the multipart/form-data
    $urlbody = @"
--$boundary
Content-Disposition: form-data; name="apikey"

$APIkey
--$boundary
Content-Disposition: form-data; name="url"

$item
--$boundary--
"@
    Write-Debug -Message $urlbody
    if ($Intensive)
    {
        # Skip, as we don't care about API limits
        Write-Verbose -Message "Continuing as intensive switch was specified" -Verbose:$VerboseMode
    } else {
        $count++
        $End = New-Timespan $Start (Get-Date)
        if ($End -le (New-TimeSpan -Minutes 1))
        {
            # If the elapsed time if less than a minute, we'll check how many operations we did
            if ($count -le 4)
            {
                Write-Verbose -Message "Continuing as count is $count and the 1 minute limit was not reached yet" -Verbose:$VerboseMode
                # We did not reach yet the maximum of 4 operations per minute
            } else {
                # We have to wait as only 4 operations of any kind is allowed per minute
                $Waitfor = ((New-TimeSpan -Minutes 1) - $End).Seconds
                Write-Verbose -Message "Waiting for $Waitfor seconds as max count of $count reached and 1 minute not yet elapsed" -Verbose:$VerboseMode
                Start-Sleep -Seconds  $Waitfor
                # Reset the count and the start date
                $count = 1
                $Start = (Get-Date)
            } # end of less than 4 operations
        } else {
            # More than a minute elapsed, we can safely reset the count and the start
            $count = 1
            $Start = (Get-Date)
        } # end of less than a minute
    } # end of Intensive
    # Here we go !
    $webresponse = $null
    try
    {
        # Invoke-RestMethod returns a PSCustom object
        $webresponse = Invoke-RestMethod -Method Post  -Uri ("https://www.virustotal.com/vtapi/v2/url/scan") -ContentType "multipart/form-data; boundary=$boundary" -Body $urlbody @otherparams -ErrorAction Stop
    } 
    catch
    {
        Write-Host -ForegroundColor Red -Object ("The following error occured while posting request: " + $_)
    }
    # Add the response to an array
    if ($webresponse -ne $null)
    {
        $arResponses += New-Object -TypeName PSObject -Property @{            
            Resource = $webresponse.resource
            Code = $webresponse.response_code
            SubmittedURL = $item
            SubmissionDate = [datetime]$webresponse.scan_date
            ScanID = $webresponse.scan_id
        }
        Start-Sleep -Seconds 2
        # We can convert it to JSON to display it if verbose mode was enabled
        if ($VerboseMode)
        {
            $webresponse | ConvertTo-Json
        }
    } else {
        Write-Host -ForegroundColor Red -Object ("The response of the web request is null for some reasons")
    }
}
# Calculate how much time elapsed
$BigEnd = New-Timespan $BigStart (Get-Date)                    
Write-Verbose -Message ("Operation completed in: $('{0}' -f $BigEnd)") -Verbose:$VerboseMode
# Finally return our array
return $arResponses
<#
    
.SYNOPSIS    
    Send a malicious URL to be analysed by virustotal
   
.DESCRIPTION  
    Send a malicious URL using the public API v2 of virustotal.com
    The URL is sent by forming a multipart/form-data post web request over https.

.PARAMETER URL
    The URL to be submitted to virustotal.com

.PARAMETER VerboseMode
    Switch parameter that will activate a verbose console output

.PARAMETER Intensive
    Switch to override the default limit of 4 requests of any nature per minute if you have the private API

.PARAMETER Proxy
    Set the proxy address to use (optional)

.PARAMETER ProxyCredential
    Set the credentials to be used with the proxy address (optional)
     
.LINK    
    https://p0w3rsh3ll.wordpress.com

.EXAMPLE
    Send-MaliciousURLSample -URL http://www.eicar.org/download/eicar.com.txt
    Submit a malicious URL to virustotal.com
     
.EXAMPLE
    "http://www.eicar.org/download/eicar.com.txt" | Send-MaliciousURLSample
    Submit a malicious URL to virustotal.com

.EXAMPLE
    "http://www.eicar.org/download/eicar.com.txt" | Send-MaliciousURLSample -Proxy "http://my.internal.proxy.address"
    Submit a malicious URL to virustotal.com using the internal http://my.internal.proxy.address

.NOTES    
    Name: Send-MaliciousURLSample
    Author: Emin Atac
    DateCreated: 24/02/2012

#>

} # end of function Send-MaliciousURLSample

Function Get-MaliciousURLReport
{
[cmdletbinding(DefaultParameterSetName='',SupportsTransactions=$false)]
param(
    [Parameter(Mandatory=$true,ParameterSetName='',ValueFromPipeline=$true,Position=0)]
    [system.array]$InputObject=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=1)]
    [System.Management.Automation.SwitchParameter]$VerboseMode = $false,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=2)]
    [System.Management.Automation.SwitchParameter]$Intensive = $false,
    
    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=3)]
    [System.URI]$Proxy=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=4)]
    [System.Management.Automation.PSCredential]$ProxyCredential=$null
)
# Store the start time
$BigStart = (Get-Date)
if ($APIkey -eq $null)
{
    Write-Host -ForegroundColor Gray -Object ("Define first a `$APIkey variable with the key you got from virustotal, see help")
    break
}
# Prepare a hashtable for splatting
$otherparams = @{}
if ($Proxy)
{
    $otherparams += @{Proxy = $Proxy}
    if ($ProxyCredential)
    {
       $otherparams += @{ProxyCredentials = $ProxyCredential}
    }
}
# Initialize empty arrays
$arResponses = @()
$arSamples = @()
# Make sure that the InputObject has all the properties we expect
foreach ($item in $InputObject)
{
    if ($item.Code -eq 1)
    {
        # If the item was indeed present and it could be retrieved it will be 1.
        $arSamples += $item
    } elseif ($item.Code -eq -2) {
        # If the requested item is still queued for analysis it will be -2.
        Write-Host -ForegroundColor Yellow -Object ("The item $($item.SubmittedURL) is still queued for analysis")
    } elseif ($item.Code -eq 0) {
        # if the item you searched for was not present in VirusTotal's dataset this result will be 0.
        Write-Host -ForegroundColor Red -Object ("The item $($item.SubmittedURL) was not found in the VT dataset")
    } else {
        Write-Host -ForegroundColor Red -Object ("Unknown response from API: $($item.Code)")
    }
}
# Initialize variables
$count = $i = 0
# Now that we are sure that we have only system.io.fileinfo objects in our arSamples array
$Start =  (Get-Date)
foreach ($item in $arSamples)
{
    # Show some progress activity...
    $i++
    Write-Progress -activity "Dealing with file: $($item.SubmittedURL)" -status "Percent added: " -PercentComplete (($i/$arSamples.Count)*100)
    # Get a random GUID
    $boundary = [System.Guid]::NewGuid().ToString();
$respbody = @"
--$boundary
Content-Disposition: form-data; name="apikey"

$APIkey
--$boundary
Content-Disposition: form-data; name="resource"

$($item.SubmittedURL)
--$boundary--
"@
# $($item.scanID)
# $respbody
    Write-Debug -Message $respbody
    if ($Intensive)
    {
        # Skip, as we don't care about API limits
        Write-Verbose -Message "Continuing as intensive switch was specified" -Verbose:$VerboseMode
    } else {
        $count++
        $End = New-Timespan $Start (Get-Date)
        if ($End -le (New-TimeSpan -Minutes 1))
        {
            # If the elapsed time if less than a minute, we'll check how many operations we did
            if ($count -le 4)
            {
                Write-Verbose -Message "Continuing as count is $count and the 1 minute limit was not reached yet" -Verbose:$VerboseMode
                # We did not reach yet the maximum of 4 operations per minute
            } else {
                # We have to wait as only 4 operations of any kind is allowed per minute
                $Waitfor = ((New-TimeSpan -Minutes 1) - $End).Seconds
                Write-Verbose -Message "Waiting for $Waitfor seconds as max count of $count reached and 1 minute not yet elapsed" -Verbose:$VerboseMode
                Start-Sleep -Seconds  $Waitfor
                # Reset the count and the start date
                $count = 1
                $Start = (Get-Date)
            } # end of less than 4 operations
        } else {
            # More than a minute elapsed, we can safely reset the count and the start
            $count = 1
            $Start = (Get-Date)
        } # end of less than a minute
    } # end of Intensive
    # Here we go !
    $webresponse = $null
    try
    {
        # Invoke-RestMethod returns a PSCustom object
        $webresponse = Invoke-RestMethod -Method Post -Uri ("https://www.virustotal.com/vtapi/v2/url/report") -ContentType "multipart/form-data; boundary=$boundary" -Body $respbody @otherparams -ErrorAction Stop
    } 
    catch
    {
        Write-Host -ForegroundColor Red -Object ("The following error occured while posting request: " + $_)
    }
    # Add the response to an array
    if ($webresponse -ne $null)
    {
        $fullURL = [system.URI]$webresponse.permalink
        $linkar = @($fullURL.Segments)
        $browsableURL = $fullURL.Scheme + "://" + $fullURL.Host + (-join ($linkar[0..($linkar.Count-2)]))
        # $browsableURL
        $arResponses += New-Object -TypeName PSObject -Property @{            
            ScanDate = [datetime]$webresponse.scan_date
            Total = $webresponse.total
            Positives = $webresponse.positives
            Resource = $webresponse.resource
            Code = $webresponse.response_code
            URL = $webresponse.url
            Link = $browsableURL
            Scans = $webresponse.scans
        }
        Start-Sleep -Seconds 2
        # We can convert it to JSON to display it if verbose mode was enabled
        if ($VerboseMode)
        {
            $webresponse | ConvertTo-Json
        }
    } else {
        Write-Host -ForegroundColor Red -Object ("The response of the web request is null for some reasons")
    } # end of if webresponse is null
} # end of foreach
# Calculate how much time elapsed
$BigEnd = New-Timespan $BigStart (Get-Date)                    
Write-Verbose -Message ("Operation completed in: $('{0}' -f $BigEnd)") -Verbose:$VerboseMode
# Finally return our array
return $arResponses
<#
    
.SYNOPSIS    
    Retrieve the report of a malicious URL sample already sent to virustotal
   
.DESCRIPTION  
    Retrieve the report of a previously submitted malicious URL by using the public API v2 of virustotal.com
    The report is retrieved by forming a multipart/form-data post web request over https.

.PARAMETER InputObject
    It represents a PSCustom object that has 3 properties: a filename, a resource and a response from the API when submitted

.PARAMETER VerboseMode
    Switch parameter that will activate a verbose console output

.PARAMETER Intensive
    Switch to override the default limit of 4 requests of any nature per minute if you have the private API

.PARAMETER Proxy
    Set the proxy address to use (optional)

.PARAMETER ProxyCredential
    Set the credentials to be used with the proxy address (optional)
     
.LINK    
    https://p0w3rsh3ll.wordpress.com

.EXAMPLE
    Send-MaliciousURLSample -URL http://www.eicar.org/download/eicar.com.txt | Get-MaliciousURLReport
    Submit a malicious URL to virustotal.com and retrieve its report
     
.EXAMPLE
    "http://www.eicar.org/download/eicar.com.txt" | Send-MaliciousURLSample | Get-MaliciousURLReport
    Submit a malicious URL to virustotal.com and retrieve its report

.NOTES    
    Name: Get-MaliciousURLReport
    Author: Emin Atac
    DateCreated: 24/02/2012

#>     
} # end of function Get-MaliciousURLReport

#endregion Functions

Export-ModuleMember -Function *

Get-FirefoxInfo

In less than 2 weeks, the Mozilla foundation released two security updates for both the ESR (Extended Support Release) and the ‘Release’ channel version of Firefox.

With all previous releases of Firefox, we used to check the version of firefox.exe to find out which version it was.

With Firefox 10.0 executable, we had a version 10.0.0.4412 for the ESR and 10.0.0.4411 for the release update channel but for firefox 10.0.1, we have both 10.0.1.4421 which doesn’t allow us to differentiate between the ESR and the release channel version anymore.

I’ve asked the Enterprise Working group and got the following answer from the Ben Hearsum

The only way to distinguish between the two versions these days is through the application.ini file. The SourceRepository field in the [App] section will be “http://hg.mozilla.org/releases/mozilla-esr10&#8221; for this line of ESR builds. (When we start the next line of ESR at Firefox 17 this will change, of course.)

The version you’re looking at isn’t used by us at all. The only thing we do is guarantee that it doesn’t go backwards.

Powershell, to the rescue… ๐Ÿ˜‰

#Requires -Version 3.0

<#
.SYNOPSIS    
    Get the channel and version of Mozilla Firefox
 
.DESCRIPTION  
    Get the channel and version of Mozilla Firefox
 
.PARAMETER ComputerName
    Non mandatory parameter, array of remote computernames
 
.PARAMETER Credential
    Non mandatory parameter, credential used to contact remote computer

.NOTES    
    Name: Get-FirefoxInfo
    Author: Emin Atac
    DateCreated: 19/02/2012
 
.LINK    
    https://p0w3rsh3ll.wordpress.com
 
.EXAMPLE    
    .\Get-FirefoxInfo.ps1

    Channel         Version
    -------         -------
    release         10.0
    
    Returns the channel and version of Mozilla Firefox found locally
.EXAMPLE    
    .\Get-FirefoxInfo.ps1 -ComputerName "PAR903","127.0.0.1"
    Returns the channel and version of Mozilla Firefox of the two remote computers

.EXAMPLE    
    .\Get-FirefoxInfo.ps1 -ComputerName "remoteComputer1","remoteComputer2" -Credential (Get-Credential) 
    Use the credentials you specified to retrieve the channel and version of Mozilla Firefox of the two remote computers
#>

[cmdletbinding(DefaultParameterSetName='',SupportsTransactions=$false)]
param(
    [Parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$true,Position=0)]
    [system.array]$ComputerName=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$true,Position=1)]
    [System.Management.Automation.PSCredential]$Credential = $null
)

# Build a hashtable for splatting
$otherparams = @{}
if ($credential)
{
    $otherparams += @{Credential = $Credential}
}
# Note to myself: both test-path and get-content support PScredential inputs

if ($ComputerName -ne $null)
{
    foreach ($computer in $ComputerName)
    {
        # Variable reset
        $osprop = $programfiles = $appinipath = $content = $sourcerep = $URL = $channel = $sourcever = $version = $MFOjb = $null
        # We need to get the target OS architecture and systemdrive
        try
        {
            $osprop = @(Get-WmiObject -Query "Select * FROM Win32_operatingsystem" -ComputerName $computer -ErrorAction Stop @otherparams)
        } 
        catch
        {
            # WMI was unable to retrieve the information
            switch ($_)
            {
                {$_.Exception.ErrorCode -eq 0x800706ba} { $reason =  "Unavailable (offline, firewall)" }
                {$_.CategoryInfo.Reason -eq 'UnauthorizedAccessException' } { $reason = "Access denied" }
                default { $reason  = $_.Exception.Message }
            }
            Write-Host -ForegroundColor Yellow  -Object "Failed to connect to WMI on $Computer because: $reason"
            # break
        } # end of catch
        
        if (($osprop.Count -ne 0) -and ($osprop -ne $null))
        {
            # Based on the OS architecture, we set the target program files directory
            if ($osprop[0].OSArchitecture -eq "64-bit")
            {
                $programfiles = "Program Files (x86)"
            } else {
                $programfiles = "Program Files"
            }
            # Now we are ready to build the full target path
            $appinipath = "\\" + $computer + "\" + ($osprop[0].SystemDrive -Replace ":","$") + "\" +  $programfiles + "\Mozilla Firefox\application.ini"
            # $appinipath
            if (Test-Path -Path $appinipath @otherparams)
            {
                try
                {
                    $content = Get-Content -Path $appinipath @otherparams
                } catch {
                    $reason  = $_.Exception.Message
                    Write-Host -ForegroundColor Yellow  -Object "Failed to use get-content against $computer because: $reason"
                }
                if ($content -ne $null)
                {
                    # Look for the SourceRepository into the application.ini file
                    $sourcerep = @($content | Select-String -Pattern "^SourceRepository=http")
    
                    if ($sourcerep.Count -ne 0)
                    {
                        # Cast the URL into a System.URI object and split its segments properties
                        $URL = ([System.URI](($sourcerep[0] -split "=")[1].ToString())).Segments[2]
                        if ($URL -eq "mozilla-release")
                        {
                            $channel = "release"
                        } elseif ($URL -match "mozilla-esr") {
                            $channel = "esr"
                        } else {
                            Write-Host -ForegroundColor Red -Object ("SourceRepository not recognised in $appinipath")
                            $channel = "Unknown"
                        }
                    } else {
                        Write-Host -ForegroundColor Red -Object ("SourceRepository not found in $appinipath")
                    }
    
                    # Look for the Version into the application.ini file
                    $sourcever = @($content | Select-String -Pattern "^Version=\d{2}\.\d{1}")
                    if ($sourcever.Count -ne 0)
                    {
                        $version = [system.version]($sourcever[0] -split "=")[1].ToString()
                    } else {
                        Write-Host -ForegroundColor Red -Object ("Version not found in $appinipath")
                        $version = "Unknown"
                    }
                    # Build a custom object
                    $MFobj = New-Object -TypeName PSObject -Property @{
                        Channel = $Channel
                        Version = $Version
                    }
                    # Return it
                    Write-Output -InputObject $MFobj
                } #end of if $content -ne $null
            } else {
                Write-Host -ForegroundColor Red -Object ("Cannot find a recent installed version of Firefox in $appinipath")
            } # end of test-path
        } # end of osprop is null
    } # end of foreach computer
} else {
    # No paramter has been passed, so we can use the new .Net 4 static property of system.environnemet for performance reasons compared to WMI
    if ([system.environment]::Is64BitOperatingSystem)
    {
        $programfiles = ${env:ProgramFiles(x86)}
    } else {
        $programfiles = $env:ProgramFiles
    }

    if (Test-Path -Path "$programfiles\Mozilla Firefox\application.ini")
    {
        $content = Get-Content -Path "$programfiles\Mozilla Firefox\application.ini"
    
        # Look for the SourceRepository into the application.ini file
        $sourcerep = @($content | Select-String -Pattern "^SourceRepository=http")
    
        if ($sourcerep.Count -ne 0)
        {
            # Cast the URL into a System.URI object and split its segments properties
            $URL = ([System.URI](($sourcerep[0] -split "=")[1].ToString())).Segments[2]
            if ($URL -eq "mozilla-release")
            {
                $channel = "release"
            } elseif ($URL -match "mozilla-esr") {
                $channel = "esr"
            } else {
                Write-Host -ForegroundColor Red -Object ("SourceRepository not recognised in your $programfiles\Mozilla Firefox\application.ini")
                $channel = "Unknown"
            }
        } else {
            Write-Host -ForegroundColor Red -Object ("SourceRepository not found in your $programfiles\Mozilla Firefox\application.ini")
        }
    
        # Look for the Version into the application.ini file
        $sourcever = @($content | Select-String -Pattern "^Version=\d{2}\.\d{1}")
        if ($sourcever.Count -ne 0)
        {
            $version = [system.version]($sourcever[0] -split "=")[1].ToString()
        } else {
            Write-Host -ForegroundColor Red -Object ("Version not found in your $programfiles\Mozilla Firefox\application.ini")
            $version = "Unknown"
        }
        # Build a custom object
        $MFobj = New-Object -TypeName PSObject -Property @{
            Channel = $Channel
            Version = $Version
        }
        # Return it
        Write-Output -InputObject $MFobj
    } else {
        Write-Host -ForegroundColor Red -Object ("Cannot find a recent installed version of Firefox in $programfiles")
    }
}

Get TimeZone

I’ve a daily security audit script that checks the timezone of our computers. This script actually queries the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation registry key of computers and checks their StandardName value. Since Windows Vista and Windows 7, this value doesn’t contain a string as it usually did on Windows XP. As you can see it now contains a resource.
regqueryTZ

To help me translate those resource back into strings, I could have used Tobias Weltner’s brillant Get-ResourceString function available on this page but I wanted to dig further and learn more about timezones.
Get-ResourceString

I know that there isn’t any simple way to set a timezone on a computer. Either you have a running computer and you should use the tzutil command or you have an offline image and you can use the dism command.
dism-tzutil
My goal wasn’t to set a timezone, but if you’re interested in performing this task, let me also mention that there’s a script available on the Technet script gallery that is a wrapper of the tzutil command called Set-TimeZone.ps1. Even more interesting, the DeploymentGuys posted a way to set a timezone in powershell on their blog.

To learn what’s behind the scene, I fired up a procmon and checked what the tzutil.exe /l command does. As you can see, it reads the “HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones” and its subkeys. I decided that in addition to the translation of resources, the second feature of my powershell script would read this key and dump the same output as the tzutil.exe /l command. Exploring the registry subkeys also helped me brought all the pieces together, as well as link these two goals and gave me an alternative to Tobias Weltener’s Get-ResourceString function.
procmon_tzutil

Along the learning path, I’ve discovered two useful links:

  • Marc van Orsouw, aka MOW The powershell guy, who shows how to use some methods associated with the .NET Framework 3.5 TimeZoneinfo class: more here on his blog
  • Thomas Lee who also shows on his blog how to use other methods associated with the .NET Framework 3.5 TimeZoneinfo class
    • …and I’ve also learned 3 things:

    • The ‘New-Object System.DateTime 2006, 3, 21, 2, 0, 0’ that Thomas Lee uses, has an ‘unspecified’ kind whereas you’ll get a ‘Kind’ property set to ‘Local’ if you use the Get-Date cmdlet to create a datetime object. This has some importance to use the ConvertTimeToUtc method of the .net TimeZoneInfo object.
      datetimeKind
    • There’s a “General Date Short Time (“g”) Format Specifier” that can be used to format datetimes object.

      # Method 1
      (Get-Date).ToString('dd/MM/yyyy HH:mm')
      # Method 2
      Get-Date -Format g
      # Method 3
      $date = Get-Date
      "{0:dd}/{1:MM}/{2:yyyy} {3:HH}:{4:mm}" -f $date, $date, $date, $date, $date
      # so 'g' is actually this:
      $host.CurrentCulture.DateTimeFormat.ShortDatePattern + " " + $host.CurrentCulture.DateTimeFormat.ShortTimePattern
      

      formatdatetime

    • How to use the ParameterSets in a script
      • Finally here’s my Get-TZ.ps1 script, enjoy ๐Ÿ™‚

        #Requires -Version 2.0
        
        <#
         
        .SYNOPSIS    
            Get either all timezones, the local timezone or timezone properties on remote computers
         
        .DESCRIPTION  
            Get either all timezones, the local timezone or timezone properties on remote computers
        
        .PARAMETER Local
            Shows the local timezone
         
        .PARAMETER All
            Show all timezones the same way the tzutil /l command does
        
        .PARAMETER Full
            Must be specified with the All parameter in order to show all timezones with 4 columns: CurrentTime,DisplayName,Id,TimeSpan
        
            CurrentTime: time at specified location formatted in dd/MM/yyyy HH:mm 
            DisplayName: (UTC +/- HH:mm) Location
            Id: the <time zone ID>, i.e., it's name
            TimeSpan: a timespan object of the timezone
        
        .PARAMETER FindResource
            Specifies the array of strings to look for
        
        .PARAMETER ComputerName
            Specifies the array of computername to use to read their timezone using WMI
        
        .PARAMETER Credential
            Specifies the credential to use to query remote computers
        
        .EXAMPLE    
            Get-TZ.ps1 -Local
        
            Id          : Romance Standard Time
            CurrentTime : 11/02/2012 16:27
            DisplayName : (UTC+01:00) Brussels, Copenhagen, Madrid, Paris
            MUI_Std     : @tzres.dll,-302
            MUI_Display : @tzres.dll,-300
            MUI_Dlt     : @tzres.dll,-301
            TimeSpan    : 01:00:00
            Dlt         : Romance Daylight Time
            Std         : Romance Standard Time
        
            Returns the local timezone as a PSCustomObject
        
        .EXAMPLE    
            Get-TZ.ps1 -All
            Retrieve all the timezones from the registry and display them the same way tzutil /l does.
         
        .EXAMPLE    
            Get-TZ.ps1 -All -Full
            Retrieve all the timezones from the registry and display them the same way tzutil /l does, with two addtional columns: CurrentTime and TimeSpan
         
        .EXAMPLE
            Get-TZ.ps1 "."
            Get the timezone of the local computer with WMI (the computername is the default parameter and it can be omitted)
         
        .EXAMPLE    
            .\Get-Tz.ps1 -Computername remotecomputer1,remotecomputer2
            Get the timezone of two remote computers with WMI
          
        .EXAMPLE    
            .\Get-Tz.ps1 -Computername remotecomputer1,remotecomputer2 -Credential (Get-Credential)
            Get the timezone of two remote computers with WMI using the specified prompter credential
        
        .EXAMPLE
            .\Get-Tz.ps1 -FindResource "@tzres.dll,-300"
        
            Id          : Romance Standard Time
            CurrentTime : 11/02/2012 16:33
            DisplayName : (UTC+01:00) Brussels, Copenhagen, Madrid, Paris
            MUI_Std     : @tzres.dll,-302
            MUI_Display : @tzres.dll,-300
            MUI_Dlt     : @tzres.dll,-301
            TimeSpan    : 01:00:00
            Dlt         : Romance Daylight Time
            Std         : Romance Standard Time
            
            Show all the timezones that have this specific resource.
        
        .NOTES    
            Name: Get-TZ
            Author: Emin Atac
            DateCreated: 11/02/2012
         
        .LINK    
            https://p0w3rsh3ll.wordpress.com
         
        #>
        
        [CmdletBinding(DefaultParameterSetName='GetRemoteTZ', SupportsTransactions=$false)]
        param(
           [Parameter(ParameterSetName='LocalTZ', Mandatory=$false, Position=0)]
            [System.Management.Automation.SwitchParameter]${Local},
        
            [Parameter(ParameterSetName='Tzutil', Mandatory=$false, Position=0)]
            [System.Management.Automation.SwitchParameter]${All},
        
            [Parameter(ParameterSetName='Tzutil', Mandatory=$false, Position=1)]
            [switch]${Full},
        
            [Parameter(ParameterSetName='Find', Mandatory=$true, Position=0)]
            [string[]]${FindResource},
        
            [Parameter(ParameterSetName='GetRemoteTZ', Mandatory=$true, Position=0)]
            [string[]]${Computername},
        
            [Parameter(ParameterSetName='GetRemoteTZ', Mandatory=$false, Position=1)]
            [System.Management.Automation.PSCredential]${Credential}
        )
        
        # Build a hashtable for splatting
        $otherparams = @{}
        if ($credential)
        {
            $otherparams += @{Credential = $Credential}
        }
        
        # Read the main key where all time zones are stored
        $root = Get-Childitem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
        
        # Get the current local date time
        $mydatetime = [datetime]::Now
        
        # Initialize an empty array
        $allTZobj = @()
        
        # Loop into each subkey representing a timezone
        foreach ($i in $root)
        {
            # Write-Verbose -Message "Dealing with $($i.Name)" -Verbose:$true
        
            # Build the subkey path    
            $subkey = Join-Path -Path $i.PSParentPath -ChildPath $i.PSChildName
        
            # Get its properties
            $subkeyproperties = Get-ItemProperty -Path $subkey
        
            # There isn't any IsObsolete value, ie, it's null, then it's what we are looking for
            if ($subkeyproperties.IsObsolete -eq $null)
            {
                $Object = $null
                # Build an object to store all the properties we are interested in
                $Object = New-Object -TypeName PSObject -Property @{
                    Id = $i.PSChildName
                    Std = $subkeyproperties.Std
                    TimeSpan = [System.TimeZoneInfo]::FindSystemTimeZoneById($i.PSChildName).BaseUtcOffset
                    CurrentTime = ([System.TimeZoneinfo]::ConvertTime($mydatetime,([System.TimeZoneInfo]::FindSystemTimeZoneById($i.PSChildName)))).tostring('g')
                    DisplayName = $subkeyproperties.Display
                    Dlt = $subkeyproperties.Dlt
                    MUI_Display = $subkeyproperties.MUI_Display
                    MUI_Std = $subkeyproperties.MUI_Std
                    MUI_Dlt = $subkeyproperties.MUI_Dlt
                    }
            # Add this new object to our main array
            $allTZobj += $Object
            }
        }
        
        switch ($PsCmdlet.ParameterSetName)
        { 
            Tzutil {
                # If the additional Full switch was specified, display two additional columns
                if($PSBoundParameters['Full'])
                {
                    $allTZObj | Select-Object -Property CurrentTime,DisplayName,Id,TimeSpan | Sort-Object -Descending:$false -Property TimeSpan
                } else {
                    # Display the same output as tzutil /l
                    # Use timespan to sort like tzutil /l but only display the same 2 properties as tzutil /l
                $allTZObj | Sort-Object -Descending:$false -Property TimeSpan | Select-Object -Property DisplayName,Id
                }
            } # end of Tzutil
        
            Find {
                # Parse our array and find any timezone that matches the string we specified
                foreach ($Resource in $FindResource)
                {
                    $allTZObj | Where-Object { ($_.MUI_Std -eq $Resource) -or ($_.MUI_Display -eq $Resource) -or ($_.MUI_Dlt -eq $Resource)}
                }
            } # end of Find
            
            LocalTZ {
                # Parse our array and find the current time on the local computer
                $allTZObj | Where-Object { $_.Displayname -eq ([System.TimeZoneInfo]::Local).ToString()}
            } # end of LocalTZ
            
            GetRemoteTZ {
                # Read the timezone of remote computers using WMI and parse our array to display their timezone properties
                foreach ($Computer in $ComputerName)
                {
                    # Define the  HKLM Constant and the Key we are looking for
                    $HKLM = 2147483650
                    $Key = "SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
                    try
                    {
                        $result = Invoke-WmiMethod -Path "ROOT\DEFAULT:StdRegProv" -ComputerName $Computer -Name GetStringValue -ArgumentList $HKLM,$Key,"StandardName"  -ErrorAction Stop @otherparams
                    }
                    catch
                    {
                        # WMI was unable to retrieve the information
                        switch ($_)
                        {
                            {$_.Exception.ErrorCode -eq 0x800706ba} { $reason =  "Unavailable (offline, firewall)" }
                            {$_.CategoryInfo.Reason -eq 'UnauthorizedAccessException' } { $reason = "Access denied" }
                            default { $reason  = $_.Exception.Message }
                        }
                        Write-Host -ForegroundColor Yellow  -Object "Failed to connect to WMI on $Computer because: $reason"
                    }
                    if ($result.sValue -ne $null)
                    {
                        $allTZObj | Where-Object { ($_.MUI_Std -eq $result.sValue) -or ($_.MUI_Display -eq $result.sValue) -or ($_.MUI_Dlt -eq $result.sValue)}
                    }
                }
            } # end of GetRemoteTZ
        } # end of switch
        

        Working with Applocker and Filepath Rules

        A few days ago, I’ve added some filepath rules concerning new servers put into production to my Applocker GPO.
        I decided to quickly check if all the files paths were correct. Manually typing filepath is prone to errors. To detect typos or servers currently being unavailable, I did:

            
        
        Import-Module -Name "GroupPolicy"
        Import-Module -Name "Applocker"
        
        # Read the GPO and store it as an XML object
        $GPO = [xml](Get-AppLockerPolicy -Ldap ("LDAP://" + (Get-GPO -Name "Computers Parameters").path) -Domain -XML)
        
        (($GPO.AppLockerPolicy.RuleCollection | Where-Object { $_.Type -eq "Exe"}).FilePathRule) | ForEach-Object {
        
                $string = $_.Conditions.FilePathCondition.Path
        
                # Use a regular expression that represents 
                # \\servername\share\* or \\servername\share$\* or
                # \\server.fqdn.domain.suffix\share\* or \\server.fqdn.domain.suffix\share$\*
                $pattern = '^\\\\\b([A-Za-z0-9_\.\-]+)\\\b([A-Za-z0-9_\.\-]+(\$)?)\\\*$'
                if ($string -match $pattern)
                {
                        if (Test-Path -Path ($string -replace "\*","") -ErrorAction SilentlyContinue)
                        {
                            Write-Host -ForegroundColor Green -Object ($string + " -> ok")
                        } else {
                            Write-Host -ForegroundColor Red -Object ($string + " -> not resolved !")
                        }
                } else {
                    Write-Host -ForegroundColor Yellow -Object ($string + " -> ignored / not verified")
                }
            }    
        

        and got the following result displayed
        applocker filepath rules