EDIT: Scotty2012 and David Morton's answers don't work for me so I have put a bounty on this question. I think I need to change the type of the string to something else before passing it in.
I'm not much cop at P/Invoke and I'm struggling with declaring and calling SHSetKnownFolderPath. I'm using VB9 but if anyone puts answers in C# I should be able to translate.
I have got SHGetKnowFolderPath working. Here is my code.
In VB
Imports System.Runtime.InteropServices
Public Class Form1
<DllImport("shell32.dll")> _
Private Shared Function SHGetKnownFolderPath(<MarshalAs(UnmanagedType.LPStruct)> ByVal rfid As Guid, ByVal dwFlags As UInteger, ByVal hToken As IntPtr, ByRef pszPath As IntPtr) As Integer
End Function
<DllImport("shell32.dll")> _
Private Shared Function SHSetKnownFolderPath(<MarshalAs(UnmanagedType.LPStruct)> ByVal rfid As Guid, ByVal dwFlags As UInteger, ByVal hToken As IntPtr, ByRef pszPath As IntPtr) As Integer
End Function
Public Shared ReadOnly Documents As New Guid("FDD39AD0-238F-46AF-ADB4-6C85480369C7")
Private Sub ButtonSetDocumentsPath_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonSetDocumentsPath.Click
Dim pPath As IntPtr = Marshal.StringToCoTaskMemUni(TextBoxPath.Text)
If SHSetKnownFolderPath(Documents, 0, IntPtr.Zero, pPath) = 0 Then
MsgBox("Set Sucessfully")
End If
End Sub
Private Sub ButtonGetDocumentsPath_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonGetDocumentsPath.Click
Dim pPath As IntPtr
If SHGetKnownFolderPath(Documents, 0, IntPtr.Zero, pPath) = 0 Then
Dim s As String = Marshal.PtrToStringUni(pPath)
Marshal.FreeCoTaskMem(pPath)
TextBoxPath.Text = s
End If
End Sub
End Class
Thanks!
Try this code out. Sorry for the length, but it's all needed to properly PInvoke this particular function. It's a simple console application that includes a definition for both functions and an example usage of SHGetKnownFolderPath.
I went ahead and included the definitions for KNOWN_FOLDER_FLAG as well as a few definitions for the folder ID's. All of the folder Id's are actually just GUIDs. All of the possible ID's can be found at %ProgramFiles%\Windows SDK\v6.0A\Include\KnownFolders.h and added in the same manner that I added in the sample.
I included several wrapper functions that hide all of the evil marashal'ing details for calling the particular functions.
If there is any particular folder id you'd like or explanation please add a comment and I'll update the sample.
EDIT Corrected a mistake in the Marshalling of SHSetKnownFolderPath. I did not add a MarshalAs tag to the String value and it was defaulting to an ANSI string. The API required unicode. The SHSetFolderFunction now works (confirmed with RecentFolder)
Imports System.Runtime.InteropServices
Module NativeMethods
Public Enum KNOWN_FOLDER_FLAG
'''KF_FLAG_CREATE -> 0x00008000
KF_FLAG_CREATE = 32768
'''KF_FLAG_DONT_VERIFY -> 0x00004000
KF_FLAG_DONT_VERIFY = 16384
'''KF_FLAG_DONT_UNEXPAND -> 0x00002000
KF_FLAG_DONT_UNEXPAND = 8192
'''KF_FLAG_NO_ALIAS -> 0x00001000
KF_FLAG_NO_ALIAS = 4096
'''KF_FLAG_INIT -> 0x00000800
KF_FLAG_INIT = 2048
'''KF_FLAG_DEFAULT_PATH -> 0x00000400
KF_FLAG_DEFAULT_PATH = 1024
'''KF_FLAG_NOT_PARENT_RELATIVE -> 0x00000200
KF_FLAG_NOT_PARENT_RELATIVE = 512
'''KF_FLAG_SIMPLE_IDLIST -> 0x00000100
KF_FLAG_SIMPLE_IDLIST = 256
'''KF_FLAG_ALIAS_ONLY -> 0x80000000
KF_FLAG_ALIAS_ONLY = &H80000000
End Enum
Public ComputerFolder As Guid = New Guid("0AC0837C-BBF8-452A-850D-79D08E667CA7")
Public DesktopFolder As Guid = New Guid("B4BFCC3A-DB2C-424C-B029-7FE99A87C641")
Public DocumentsFolder As Guid = New Guid("FDD39AD0-238F-46AF-ADB4-6C85480369C7")
<DllImport("shell32.dll")> _
Public Function SHGetKnownFolderPath( _
ByRef folderId As Guid, _
ByVal flags As UInteger, _
ByVal token As IntPtr, _
<Out()> ByRef pathPtr As IntPtr) As Integer
End Function
<DllImport("shell32.dll")> _
Public Function SHSetKnownFolderPath( _
ByRef folderId As Guid, _
ByVal flags As UInteger, _
ByVal token As IntPtr, _
<[In](), MarshalAs(UnmanagedType.LPWStr)> ByVal path As String) As Integer
End Function
Public Function SHGetKnownFolderPathWrapper(ByVal folderId As Guid) As String
Return SHGetKnownFolderPathWrapper(folderId, 0)
End Function
Public Function SHGetKnownFolderPathWrapper( _
ByVal folderId As Guid, _
ByVal flags As KNOWN_FOLDER_FLAG) As String
Dim ptr = IntPtr.Zero
Dim path = String.Empty
Try
Dim ret = SHGetKnownFolderPath(folderId, CUInt(flags), IntPtr.Zero, ptr)
If ret <> 0 Then
Throw Marshal.GetExceptionForHR(ret)
End If
path = Marshal.PtrToStringUni(ptr)
Finally
Marshal.FreeCoTaskMem(ptr)
End Try
Return path
End Function
Public Sub SHSetKnownFolderPathWrapper( _
ByVal folderId As Guid, _
ByVal path As String)
SHSetKnownFolderPathWrapper(folderId, 0, path)
End Sub
Public Sub SHSetKnownFolderPathWrapper( _
ByVal folderId As Guid, _
ByVal flags As KNOWN_FOLDER_FLAG, _
ByVal path As String)
Dim ret = SHSetKnownFolderPath(folderId, CUInt(flags), IntPtr.Zero, path)
If ret <> 0 Then
Throw Marshal.GetExceptionForHR(ret)
End If
End Sub
End Module
Module Module1
Sub Main()
Dim path = SHGetKnownFolderPathWrapper(NativeMethods.DesktopFolder)
Console.WriteLine(path)
End Sub
End Module
I think this should work in C# (I'm not running vista here so I can't check):
[DllImport("shell32.dll")]
private static int SHSetKnownFolderPath(ref Guid guid, int flags, IntPtr hToken, string newPath);
you can call it this way
SHSetKnownFolderPath(ref Documents, 0, IntPtr.Zero, "c:\\my new path\\");
This would be the declaration:
[DllImport("shell32.dll")]
static extern int SHSetFolderPath(int csidl, IntPtr hToken, uint dwFlags, StringBuilder path)
You would need to create a StringBuilder, passing in the maximum path of 260 into the constructor (This would be true for Vista/XP.) This is the stringbuilder that would have the new directory for the folder you're trying to set, so append your text into the StringBuilder for your new location. The biggest problem with your implementation, though is that the csidl parameter isn't the same as the Guid specified in Windows. These values are actually the values that are declared in shlobj.h. Follow the link to see the values that are top be passed in. The hToken should always be IntPtr.Zero, unless you have a pointer to a specific user you're trying to change this for. IntPtr.Zero will use the current user. dwFlags should always be set to 0.