Jump to content

Get KnownFolderID from folder path


benners
 Share

Recommended Posts

I have previously used _WinAPI_ShellGetKnownFolderPath to get the the full path of a known folder. This time I want to do the reverse and check if a file path matches a known folder.

The idea is to supply a path to a function and first try to find an exact match. If no exact match is found, walk up the file path to find the closest partial match.

I have found information on T'Internet about the KnownFolderManager and that has a method called FindFolderFromPath but the information there is right over my head. I have also found some VB code Here, and other code here, again when dll calls are mentioned, eyes glaze over and I end up going into a Homer Simpson day dream 🤔

I created a BS pseudo code function layout as an example of what I would like returned. I think the answer may involve a dll call. If someone could look at the links and add any info which may help me on my way, it would be appreciated 😍

Global $s_Path = 'C:\Program Files (x86)'

Global $s_KFN = GetKnownFolderFromPath($s_Path)

If Not @error Then MsgBox(0,'', $s_KFN)

Func GetKnownFolderFromPath($s_Path)
    Const $FFFP_EXACTMATCH = 0
    Const $FFFP_NEARESTPARENTMATCH = 1

    Local $o_KFM = ObjCreate('KnownFolderManager')
    If @error Then Return SetError(1, 0, 'ObjCreate(KnownFolderManager)')

    Local $o_IKF = ObjCreate('IKnownFolder')
    If @error Then Return SetError(2, 0, 'ObjCreate(IKnownFolder)')

    ; try to find an exact match for the path
    Local $v_Ret = $o_KFM.FindFolderFromPath($s_Path, $FFFP_EXACTMATCH, $o_IKF)

    If @error Then
        ; if no exact match then walk backwards up the file path and find the nearest match
        $v_Ret = $o_KFM.FindFolderFromPath($s_Path, $FFFP_NEARESTPARENTMATCH, $o_IKF)

        ; if no partial match then return the file path
        If @error Then Return SetError(3, 0, $s_Path)
    EndIf

    Return $o_IKF.Path
EndFunc   ;==>GetKnownFolderFromPath

 

Edited by benners
added another link
Link to comment
Share on other sites

Maybe this?

AutoItSetOption("MustDeclareVars", True)

Local $path = "C:\Program Files (x86)\something"

ConsoleWrite(FindExactOrClosestPatialMatch($path) & @CRLF)

Func FindExactOrClosestPatialMatch($path)
   While True
      If FileExists($path) Then Return $path
      $path = StringRegExpReplace($path, "^(.*[\\/]).*?$", "$1")
      If @extended == 0 Then Return SetError(1, 0, Null)
   WEnd
EndFunc
Link to comment
Share on other sites

Thanks for the reply boom

The code isn't any that I can use to sort the issue. I don't need to get the path from the string. I need to know if the string is a KnownFolder. These are special folders like My Documents or My Pictures for instance. These are different per user. I am writing a program that install software, copy files to dirs and delete shortcuts etc for mutliple users.

The idea is to test on my system, do the deleting, copying etc and any file paths that relate to my user folder are changed to the correct environment string for any CurrentUser or AllUsers.

I have been using Code by Yashied, which is now included in the default includes, to get the file path from a folders guid and needed to do the reverse. I had found some info as mentioned in the first post and various AHK scripts and C Code but can't fathom how to do it in AutoIt.

I might have a workaround but don't know how bulletproof it is.

Link to comment
Share on other sites

I managed to cobble some code together that gets me what I want. I haven't used it in anger yet so it may need tweaknig when I get around to using it in the app. I would prefer to be able the built in api ones in the first post but hey 😦

What function did you wrap?

#AutoIt3Wrapper_AU3Check_Parameters=-q -d -w 1 -w 2 -w 3 -w- 4 -w 5 -w 6 -w-7

#include-once

; #INDEX# =======================================================================================================================
; Title .........: KnownFolders
; AutoIt Version : 3.3.14.3
; Description ...: A collection of functions for KnownFolder interrogation
; Author(s) .....: Benners
; ===============================================================================================================================

; #CURRENT# =====================================================================================================================
;_KFS_CreateArray           : Create an array listing all the knownfolders for the current system along with their path values
;_KFS_GetFolderNameFromPath : Searches an array for matches to a specified path and subsitutes path for KnownFolder name
;_KFS_GetFolderPathFromName : Retrieve a knownfolders path based on its assigned name
; ===============================================================================================================================

; #INTERNAL# ====================================================================================================================
; ===============================================================================================================================

#include <Array.au3>
#include <WinAPIReg.au3>
#include <WinAPIShellEx.au3>

;~ #Tidy_Parameters=/sf

;#### Example ####
; create an array
Global $as_KF = _KFS_CreateArray()

;~ _ArrayDisplay($as_KF)

If @error Then
    MsgBox(0, 'Error', $as_KF)
Else
    ; search for the closest path and create a string path from it
    Local $s_FolderPath = _KFS_GetFolderNameFromPath('C:\ProgramData\Microsoft\User Account Pictures\Default Pictures', $as_KF)

    MsgBox(0, 'New Folder Path', $s_FolderPath)

    ; get the path from the name
    $s_FolderPath = _KFS_GetFolderPathFromName('#Common AppData#', $as_KF)

    MsgBox(0, 'KnownFolder Path', $s_FolderPath)
EndIf
;#### Example ####

; #FUNCTION# ====================================================================================================================
; Name ..........: _KFS_CreateArray
; Description ...: Create an array of knownfolders found in the registry for the current system along with their path values
; Syntax ........: _KFS_CreateArray()
; Parameters ....: None
; Return values .: Success: a 2D array, sorted on column 2
;                       arr[0] - name of the known folder
;                       arr[1] - path of the folder
;                       arr[2] - length of the path string
;                  Failure: sets @error to non zero and based on @error returns string error message (1, 2) or an array (6, 7)
;                           @error 1: unable to open the registry key (_WinAPI_RegOpenKey)
;                           @error 2: unable to query the registry key (_WinAPI_RegQueryInfoKey)
;                           @error 6: unable to close the registry query (_WinAPI_RegCloseKey)
;                           @error 7: unable to sort thr array (_ArraySort)
; Author ........: Benners
; Modified ......:
; ===============================================================================================================================
Func _KFS_CreateArray()
    Local _
            $s_SubKey = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FolderDescriptions', _
            $s_FullKey = 'HKEY_LOCAL_MACHINE' & '\' & $s_SubKey, _
            $s_GUID

    ; gopen the root key for reading
    Local $h_RootKey = _WinAPI_RegOpenKey($HKEY_LOCAL_MACHINE, $s_SubKey, $KEY_READ)
    If @error Then Return SetError(1, @extended, _WinAPI_GetErrorMessage(@extended))

    ; get the number of sub keys
    Local $i_Max = _WinAPI_RegQueryInfoKey($h_RootKey)
    If @error Then Return SetError(2, @extended, _WinAPI_GetErrorMessage(@extended))

    ; columns in the array
    Local Enum _
            $NAME, _
            $PATH, _
            $LENGTH

    ; create an array for the info
    Local $as_KF[$i_Max[0]][3]

    ; loop through the sub keys and get the 'Name' value from the key
    For $i = 0 To $i_Max[0] - 1
        ; get the GUID
        $s_GUID = _WinAPI_RegEnumKey($h_RootKey, $i)
        If @error Then ContinueLoop ; Return SetError(3, @extended, _WinAPI_GetErrorMessage(@extended))

        ; get the 'Name' value from the GUID key
        $as_KF[$i][$NAME] = RegRead($s_FullKey & '\' & $s_GUID, 'Name')
        If @error Then ContinueLoop ; Return SetError(4, @error, 'RegRead)

        ; get the path of the folder
        $as_KF[$i][$PATH] = _WinAPI_ShellGetKnownFolderPath($s_GUID)
        If @error Then ContinueLoop ; Return SetError(5, @extended, _WinAPI_GetErrorMessage(@extended))

        ; get the string length. used for sorting later
        $as_KF[$i][$LENGTH] = StringLen($as_KF[$i][$PATH])
    Next

    ; close the handle to the key
    _WinAPI_RegCloseKey($h_RootKey)
    If @error Then SetError(6, @extended, _WinAPI_GetErrorMessage(@extended))

    ; sort the array, longest path first
    _ArraySort($as_KF, 1, 0, 0, $LENGTH)
    If @error Then SetError(7, @error, '_ArraySort failed')

    Return SetError(@error, @extended, $as_KF)
EndFunc   ;==>_KFS_CreateArray

; #FUNCTION# ====================================================================================================================
; Name ..........: _KFS_GetFolderNameFromPath
; Description ...: Searches an array for matches to a specified path andsubsitutes path for KnownFolder name
; Syntax ........: _KFS_GetFolderNameFromPath($s_Path, $as_KF)
; Parameters ....: $s_Path - a string value.
;                  $as_KF  - a 2D array of strings. uses _KFS_CreateArray() is passed variable isn't an array
; Return values .: Success: a string path
;                           If a match is found    - sets @extended to 1, path strings substituted and enclosed in '#'
;                           If a match isn't found - sets @extended to 0, original path is returned
;                  Failure: sets @error to non zero is _KFS_CreateArray is not created and returns an error message
; Author ........: Benners
; Modified ......:
; Remarks .......: Uses _KFS_CreateArray to create the array of KnownFolders if no array specified
; ===============================================================================================================================
Func _KFS_GetFolderNameFromPath($s_Path, ByRef $as_KF)
    ; create the array
    If Not IsArray($as_KF) Then
        $as_KF = _KFS_CreateArray()
        If @error Then Return SetError(@error, @extended, $as_KF)
    EndIf

    ; columns in the array
    Local Enum _
            $NAME, _
            $PATH

    ; loop through the array and search for the path string
    For $i = 0 To UBound($as_KF) - 1
        ; if the string is found, replace it with the name
        If StringInStr($s_Path, $as_KF[$i][$PATH]) Then
            $s_Path = StringReplace($s_Path, $as_KF[$i][$PATH], '#' & $as_KF[$i][$NAME] & '#')
            SetExtended(1)
            ExitLoop
        EndIf
    Next

    Return SetError(@error, @extended, $s_Path)
EndFunc   ;==>_KFS_GetFolderNameFromPath

; #FUNCTION# ====================================================================================================================
; Name ..........: _KFS_GetFolderPathFromName
; Description ...: Retrieve a knownfolders path based on its assigned name
; Syntax ........: _KFS_GetFolderPathFromName($s_Name[, $as_KF = 0])
; Parameters ....: $s_Name - a string value. name of the folder
;                  $as_KF  - a 2D array of strings. uses _KFS_CreateArray() is passed variable isn't an array
; Return values .: Success: a string
;                       If a match is found    - sets @extended to 1, path to the specified folder
;                       If a match isn't found - sets @extended to 0, original name is returned
;                  Failure: sets @error to non zero is _KFS_CreateArray is not created and returns an error message
; Author ........: Benners
; Modified ......:
; Remarks .......: Uses _KFS_CreateArray to create the array of KnownFolders if no array specified
; ===============================================================================================================================
Func _KFS_GetFolderPathFromName($s_Name, ByRef $as_KF)
    ; create the array
    If Not IsArray($as_KF) Then
        $as_KF = _KFS_CreateArray()
        If @error Then Return SetError(@error, @extended, $as_KF)
    EndIf

    ; columns in the array
    Local Enum _
            $NAME, _
            $PATH

    ; strip the '#' symbols
    $s_Name = StringReplace($s_Name, '#', '')

    ; set the default return path
    Local $s_Path = $s_Name

    ; loop through the array and search for the name string
    For $i = 0 To UBound($as_KF) - 1
        ; if the string is found, replace it with the path
        If StringCompare($s_Name, $as_KF[$i][$NAME]) = 0 Then
            $s_Path = $as_KF[$i][$PATH]
            SetExtended(1)
            ExitLoop
        EndIf
    Next

    Return SetError(@error, @extended, $s_Path)
EndFunc   ;==>_KFS_GetFolderPathFromName

 

Link to comment
Share on other sites

@benners  Not a bad solution you got there.  If you also need localized name, you can use this to get it from :

#include <WinAPIRes.au3>

ConsoleWrite (_GetString(21813) & @CRLF)

Func _GetString($iNum)
  Local $hInstance = _WinAPI_LoadLibraryEx("shell32.dll", $LOAD_LIBRARY_AS_DATAFILE)
  If Not $hInstance Then Return SetError(1, 0, "")
  Local $sText = _WinAPI_LoadString($hInstance, $iNum)
  Local $iErr = @error
  $iNum = @extended
  _WinAPI_FreeLibrary($hInstance)
  If $iErr Then Return SetError(2, 0, "")
  Return SetError(0, $iNum, $sText)
EndFunc

where the number is from the registry you are reading...

Link to comment
Share on other sites

Cheers Nine. It may need some extra to use the correct registry tree based on bit type but I'll get to that. I'll also keep trying to understand the AHK code I found and other code types and maybe improve it, but for now it will do.

 I'll add your code to mysnippets collection 😀.  It could come in useful for other projects.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...