none
Unreadable code for the unicode language in getting IE cache RRS feed

  • Question

  • I want to get IE cache using the code below in Windows 10 x64 using the code below, but will return the unreadable code for Chinese string, it's good for English text, how to correct it? thank you.

    Public Class DeleteHistoryAPI

        'For PInvoke: Contains information about an entry in the Internet cache
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
        Public Structure INTERNET_CACHE_ENTRY_INFOA
            Public dwStructSize As Integer
            Public lpszSourceUrlName As IntPtr
            Public lpszLocalFileName As IntPtr
            Public CacheEntryType As Integer
            Public dwUseCount As Integer
            Public dwHitRate As Integer
            Public dwSizeLow As Integer
            Public dwSizeHigh As Integer
            Public LastModifiedTime As ComTypes.FILETIME
            Public ExpireTime As ComTypes.FILETIME
            Public LastAccessTime As ComTypes.FILETIME
            Public LastSyncTime As ComTypes.FILETIME
            Public lpHeaderInfo As IntPtr
            Public dwHeaderInfoSize As Integer
            Public lpszFileExtension As IntPtr
            Public dwExemptDelta As Integer
        End Structure

        'For PInvoke: Initiates the enumeration of the cache groups in the Internet cache
        <DllImport("wininet.dll", SetLastError:=True,
           CharSet:=CharSet.Auto,
           EntryPoint:="FindFirstUrlCacheGroup",
           CallingConvention:=CallingConvention.StdCall)>
        Shared Function FindFirstUrlCacheGroup( _
            ByVal dwFlags As Int32, _
            ByVal dwFilter As Integer, _
            ByVal lpSearchCondition As IntPtr, _
            ByVal dwSearchCondition As Int32, _
            ByRef lpGroupId As Long, _
            ByVal lpReserved As IntPtr) As IntPtr
        End Function

        'For PInvoke: Retrieves the next cache group in a cache group enumeration
        <DllImport("wininet.dll",
           SetLastError:=True,
           CharSet:=CharSet.Auto,
           EntryPoint:="FindNextUrlCacheGroup",
           CallingConvention:=CallingConvention.StdCall)>
        Shared Function FindNextUrlCacheGroup( _
            ByVal hFind As IntPtr, _
            ByRef lpGroupId As Long, _
            ByVal lpReserved As IntPtr) As Boolean
        End Function

        'For PInvoke: Releases the specified GROUPID and any associated state in the cache index file
        <DllImport("wininet.dll",
           SetLastError:=True,
           CharSet:=CharSet.Auto,
           EntryPoint:="DeleteUrlCacheGroup",
           CallingConvention:=CallingConvention.StdCall)>
        Shared Function DeleteUrlCacheGroup( _
            ByVal GroupId As Long, _
            ByVal dwFlags As Int32, _
            ByVal lpReserved As IntPtr) As Boolean
        End Function

        'For PInvoke: Begins the enumeration of the Internet cache
        <DllImport("wininet.dll",
            SetLastError:=True,
            CharSet:=CharSet.Auto,
            EntryPoint:="FindFirstUrlCacheEntryA",
            CallingConvention:=CallingConvention.StdCall)>
        Shared Function FindFirstUrlCacheEntry( _
         <MarshalAs(UnmanagedType.LPStr)> ByVal lpszUrlSearchPattern As String, _
              ByVal lpFirstCacheEntryInfo As IntPtr, _
              ByRef lpdwFirstCacheEntryInfoBufferSize As Int32) As IntPtr
        End Function

        'For PInvoke: Retrieves the next entry in the Internet cache
        <DllImport("wininet.dll",
           SetLastError:=True,
           CharSet:=CharSet.Auto,
           EntryPoint:="FindNextUrlCacheEntryA",
           CallingConvention:=CallingConvention.StdCall)>
        Shared Function FindNextUrlCacheEntry( _
              ByVal hFind As IntPtr, _
              ByVal lpNextCacheEntryInfo As IntPtr, _
              ByRef lpdwNextCacheEntryInfoBufferSize As Integer) As Boolean
        End Function

        'For PInvoke: Removes the file that is associated with the source name from the cache, if the file exists
        <DllImport("wininet.dll",
          SetLastError:=True,
          CharSet:=CharSet.Auto,
          EntryPoint:="DeleteUrlCacheEntryA",
          CallingConvention:=CallingConvention.StdCall)>
        Shared Function DeleteUrlCacheEntry(
            ByVal lpszUrlName As String) As Long
        End Function

    End Class

    Module GetIEHistory

        Public Sub GetIEURLCache()
         
            Try
                Const ERROR_NO_MORE_ITEMS As Int32 = 259

                Dim cacheEntryInfoBufferSizeInitial As Integer = 0
                Dim cacheEntryInfoBufferSize As Integer = 0
                Dim cacheEntryInfoBuffer As IntPtr = IntPtr.Zero
                Dim internetCacheEntry As DeleteHistoryAPI.INTERNET_CACHE_ENTRY_INFOA
                Dim enumHandle As IntPtr = IntPtr.Zero
                Dim returnValue As Boolean = False

                enumHandle = DeleteHistoryAPI.FindFirstUrlCacheEntry(vbNull, IntPtr.Zero, cacheEntryInfoBufferSizeInitial)
                If (Not enumHandle.Equals(IntPtr.Zero) And ERROR_NO_MORE_ITEMS.Equals(Marshal.GetLastWin32Error())) Then
                    Exit Sub
                End If

                cacheEntryInfoBufferSize = cacheEntryInfoBufferSizeInitial
                cacheEntryInfoBuffer = Marshal.AllocHGlobal(cacheEntryInfoBufferSize)
                enumHandle = DeleteHistoryAPI.FindFirstUrlCacheEntry(vbNull, cacheEntryInfoBuffer, cacheEntryInfoBufferSizeInitial)

                 Dim SourceUrlName As String
           
                While (True)
                    internetCacheEntry = CType(Marshal.PtrToStructure(cacheEntryInfoBuffer, GetType(DeleteHistoryAPI.INTERNET_CACHE_ENTRY_INFOA)), DeleteHistoryAPI.INTERNET_CACHE_ENTRY_INFOA)
                    cacheEntryInfoBufferSizeInitial = cacheEntryInfoBufferSize

                    SourceUrlName = Marshal.PtrToStringAnsi(internetCacheEntry.lpszSourceUrlName)


                    returnValue = DeleteHistoryAPI.FindNextUrlCacheEntry(enumHandle, cacheEntryInfoBuffer, cacheEntryInfoBufferSizeInitial)
                    If (Not returnValue And ERROR_NO_MORE_ITEMS.Equals(Marshal.GetLastWin32Error())) Then
                        Exit While
                    End If

                    If (Not returnValue And cacheEntryInfoBufferSizeInitial > cacheEntryInfoBufferSize) Then

                        cacheEntryInfoBufferSize = cacheEntryInfoBufferSizeInitial
                        Dim tempIntPtr As New IntPtr(cacheEntryInfoBufferSize)
                        cacheEntryInfoBuffer = Marshal.ReAllocHGlobal(cacheEntryInfoBuffer, tempIntPtr)
                        returnValue = DeleteHistoryAPI.FindNextUrlCacheEntry(enumHandle, cacheEntryInfoBuffer, cacheEntryInfoBufferSizeInitial)
                    End If

                End While

                Marshal.FreeHGlobal(cacheEntryInfoBuffer)

            Catch ex As Exception
            End Try
        
        End Sub

    Thursday, February 23, 2017 4:15 AM

Answers

  •  You probably are not using the correct EntryPoint for the functions or did not set the correct charset for them.  You might also not be marshaling the strings right in the functions.  If you look at the msdn documents for these functions,  they will tell you if there is a separate Unicode and Ansi entry point for the function.  The Unicode entry point would have a "W" and the Ansi would have an "A".  If it does not show this,  the function does not have a separate entry points and would not have a "W" or "A" on the entry point.

     Below is an example you can test in a new form project with 1 Button and 1 RichTextBox on the form.

     I did not show the code for deleting the items of the cache but,  i can give an example if you need it.  You would just pass the "SourceUrlName" string to the DeleteUrlCacheEntry function.  I included the correct function signature and the 2 win32 error code constants that it can return too.

     Also,  you should be calling the FindCloseUrlCache function,  passing the handle that was returned from the FindFirst function,  after you are finished enumerating the entries.

    Imports System.Runtime.InteropServices
    
    Public Class Form1
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            GetIEURLCache()
        End Sub
    
        Public Sub GetIEURLCache()
            Dim CacheBufferSize As Integer = 0
            Dim hCacheBuffer As IntPtr = IntPtr.Zero
            Dim CacheEntry As NativeMethods.INTERNET_CACHE_ENTRY_INFOW
            Dim hEnum As IntPtr = IntPtr.Zero
            Dim SourceUrlName As String = ""
            Dim bReturn As Boolean = False
    
            hEnum = NativeMethods.FindFirstUrlCacheEntry(Nothing, IntPtr.Zero, CacheBufferSize)
            If hEnum = IntPtr.Zero AndAlso Marshal.GetLastWin32Error <> NativeMethods.ERROR_INSUFFICIENT_BUFFER Then Exit Sub
    
            hCacheBuffer = Marshal.AllocHGlobal(CacheBufferSize)
            hEnum = NativeMethods.FindFirstUrlCacheEntry(Nothing, hCacheBuffer, CacheBufferSize)
            If hEnum = IntPtr.Zero Then Exit Sub
    
            Do
                CacheEntry = CType(Marshal.PtrToStructure(hCacheBuffer, GetType(NativeMethods.INTERNET_CACHE_ENTRY_INFOW)), NativeMethods.INTERNET_CACHE_ENTRY_INFOW)
    
                SourceUrlName = Marshal.PtrToStringUni(CacheEntry.lpszSourceUrlName)
                RichTextBox1.AppendText(SourceUrlName & vbNewLine)
    
                bReturn = NativeMethods.FindNextUrlCacheEntry(hEnum, hCacheBuffer, CacheBufferSize)
                If Not bReturn AndAlso Marshal.GetLastWin32Error = NativeMethods.ERROR_NO_MORE_ITEMS Then
                    Exit Do
                End If
    
                If Not bReturn AndAlso Marshal.GetLastWin32Error = NativeMethods.ERROR_INSUFFICIENT_BUFFER Then
                    hCacheBuffer = Marshal.ReAllocHGlobal(hCacheBuffer, New IntPtr(CacheBufferSize))
                    bReturn = NativeMethods.FindNextUrlCacheEntry(hEnum, hCacheBuffer, CacheBufferSize)
                End If
            Loop
    
            Marshal.FreeHGlobal(hCacheBuffer)
            NativeMethods.FindCloseUrlCache(hEnum)
        End Sub
    End Class
    
    
    Public Class NativeMethods
        '############## Possible Errors for DeleteUrlCacheEntry ##############
        ''' <summary>The file is not in the cache.</summary>
        Public Const ERROR_FILE_NOT_FOUND As Integer = &H2
        ''' <summary>The file is locked or in use. The entry is marked and deleted when the file is unlocked.</summary>
        Public Const ERROR_ACCESS_DENIED As Integer = &H5
    
        '############## Possible Errors for FindFirstUrlCacheEntry and FindNextUrlCacheEntry ##############
        ''' <summary>The buffer size is too small.</summary>
        Public Const ERROR_INSUFFICIENT_BUFFER As Integer = &H7A
        ''' <summary>No more data is available.</summary>
        Public Const ERROR_NO_MORE_ITEMS As Integer = &H103
    
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
        Public Structure INTERNET_CACHE_ENTRY_INFOW
            Public dwStructSize As Integer
            Public lpszSourceUrlName As IntPtr
            Public lpszLocalFileName As IntPtr
            Public CacheEntryType As Integer
            Public dwUseCount As Integer
            Public dwHitRate As Integer
            Public dwSizeLow As Integer
            Public dwSizeHigh As Integer
            Public LastModifiedTime As ComTypes.FILETIME
            Public ExpireTime As ComTypes.FILETIME
            Public LastAccessTime As ComTypes.FILETIME
            Public LastSyncTime As ComTypes.FILETIME
            Public lpHeaderInfo As IntPtr
            Public dwHeaderInfoSize As Integer
            Public lpszFileExtension As IntPtr
            Public dwExemptDelta As Integer
        End Structure
    
        <DllImport("wininet.dll", SetLastError:=True, EntryPoint:="FindFirstUrlCacheEntryW")>
        Public Shared Function FindFirstUrlCacheEntry(<MarshalAs(UnmanagedType.LPWStr)> ByVal lpszUrlSearchPattern As String, ByVal lpFirstCacheEntryInfo As IntPtr, ByRef lpdwFirstCacheEntryInfoBufferSize As Integer) As IntPtr
        End Function
    
        <DllImport("wininet.dll", SetLastError:=True, EntryPoint:="FindNextUrlCacheEntryW")>
        Public Shared Function FindNextUrlCacheEntry(ByVal hFind As IntPtr, ByVal lpNextCacheEntryInfo As IntPtr, ByRef lpdwNextCacheEntryInfoBufferSize As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function
    
        <DllImport("wininet.dll", SetLastError:=True, EntryPoint:="DeleteUrlCacheEntryW")>
        Public Shared Function DeleteUrlCacheEntry(<MarshalAs(UnmanagedType.LPWStr)> ByVal lpszUrlName As String) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function
    
        <DllImport("wininet.dll", EntryPoint:="FindCloseUrlCache")>
        Public Shared Function FindCloseUrlCache(ByVal hEnumHandle As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function
    End Class
    
     

     Here you can see some of what it shows.


    If you say it can`t be done then i`ll try it

    • Edited by IronRazerz Thursday, February 23, 2017 3:59 PM
    • Proposed as answer by Neda ZhangModerator Friday, February 24, 2017 1:33 AM
    • Marked as answer by gaxjyxq Friday, February 24, 2017 6:54 AM
    Thursday, February 23, 2017 3:53 PM

All replies

  • Perform a research: use CharSet.Unicode in your structures, rename INTERNET_CACHE_ENTRY_INFOA to INTERNET_CACHE_ENTRY_INFOW, and use PtrToStringUni instead of PtrToStringAnsi.

    Thursday, February 23, 2017 7:22 AM
  • Be aware that on Internet mostly not Unicode is used but W3S code.

    https://www.w3schools.com/charsets/ref_utf_punctuation.asp


    Success
    Cor


    • Edited by Cor Ligthert Thursday, February 23, 2017 9:31 AM
    Thursday, February 23, 2017 9:30 AM
  • Thank you, i have changed the code following you, but not luck, no any item can be shown to list.

    /////////////////////////////////////////////////////////////////////////

    Public Class DeleteHistoryAPI

    'For PInvoke: Contains information about an entry in the Internet cache
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
        Public Structure INTERNET_CACHE_ENTRY_INFOW
            Public dwStructSize As Integer
            Public lpszSourceUrlName As IntPtr
            Public lpszLocalFileName As IntPtr
            Public CacheEntryType As Integer
            Public dwUseCount As Integer
            Public dwHitRate As Integer
            Public dwSizeLow As Integer
            Public dwSizeHigh As Integer
            Public LastModifiedTime As ComTypes.FILETIME
            Public ExpireTime As ComTypes.FILETIME
            Public LastAccessTime As ComTypes.FILETIME
            Public LastSyncTime As ComTypes.FILETIME
            Public lpHeaderInfo As IntPtr
            Public dwHeaderInfoSize As Integer
            Public lpszFileExtension As IntPtr
            Public dwExemptDelta As Integer
        End Structure

       .............

    End Class


     Public Sub GetIEURLCache()
       
            Try
                Const ERROR_NO_MORE_ITEMS As Int32 = 259

                Dim cacheEntryInfoBufferSizeInitial As Integer = 0
                Dim cacheEntryInfoBufferSize As Integer = 0
                Dim cacheEntryInfoBuffer As IntPtr = IntPtr.Zero
                Dim internetCacheEntry As DeleteHistoryAPI.INTERNET_CACHE_ENTRY_INFOW
                Dim enumHandle As IntPtr = IntPtr.Zero
                Dim returnValue As Boolean = False

                enumHandle = DeleteHistoryAPI.FindFirstUrlCacheEntry(vbNull, IntPtr.Zero, cacheEntryInfoBufferSizeInitial)
                If (Not enumHandle.Equals(IntPtr.Zero) And ERROR_NO_MORE_ITEMS.Equals(Marshal.GetLastWin32Error())) Then
                    Exit Sub
                End If

                cacheEntryInfoBufferSize = cacheEntryInfoBufferSizeInitial
                cacheEntryInfoBuffer = Marshal.AllocHGlobal(cacheEntryInfoBufferSize)
                enumHandle = DeleteHistoryAPI.FindFirstUrlCacheEntry(vbNull, cacheEntryInfoBuffer, cacheEntryInfoBufferSizeInitial)

                Dim SourceUrlName As String

                               


                While (True)
                    internetCacheEntry = CType(Marshal.PtrToStructure(cacheEntryInfoBuffer, GetType(DeleteHistoryAPI.INTERNET_CACHE_ENTRY_INFOW)), DeleteHistoryAPI.INTERNET_CACHE_ENTRY_INFOW)

      cacheEntryInfoBufferSizeInitial = cacheEntryInfoBufferSize

                    SourceUrlName = Marshal.PtrToStringUni(internetCacheEntry.lpszSourceUrlName)

    ...........
        End Sub


    • Edited by gaxjyxq Thursday, February 23, 2017 11:12 AM
    Thursday, February 23, 2017 11:11 AM
  •  You probably are not using the correct EntryPoint for the functions or did not set the correct charset for them.  You might also not be marshaling the strings right in the functions.  If you look at the msdn documents for these functions,  they will tell you if there is a separate Unicode and Ansi entry point for the function.  The Unicode entry point would have a "W" and the Ansi would have an "A".  If it does not show this,  the function does not have a separate entry points and would not have a "W" or "A" on the entry point.

     Below is an example you can test in a new form project with 1 Button and 1 RichTextBox on the form.

     I did not show the code for deleting the items of the cache but,  i can give an example if you need it.  You would just pass the "SourceUrlName" string to the DeleteUrlCacheEntry function.  I included the correct function signature and the 2 win32 error code constants that it can return too.

     Also,  you should be calling the FindCloseUrlCache function,  passing the handle that was returned from the FindFirst function,  after you are finished enumerating the entries.

    Imports System.Runtime.InteropServices
    
    Public Class Form1
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            GetIEURLCache()
        End Sub
    
        Public Sub GetIEURLCache()
            Dim CacheBufferSize As Integer = 0
            Dim hCacheBuffer As IntPtr = IntPtr.Zero
            Dim CacheEntry As NativeMethods.INTERNET_CACHE_ENTRY_INFOW
            Dim hEnum As IntPtr = IntPtr.Zero
            Dim SourceUrlName As String = ""
            Dim bReturn As Boolean = False
    
            hEnum = NativeMethods.FindFirstUrlCacheEntry(Nothing, IntPtr.Zero, CacheBufferSize)
            If hEnum = IntPtr.Zero AndAlso Marshal.GetLastWin32Error <> NativeMethods.ERROR_INSUFFICIENT_BUFFER Then Exit Sub
    
            hCacheBuffer = Marshal.AllocHGlobal(CacheBufferSize)
            hEnum = NativeMethods.FindFirstUrlCacheEntry(Nothing, hCacheBuffer, CacheBufferSize)
            If hEnum = IntPtr.Zero Then Exit Sub
    
            Do
                CacheEntry = CType(Marshal.PtrToStructure(hCacheBuffer, GetType(NativeMethods.INTERNET_CACHE_ENTRY_INFOW)), NativeMethods.INTERNET_CACHE_ENTRY_INFOW)
    
                SourceUrlName = Marshal.PtrToStringUni(CacheEntry.lpszSourceUrlName)
                RichTextBox1.AppendText(SourceUrlName & vbNewLine)
    
                bReturn = NativeMethods.FindNextUrlCacheEntry(hEnum, hCacheBuffer, CacheBufferSize)
                If Not bReturn AndAlso Marshal.GetLastWin32Error = NativeMethods.ERROR_NO_MORE_ITEMS Then
                    Exit Do
                End If
    
                If Not bReturn AndAlso Marshal.GetLastWin32Error = NativeMethods.ERROR_INSUFFICIENT_BUFFER Then
                    hCacheBuffer = Marshal.ReAllocHGlobal(hCacheBuffer, New IntPtr(CacheBufferSize))
                    bReturn = NativeMethods.FindNextUrlCacheEntry(hEnum, hCacheBuffer, CacheBufferSize)
                End If
            Loop
    
            Marshal.FreeHGlobal(hCacheBuffer)
            NativeMethods.FindCloseUrlCache(hEnum)
        End Sub
    End Class
    
    
    Public Class NativeMethods
        '############## Possible Errors for DeleteUrlCacheEntry ##############
        ''' <summary>The file is not in the cache.</summary>
        Public Const ERROR_FILE_NOT_FOUND As Integer = &H2
        ''' <summary>The file is locked or in use. The entry is marked and deleted when the file is unlocked.</summary>
        Public Const ERROR_ACCESS_DENIED As Integer = &H5
    
        '############## Possible Errors for FindFirstUrlCacheEntry and FindNextUrlCacheEntry ##############
        ''' <summary>The buffer size is too small.</summary>
        Public Const ERROR_INSUFFICIENT_BUFFER As Integer = &H7A
        ''' <summary>No more data is available.</summary>
        Public Const ERROR_NO_MORE_ITEMS As Integer = &H103
    
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
        Public Structure INTERNET_CACHE_ENTRY_INFOW
            Public dwStructSize As Integer
            Public lpszSourceUrlName As IntPtr
            Public lpszLocalFileName As IntPtr
            Public CacheEntryType As Integer
            Public dwUseCount As Integer
            Public dwHitRate As Integer
            Public dwSizeLow As Integer
            Public dwSizeHigh As Integer
            Public LastModifiedTime As ComTypes.FILETIME
            Public ExpireTime As ComTypes.FILETIME
            Public LastAccessTime As ComTypes.FILETIME
            Public LastSyncTime As ComTypes.FILETIME
            Public lpHeaderInfo As IntPtr
            Public dwHeaderInfoSize As Integer
            Public lpszFileExtension As IntPtr
            Public dwExemptDelta As Integer
        End Structure
    
        <DllImport("wininet.dll", SetLastError:=True, EntryPoint:="FindFirstUrlCacheEntryW")>
        Public Shared Function FindFirstUrlCacheEntry(<MarshalAs(UnmanagedType.LPWStr)> ByVal lpszUrlSearchPattern As String, ByVal lpFirstCacheEntryInfo As IntPtr, ByRef lpdwFirstCacheEntryInfoBufferSize As Integer) As IntPtr
        End Function
    
        <DllImport("wininet.dll", SetLastError:=True, EntryPoint:="FindNextUrlCacheEntryW")>
        Public Shared Function FindNextUrlCacheEntry(ByVal hFind As IntPtr, ByVal lpNextCacheEntryInfo As IntPtr, ByRef lpdwNextCacheEntryInfoBufferSize As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function
    
        <DllImport("wininet.dll", SetLastError:=True, EntryPoint:="DeleteUrlCacheEntryW")>
        Public Shared Function DeleteUrlCacheEntry(<MarshalAs(UnmanagedType.LPWStr)> ByVal lpszUrlName As String) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function
    
        <DllImport("wininet.dll", EntryPoint:="FindCloseUrlCache")>
        Public Shared Function FindCloseUrlCache(ByVal hEnumHandle As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function
    End Class
    
     

     Here you can see some of what it shows.


    If you say it can`t be done then i`ll try it

    • Edited by IronRazerz Thursday, February 23, 2017 3:59 PM
    • Proposed as answer by Neda ZhangModerator Friday, February 24, 2017 1:33 AM
    • Marked as answer by gaxjyxq Friday, February 24, 2017 6:54 AM
    Thursday, February 23, 2017 3:53 PM
  • Thank you very much, it works well!
    Friday, February 24, 2017 6:54 AM