Case of the Slow Downloads Folder

In Windows 10 on Surface Pro 4, performance is mostly pretty quick. But the first time opening Downloads folder after a restart it was very slow, taking 11 seconds just to display 114 files and 35 directories.

To confirm issue was repeatable I tried rebooting system 3 times and each time the issue occurred when browsing Downloads folder.

Using Process Monitor I took a trace with filter

  • Path Includes Downloads

From this I noticed a very high number of ReadFile events coming from Explorer.exe

Most files in the directory had no ReadFile events related to them. They looked like this:

image

However some files had hundreds of ReadFile events:

image

Right clicking a single ReadFile event and viewing Properties we can view stack whicfh may give us some clues us to what is causing this.

image

Interestingly this ReadFile operation, although taking some time, was only occurring on a few files, some DOCX, PPTX, and EXE file.

However this didn’t happen for all files of those type in the Downloads folder.

With a ProcMon filter set to

  • Process Name is Explorer.exe
  • Operation is ReadFile

I found the six files this operation occurred on

I removed those files from the folder

Restarted the machine. Downloads folder opened instantly.

Restarted the machine again, Downloads folder opened instantly again.

So I copied back the “bad files” into the Downloads folder…

The issue could no longer be reproduced…

I took my original ProcMon file from when Issue was occurring, and selected a top ReadFile event from affected machine

I then reset the filter to show all events, and could see that Windows Antimalware (MsMpEng.exe) had scanned the file just before these ReadFile events

image

After I had moved the files, Explorer behaved differently.

When I filtered on

  • Process Name is MsMpEng.exe
  • Path Contains Downloads

Now when I clicked Downloads folder in Explorer, these files were no longer being scanned

image

However as I started to scroll through the directory listing they did get scanned sometimes, seemingly randomly, but without any noticeable performance impact..image

Posted in Performance, ProcMon, Window, Windows 10 | Tagged , , | Leave a comment

Retrieve Time Machine Password Last Reset via PowerShell

In diagnosing a machine lockout issue I wanted to find the time that:

  • Machine Password was last set in Active Directory
  • Machine Password was last set locally

By default when a machine has a healthy connection to Active Directory you should see the following:

  • Local and Active Directory Times should match (May be out by a few seconds)
  • The time should not be older than 30 days (Unless default max. password age has been modified from 30 days)

The script can be downloaded here, instructions for use contained in the script.

https://onedrive.live.com/redir?resid=93A8E9D387076121!23829&authkey=!AJXBw6m_bmSSRko&ithint=file%2czip

# Get-MachinePasswordLastSetDate by Malcolm McCaffery (@chentiangemalc)
# https://chentiangemalc.wordpress.com
#
#
# Retrieves the date/time Windows machine account was last set
# 
# For Location LocalMachine = Must run as local Administrator
# For Location ActiveDirectory = Must run as, or specify credential, with appropriate Active Directory access
#
# Usage examples:
#
# Get-MachinePasswordLastSetDate -Location ActiveDirectory
# Get-MachinePasswordLastSetDate -Location LocalMachine
# Get-MachinePasswordLastSetData -Location ActiveDirectory -ComputerName server01
# Get-MachinePasswordLastSetData -Location ActiveDirectory -ComputerName server01 -Server DC02
# Get-MachinePasswordLastSetData -Location ActiveDirectory -ComputerName server01 -Server DC02 -Domain other.domain.local -Credential (Get-Credential)

Set-StrictMode -Version Latest

Add-Type -TypeDefinition @'
public enum AccountLocation {
    ActiveDirectory,
    LocalMachine
}
'@

Function Get-MachinePasswordLastSetDate
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        [AccountLocation]$Location
    )

    DynamicParam 
    {
        $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        
        if ($Location -eq 'ActiveDirectory')
        {
            # COMPUTERNAME PARAMETER
            $computerAttributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute]
            $computernameAttribute = New-Object System.Management.Automation.ParameterAttribute
            $computernameAttribute.Mandatory = $false
            $computernameAttribute.HelpMessage = "The name of computer object to search for in Active Directory"
            $computerAttributeCollection.Add($computernameAttribute)

            #add our paramater specifying the attribute collection
            $computernameParam = New-Object System.Management.Automation.RuntimeDefinedParameter('ComputerName', [string], $computerAttributeCollection)
 
            #expose the name of our parameter
            $paramDictionary.Add('ComputerName', $computernameParam)

            # CREDENTIAL Parameter
            $credentialAttributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute]
            $credentialAttribute = New-Object System.Management.Automation.ParameterAttribute
            $credentialAttribute.Mandatory = $false
            $credentialAttribute.HelpMessage = "Specifies a user account that has permission to perform this action. The default is the current user."
            $credentialAttributeCollection.Add($credentialAttribute)

            #add our paramater specifying the attribute collection
            $credentialParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Credential', [PSCredential], $credentialAttributeCollection)
 
            #expose the name of our parameter
            $paramDictionary.Add('Credential', $credentialParam)

            # SERVER Parameter
            $serverAttributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute]
            $serverAttribute = New-Object System.Management.Automation.ParameterAttribute
            $serverAttribute.Mandatory = $false
            $serverAttribute.HelpMessage = "Specifies the domain controller to use."
            $serverAttributeCollection.Add($serverAttribute)

            #add our paramater specifying the attribute collection
            $serverParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Server', [String], $serverAttributeCollection)
 
            #expose the name of our parameter
            $paramDictionary.Add('Server', $serverParam)

            # DOMAIN parameter
            $domainAttributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute]
            $domainAttribute = New-Object System.Management.Automation.ParameterAttribute
            $domainAttribute.Mandatory = $false
            $domainAttribute.HelpMessage = "Specifies the FQDN of domain to use."
            $domainAttributeCollection.Add($domainAttribute)

            #add our paramater specifying the attribute collection
            $domainParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Domain', [String], $domainAttributeCollection)
 
            #expose the name of our parameter
            $paramDictionary.Add('Domain', $domainParam)


        }

        return $paramDictionary
    }

    Process 
    {
        $result = $null
        switch ($Location)
        {
            "ActiveDirectory"
            {
                $computerName = $paramDictionary["ComputerName"].Value
                $domain = $paramDictionary["Domain"].Value
                $credential = $paramDictionary["Credential"].Value
                $server = $paramDictionary["Server"].Value

                if ($computerName -eq $null) { $computerName = $env:COMPUTERNAME }

                # EASY WAY
                #$computer = Get-ADComputer -Identity $ComputerName -Credential $credential -Server $server -Properties "PasswordLastSet"
                #$result = $computer.PasswordLastSet

                # Using ADSI to use on machines without ActiveDirectory PowerShell Module
                $Searcher = New-Object -TypeName System.DirectoryServices.DirectorySearcher -ErrorAction Stop
                $Searcher.Filter = "(&(objectCategory=Computer)(name=$ComputerName))"
                $Searcher.SizeLimit = 0
                $Searcher.SearchRoot = ([ADSISearcher]"").SearchRoot.Path
                if ($server -ne $null)
                {
                    $Searcher.SearchRoot = [String]::Format("LDAP://{0}/{1}",$server,([ADSISearcher]"").SearchRoot.Path.Substring(7))
                }
                if ($domain -ne $null)
                {
                    [String]$DomainDN = "LDAP://"
                    if ($server -ne $null) { $DomainDN += $server + "/" }

                    $DNSArray = $Domain.Split('.')
                    for ($x = 0; $x -lt $DNSArray.Length - 1 ; $x++) 
                    { 
                        $DomainDN += "DC=$($DNSArray[$x]),"
                    } 
                    $DomainDN += "DC=$($DNSArray[$DNSArray.Length-1])"
    
                    $Searcher.SearchRoot = $DomainDN
                }

                if ($credential -ne $null) 
                {
                    $Domain = New-Object -TypeName System.DirectoryServices.DirectoryEntry 
                        -ArgumentList $DomainDN,$($Credential.UserName),$($Credential.GetNetworkCredential().password) 
                    $Searcher.SearchRoot = $Domain
                }
                    
                $computer = $searcher.FindOne()
                $pwdLastSetTime = $computer.Properties["pwdLastSet"][0]
                $result = [DateTime]::FromFileTime($pwdLastSetTime)

            }

            "LocalMachine" 
            {
                # Scheduled Task Used To Access Registry key as SYSTEM account
                $taskName ="ExtractMachineSecrets"
                $taskDescription = "Extract Machine Policy Secrets"
                $regKey = "HKLM\SECURITY\Policy\Secrets\`$MACHINE.ACC\CupdTime"
                
                # the number of attempts to wait for scheduled task output
                $maxTries = 10
                
                $fileName = [System.IO.Path]::GetTempFileName()
                if (Test-Path $filename) { Remove-Item $filename -Force }

                # must run as SYSTEMfs to access these registry keys
                # even local Administrator does not have access
                $principal = New-ScheduledTaskPrincipal -LogonType ServiceAccount -RunLevel Highest -UserId "SYSTEM"
    
                # use reg command line tool to export required registry data
                $action = New-ScheduledTaskAction -Execute "reg.exe" -Argument ([String]::Format('export "{0}" "{1}"',$regKey,$filename))
               
                # must apply these settings or task won't run on laptop running on battery
                $settings = New-ScheduledTaskSettingsSet -DontStopIfGoingOnBatteries -AllowStartIfOnBatteries 
    
                # create scheduled task
                $task = Register-ScheduledTask -TaskName $taskName -Description $taskDescription -Action $action -Principal $principal -Settings $settings -Force
                $start = Start-ScheduledTask -TaskName $taskName

                $counter = 0
                while ($counter -lt $maxTries)
                {
                    if (Test-Path $filename) { break }
                    Start-Sleep -Milliseconds 500
                    $counter++
                }

                if (!(Test-Path $fileName))
                {
                    Throw "$fileName not found!"
                }
                else
                {
                    $content = Get-Content $fileName
    
                    # extract comma separated hex values from reg file on line 3
                    $hexData = $content[3].Substring(9)

                    # byte array to store the hex values
                    [byte[]]$bytes = @()
                    for ($i = 0; $i -lt 8; $i++)
                    {
                        $bytes += [Convert]::ToInt32($hexData.Split(",")[$i],16)
                    }

                    # convert bytes into Int64
                    $time = [System.BitConverter]::ToInt64($bytes,0)
        
                    # convert Int64 to DateTime
                    $result = [DateTime]::FromFileTime($time)
                }

                Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
                Remove-Item  $fileName -Force
            }
       
        }
        return $result
    }
}

Posted in .NET, Active Directory, PowerShell | Tagged | Leave a comment

Using PowerShell + Word Interop to Extract Tables from PDFs

I needed to extract some tables from PDFs. If you’ve ever done this before you realise PDF format doesn’t really contain table information, it just contains lines and positions of text. Testing out Tabula http://tabula.technology/ which worked reasonably well, but didn’t handle tables split across pages.

So I used PowerShell and Word Interop with Microsoft Office 2016 to extract the tables into CSV files.

This script will extract every table into an individual CSV file named after the PDF. i.e. Document.pdf will produce Document1.csv, Document2.csv etc

Currently header names are not taken from the tables, and are instead labelled Column1, Column2, etc. This is due to the fact I was working with a lot of tables that didn’t have header names.

The script can be downloaded here:

https://onedrive.live.com/redir?resid=93A8E9D387076121!23690&authkey=!ADJ_3Kn6-tMBO4Q&ithint=file%2czip

# USAGE
# Extract-PdfTables -FileName <PDF FILE>
# Extracts a single CSV in same directory as PDF file
# For every table found in the PDF file
# PDFFile1.CSV, PDFFile2.CSV etc

Set-StrictMode -Version Latest

# function from http://stackoverflow.com/questions/5544844/how-to-call-a-complex-com-method-from-powershell
Function Invoke-NamedParameter {
    [CmdletBinding(DefaultParameterSetName = "Named")]
    param(
        [Parameter(ParameterSetName = "Named", Position = 0, Mandatory = $true)]
        [Parameter(ParameterSetName = "Positional", Position = 0, Mandatory = $true)]
        [ValidateNotNull()]
        [System.Object]$Object
        ,
        [Parameter(ParameterSetName = "Named", Position = 1, Mandatory = $true)]
        [Parameter(ParameterSetName = "Positional", Position = 1, Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]$Method
        ,
        [Parameter(ParameterSetName = "Named", Position = 2, Mandatory = $true)]
        [ValidateNotNull()]
        [Hashtable]$Parameter
        ,
        [Parameter(ParameterSetName = "Positional")]
        [Object[]]$Argument
    )

    end {  ## Just being explicit that this does not support pipelines
        if ($PSCmdlet.ParameterSetName -eq "Named") {
            ## Invoke method with parameter names
            ## Note: It is ok to use a hashtable here because the keys (parameter names) and values (args)
            ## will be output in the same order.  We don't need to worry about the order so long as
            ## all parameters have names
            $Object.GetType().InvokeMember($Method, [System.Reflection.BindingFlags]::InvokeMethod,
                $null,  ## Binder
                $Object,  ## Target
                ([Object[]]($Parameter.Values)),  ## Args
                $null,  ## Modifiers
                $null,  ## Culture
                ([String[]]($Parameter.Keys))  ## NamedParameters
            )
        } else {
            ## Invoke method without parameter names
            $Object.GetType().InvokeMember($Method, [System.Reflection.BindingFlags]::InvokeMethod,
                $null,  ## Binder
                $Object,  ## Target
                $Argument,  ## Args
                $null,  ## Modifiers
                $null,  ## Culture
                $null  ## NamedParameters
            )
        }
    }
}


Function Extract-PdfTables
{
    param([string]$FileName)

    Add-Type -Assembly "Microsoft.Office.Interop.Word"

    $item = Get-ChildItem $filename
    $baseFile = [System.IO.Path]::Combine($item.DirectoryName,$item.BaseName)

    $word = New-Object -ComObject Word.Application 
    $word.Visible = $true 

    # check Word Version, required for PDF conversion capability
    if ($word -eq $null -or $word.Version -lt 16)
    {
        Throw "Requires Microsoft Word 2016 or later"
    }

    # disable convert PDF warning prompt
    Set-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Office\16.0\Word\Options -Name DisableConvertPdfWarning -Value 0x1
    
    # open PDF with Microsoft Word
    $doc = Invoke-NamedParameter $word.Documents "Open" @{ "FileName" = $filename ; "AddToRecentFiles" = $false ; "ConfirmConversions" = $false ; "ReadOnly" = $true ; "Visible"=$false } 

    $tableCount = 0
    $lastTable = $null
    ForEach ($table in $doc.Tables)
    {
        # tables split across pages in PDFs will show up as separate tables
        # we will try to auto-detect if table is continuing across page
        # in this case $newTable will be set to false
        $newTable = $true
        
        # check for continuing table
        if ($lastTable -ne $null)
        {
            # Get the range between previous table and current table
            $RangeBetweenTables = $lastTable.Range
            $RangeBetweenTables.Collapse([Microsoft.Office.Interop.Word.WdCollapseDirection]::wdCollapseEnd)
            $RangeStartOfNextTable = $table.Range
            $RangeStartOfNextTable.Collapse([Microsoft.Office.Interop.Word.WdCollapseDirection]::wdCollapseStart)
            $RangeBetweenTables.End = $RangeStartOfNextTable.Start -1

            # Count lines separating tables
            $lineCount = $RangeBetweenTables.ComputeStatistics([Microsoft.Office.Interop.Word.WdStatistic]::wdStatisticLines)
            if ($lastTable.Columns.Count -eq $table.Columns.Count -and $lineCount -eq 0)
            {
                $newTable = $false
            }
        }

        $lastTable = $table
    
        if ($newTable)
        {
            $rowOffset = 0
            $tableCoun
            "Table # $tableCount"
            $dataTable = New-Object System.Data.DataTable "Table$tableCount"

            # build the columns for our table - these will get default names Column1,Column2
            For ($i = 0; $i -le $table.Columns.Count; $i++ )
            {
                $dataTable.Columns.Add()
            }
        }
        else
        {
            $rowOffset = $dataTable.Rows.Count - 1
        }

        For ($i = 0; $i -le $table.Rows.Count; $i++)
        {
            [void]$dataTable.Rows.Add() 
        }


        ForEach ($row in $table.Rows)
        {
            ForEach ($cell in $row.Cells)
            {
                # remove some unprintable characters that may be at end of cell value
                $text = $cell.Range.Text.Trim()
                $text = $text.Replace([string][char]13,"")
                $text = $text.Replace([string][char]7,"")

                $dataTable.Rows[$rowOffset + $cell.RowIndex -1][$cell.ColumnIndex-1] = $text
            }
        }

        $dataTable | Export-Csv -NoTypeInformation -Path "$baseFile$tableCount.csv"
    }
    $doc.Close
    $word.Quit
}
Posted in Office, PowerShell | Tagged | Leave a comment

Case of the Unwanted Chrome Search Modification

A friend’s machine had the Google Chrome home page set to piesearch.com

image

But there is no homepage set in settings.

image

Checking the Chrome Shortcut in Start Menu we can see it’s been modified, so we remove the piesearch parameter. This setting needs Administrative privilege to change because Chrome was installed to “All Users”

image

That fixed the homepage, but now searching in the omnibox resulted in searches from coldsearch.com NOT google which had been set before:

image

When settings were opened it was advised this was set by the administrator, and the default could not be changed.

I did a ProcMon trace and used filter Details Contains coldsearch to identify registry keys with the value coldsearch but got no hits.

So I changed ProcMon filter to

  • Process Name is Chrome.exe
  • Result is Success

Then using ProcMon –> Tools | Summary could quickly identify the folders Chrome accessed files from:

image

We could see app settings loading from C:\Users\<Username>\AppData\Local\Google\Chrome

However renaming this folder didn’t remove the search setting.

To find where the setting might be stored I used SearchMyFiles http://www.nirsoft.net/utils/search_my_files.html )

And limited the search to those folders found with ProcMon to speed it up:

image

This machine was not supposed to have any Group Policy settings, so I removed the Registry.pol file, which represents Local Machine Group Policy.

Using the tool here https://sdmsoftware.com/gpoguy/free-tools/library/registry-pol-viewer-utility/ we could view the .pol file (Requires .NET 3.5, Free registration required to download)

image

With the Registry.Pol file removed, Chrome was back to Normal.

Finally we cleaned up the Mozilla Firefox directory settings by backing up, then deleting the files under C:\Users\<username>\AppData\Roaming\Mozilla\Firefox\Profiles

Microsoft Edge had not been affected, and Internet Explorer settings modification had been blocked by Windows Defender.

Posted in Group Policy, Internet Explorer, ProcMon, Sys, SysInternals | Leave a comment

Case of the Domain Join Failure Part III

Continuing the series on domain join failures from https://chentiangemalc.wordpress.com/2012/09/08/case-of-the-domain-join-failure-iiobject-already-exists/

A Windows 10 device had been joined to domain; removed from domain. The computer account in AD had been deleted and recreated. However attempting to manually join domain failed.

The most useful error message was achieved by using the legacy Control Panel’s System Properties  Network ID… button to join the computer to the domain. This provided more detailed error then the new Windows 10 Rename your PC or join a domain in System Settings.

image

In attempting to join domain au.my.internal domain we got an error referencing the old NetBIOS domain name MYDOMAIN. This was no longer accessible as WINS servers had been decommissioned. Note: During the domain join process we made no reference to the old NetBIOS name

The domain name “MYDOMAIN” might be a NetBIOS domain name.  If this is the case, verify that the domain name is properly registered with WINS.

If you are certain that the name is not a NetBIOS domain name, then the following information can help you troubleshoot your DNS configuration.

The following error occurred when DNS was queried for the service location (SRV) resource record used to locate an Active Directory Domain Controller (AD DC) for domain “MYDOMAIN”:

The error was: “DNS name does not exist.”
(error code 0x0000232B RCODE_NAME_ERROR)

The query was for the SRV record for _ldap._tcp.dc._msdcs.MYDOMAIN

Common causes of this error include the following:

– The DNS SRV records required to locate a AD DC for the domain are not registered in DNS. These records are registered with a DNS server automatically when a AD DC is added to a domain. They are updated by the AD DC at set intervals. This computer is configured to use DNS servers with the following IP addresses:

10.46.7.71
10.172.96.68
10.172.19.82

– One or more of the following zones do not include delegation to its child zone:

MYDOMAIN
. (the root zone)

Unfortunately attempting a packet capture using netsh trace start capture=yes report=yes failed to capture any network traffic, so I installed WireShark and by filtering on dns found the following SRV records were requested:

_gc._tcp.my.internal
_kerberos._tcp.dc._msdcs.au.my.internal
_ldap._tcp.dc._msdcs.au.my.internal
_ldap._tcp.dc._msdcs.MYDOMAIN
_ldap._tcp.dc._msdcs.MYDOMAIN.au.my.internal

All were successful except those containing MYDOMAIN

Why was Windows attempting to use the NetBIOS name? I have no idea at this point, However we did join the domain successfully using Windows 10 in-built offline domain join command djoin

From an elevated cmd prompt I ran the following:

runas /user:au.my.internal\chentiangemalc /netonly cmd.exe

And provided the password as required. This allowed us to launch a cmd line with domain credentials for network purposes, without having to be joined to the domain.

To provision the machine onto the domain:

djoin /PROVISION /DOMAIN au.my.internal /MACHINE %COMPUTERNAME% /REUSE /SAVEFILE C:\support\djoin

(Using /REUSE because the computer account had been precreated)

Finally the command to actually join the machine to the domainL

djoin /REQUESTODJ /LOADFILE C:\support\djoin /WINDOWSPATH %WINDIR% /LOCALOS

After a reboot the djoin command had been successful in joining the machine to the domain…when all the Windows in-built GUI options had failed. Yet another reason to be familiar with the CLI…

Posted in Active Directory, Windows 10 | Tagged | 1 Comment

Case of the Network Name That Couldn’t Be Found

Continuing the series on .NET patching from https://chentiangemalc.wordpress.com/2015/09/03/case-of-the-object-is-not-set-to-an-instance-of-an-object-net-patching/

An application when moved from XP to Windows 8.1 started reporting error The network name cannot be found.

image

A dump file was taken at this point with procdump –ma option (http://live.sysinternals.com/procdump.exe )

Because this is 32-bit .NET 2.0 process, we load in x86 WinDbg and load .NET 2.0 SOS extension:

 

0:000> .load C:\Windows\Microsoft.NET\Framework\v2.0.50727\SOS.dll
0:000> .cordll -ve -u -l
CLRDLL: C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscordacwks.dll:2.0.50727.5485 f:0
doesn’t match desired version 2.0.50727.8009 f:0
CLRDLL: Unable to find ” on the path
Cannot Automatically load SOS
CLRDLL: Loaded DLL g:\symbols\mscordacwks_x86_x86_2.0.50727.8009.dll\53A11D015b0000\mscordacwks_x86_x86_2.0.50727.8009.dll
CLR DLL status: Loaded DLL g:\symbols\mscordacwks_x86_x86_2.0.50727.8009.dll\53A11D015b0000\mscordacwks_x86_x86_2.0.50727.8009.dll
0:000> !pe
Exception object: 02651d00
Exception type: System.ComponentModel.Win32Exception
Message: The network name cannot be found
InnerException: <none>
StackTrace (generated):
    SP       IP       Function
    0038EB74 6143C043 System_ni!System.Diagnostics.Process.StartWithShellExecuteEx(System.Diagnostics.ProcessStartInfo)+0x267
    0038EBA4 6143B1DD System_ni!System.Diagnostics.Process.Start()+0x39
    0038EBB4 6143AEAA System_ni!System.Diagnostics.Process.Start(System.Diagnostics.ProcessStartInfo)+0x32
    0038EBC4 6143AF1C System_ni!System.Diagnostics.Process.Start(System.String)+0x20
    0038EBCC 05853E67 wds!StreamlineUI.MainForm.DisplayImage(System.String)+0x7f

StackTraceString: <none>
HResult: 80004005

From dumping stack objects, it seems like the file path has too many double back slashes

0:000> !dso
OS Thread Id: 0x1e3c (0)
ESP/REG  Object   Name
0038ea70 02291198 System.String   
0038ea74 02651fbc System.String    The network name cannot be found
0038ea88 025add10 System.Windows.Forms.ControlBindingsCollection
0038eadc 02651fbc System.String    The network name cannot be found
0038eb20 025add10 System.Windows.Forms.ControlBindingsCollection
0038eb24 02651fbc System.String    The network name cannot be found
0038eb28 025fceb0 System.String    \\server01\\Images\\WDS\\FaxImages\\2015\09\17\104510_Fax_FILE01_0309.tif
0038eb48 02291198 System.String   
0038eb4c 02293be0 StreamlineUI.MainForm
0038eb8c 025add10 System.Windows.Forms.ControlBindingsCollection
0038eb98 025fd174 System.Diagnostics.ProcessStartInfo
0038ebb4 025fceb0 System.String    \\server01\\Images\\WDS\\FaxImages\\2015\09\17\104510_Fax_FILE01_0309.tif
0038ebb8 025fceb0 System.String    \\server01\\Images\\WDS\\FaxImages\\2015\09\17\104510_Fax_FILE01_0309.tif
0038ebcc 025fceb0 System.String    \\server01\\Images\\WDS\\FaxImages\\2015\09\17\104510_Fax_FILE01_0309.tif
0038ebd0 02293be0 StreamlineUI.MainForm

Further investigation identified the fact that these filenames came from a database, and had already been stored in this format. In Windows XP this faulty file paths didn’t cause a problem, the images still opened OK in Internet Explorer which had been configured to be default viewer for TIF files.

Without write access to the DB, and software developer unable to make a patch I worked in a simple patch. This time instead of developing the patch in MSIL I created a C# Class Library (DLL) with the following code:

 

using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; namespace DotNetPatches { public static class FilePaths { public static string Fix(string path) { try { return Regex.Replace(path, @"(?!^)\\\\", @"\"); } catch { return path; } } } }

Now we decompile our broken EXE with ILDASM and open the IL file for editing. At beginning of .IL file we add the reference to our DLL

.assembly extern DotNetPatches { .ver 1:0:0:0 }

Using .NET reflector we had identified get_IMAGEPATH as the method responsible for retrieving our faulty file path, so we patch it to fix the value it returns by inserting a call to our Fix function.

.method public hidebysig specialname instance string get_IMAGEPATH() cil managed { // Code size 12 (0xc) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox StreamlineUI.MainForm::txtImageURI IL_0006: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text() call string [DotNetPatches]DotNetPatches.FilePaths::Fix(string) IL_000B: ret } // end of method MainForm::get_IMAGEPATH

 
After recompiling with ilasm this patched application now works fine in Windows 8.1

Posted in .NET, AppCompat, Patching | Tagged | Leave a comment

Case of the Win10 Windows Performance Recorder Trace That Wouldn’t Stop

Disk space kept rapidly disappearing on my C: drive…

Using WinDirStat (https://windirstat.info/) it was evident majority was taken up by 40GB+ ETL files in my TEMP folder.

Running wpr –status showed no traces running

image

Searching registry with RegScanner ( http://www.nirsoft.net/utils/regscanner.html ) for the filename of the filename WPR_initiated_WprApp_WPR Event Collector.etl we found the culprits under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\WMI\AutoLogger under keys

  • WPR_initiated_WprApp_WPR Event Collector
  • WPR_initiated_WprApp_WPR System Collector

Checking the GUID subkeys and looking at provider name we could see it was monitoring the following providers:

  • Microsoft-Antimalware-Engine
  • Microsoft-Windows-DNS-Client
  • Microsoft-Windows-URLMon
  • Microsoft-Windows-Shell-Core
  • Microsoft-Windows-NCSI
  • Microsoft-Windows-Kernel-Power
  • Microsoft-Windows-WinINet
  • Microsoft-JScript
  • Microsoft-IEFRAME
  • Microsoft-Windows-BootUX
  • Microsoft-Antimalware-Service
  • IE7
  • Microsoft-Windows-Win32k
  • Microsoft-Antimalware-RTP
  • Microsoft-Windows-WLAN-AutoConfig
  • Microsoft-IE
  • Microsoft-Windows-PDC
  • Microsoft-Windows-Kernel-EventTracing
  • Microsoft-Antimalware-AMFilter
  • Microsoft-Windows-ProcessStateManager
  • Microsoft-Windows-DotNETRuntime
  • Microsoft-Antimalware-Protection
  • Microsoft-Windows-BrokerInfrastructure

Best of all max file size was set to 0 (unlimited):

image

Not sure how this trace got started, but removing the keys stopped it taking up all my disk space…

Posted in Debugging, Windows 10, Windows Performance Recorder | Tagged | 1 Comment