cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
rogdawg
Level 4

Custom Action problem -

I have the unfortunate duty of trying to update a small desktop application for Windows Vista compatibility. I need to utilize the "All Users\Documents" folder so my application can read and write files for all the users of a machine. Since the CSIDL_COMMON_DOCUMENTS folder is not one of the predefined folders in the Files view in InstallShield, I am trying to write a Custom Action to set my folder to the correct path.

Here is what I have done.

I created a folder in the Files view (Destination Computer's Folders pane) called CommonDocsAll, underneath it, I have a folder named "AppName". I placed all the files that should be in that folder there (Destination Computer's Files pane). Then I went to the Custom Actions view and created a custom action under "After Initialization (before first dialog)". I named the custom action "FindCommonDocsFolder".
Here are it's properties:
Source Location: Browse File System
File Name: \FindSpecialFolder.vbs
Function Name: GetFolderPath
Wait for Action: Yes
Ignore Exit Code: Yes
Comments:
Condition: No Conditions

Here is the code in the FindSpecialFolder.vbs file:

Option Explicit

Private Const CSIDL_COMMON_DOCUMENTS = &H2E

Private Const SHGFP_TYPE_CURRENT = &H0
Private Const SHGFP_TYPE_DEFAULT = &H1

Private Const MAX_LENGTH = 260
Private Const S_OK = 0
Private Const S_FALSE = 1

Private Declare Function SHGetFolderPath Lib "shfolder.dll" _
Alias "SHGetFolderPathA" _
(ByVal hwndOwner As Long, _
ByVal nFolder As Long, _
ByVal hToken As Long, _
ByVal dwReserved As Long, _
ByVal lpszPath As String) As Long

Private Function GetFolderPath()

Dim buff As String
buff = Space$(MAX_LENGTH)

If SHGetFolderPath(0, CSIDL_COMMON_DOCUMENTS, 0, SHGFP_TYPE_CURRENT, buff) = S_OK Then
MsiSetTargetPath(hMSI, "CommonDocsAll", TrimNull(buff))
GetFolderPath = S_OK
Else
GetFolderPath = S_FALSE
End If

End Function

Private Function TrimNull(startstr As String) As String
TrimNull = Left$(startstr, InStr(startstr, Chr(0)) - 1)
End Function


I don't get any errors from this code but, the path to the folder is not changed. On my Vista test machine, the "AppName" folder is installed like this C:\CommonDocsAll\AppName.
I wanted the files to be installed like this C:\Users\Public\Documents\AppName

What am I doing wrong? How can I set that folder to the correct path?
How can I point the installation to the "CommonDocsAll" path I have set?

Thanks, in advance, for any help you can give.
Labels (1)
0 Kudos
(13) Replies
hidenori
Level 17

In the 2nd parameter of the MsiSetTargetPath API, you need to specify the folder identifier which is a primary key in the Directory table. It would probably be COMMONDOCSALL in your case. To verify it, you need to check the Location: property on the Properties dialog that can be launched from the Properties right-click menu on the folder.
0 Kudos
rogdawg
Level 4

Thanks very much for responding.

I am not sure I understand what you are saying.

The folder identifier I should specify should be the key value in the Directory Table? That is the directory table in the installer, right?

If I right-click on the CommonDocsAll folder that I defined in the Files view ("Destination computer's folders" pane), then I do see "Location: [COMMONDOCSALL]".

Also, to update:
I changed the way I am implementing this...I created a .dll and I now point my Custom Action to the dll. I am using Function Signature = New. here is the code from the dll: (FindPublicDocsFolder.dll)

Public Function GetFolderPath(ByVal hMSI As Long) As Long
Dim buff As String
buff = Space$(MAX_LENGTH)

If SHGetFolderPath(0, CSIDL_COMMON_DOCUMENTS, 0, SHGFP_TYPE_CURRENT, buff) = S_OK Then
MsiSetTargetPath hMSI, "COMMONDOCSALL", TrimNull(buff)
GetFolderPath = -1
Else
GetFolderPath = 0
End If
End Function

Private Function TrimNull(startstr As String) As String
TrimNull = Left$(startstr, InStr(startstr, Chr(0)) - 1)
End Function

The code compiles. But, when I try to do the install, I get this:
"Can not find the entry point of function 'GetFolderPath', make sure it is exported."

I wrote the dll in VB. Do I need to include some additional file? FindPublicDocsFolder.exp perhaps? If so, where/how do I do that?

Thanks again for any help you can provide.
0 Kudos
hidenori
Level 17

COMMONDOCSALL is the key of the Directory table for the CommonDocsAll folder, and that is what you need to specify in the MsiSetTargetPath API. Since DLLs that are built using VB do not export functions, the custom action cannot call your function. You need to write your DLL in C/C++ or use a VBScript custom action to get it working.
0 Kudos
rogdawg
Level 4

Thanks very much for your help.

I originally implemented my custom action with vbscript, and switched to using a dll when the script didn't work properly. I will switch back to the script, now that I have a better understanding. Hopefully I can get it to work now.

Thanks again.
0 Kudos
rogdawg
Level 4

In my VBScript, if I use the MsiSetTargetPath function, I will need to pass in the handle to the .msi database. I understood that, if I was calling a .dll, I could use the "New" function signature to pass the handle into my named function, and I could have that function require the handle as an argument.

If I use VBScript, how can I pass the handle to the .msi database into my script, so I can use the MsiSetTargetPath function? I haven't seen an example of passing arguments to VBScripts. Or, can I refer to "COMMONDOCSALL" using the Session.Property() functionality, which doesn't require the handle?

Thanks again for your help.

UPDATE:
I have verified that using Session.Property("COMMONDOCSALL") does not work. So, I guess I need to figure out a way to pass the .msi handle into my VBScript.
0 Kudos
rogdawg
Level 4

What a miserable experience this is!!!
0 Kudos
hidenori
Level 17

Try using Session.TargetPath as the equivalent to MsiSetTargetPath. It should just work like this:

Session.TargetPath("COMMONDOCSALL") = "C:\Somewhere"
0 Kudos
rogdawg
Level 4

Here is my script:

Option Explicit

Private Const CSIDL_COMMON_DOCUMENTS = &H2E

Private Const SHGFP_TYPE_CURRENT = &H0
Private Const SHGFP_TYPE_DEFAULT = &H1

Private Const MAX_LENGTH = 260
Private Const S_OK = 0
Private Const S_FALSE = 1

Private Declare Function SHGetFolderPath Lib "shfolder.dll" _
Alias "SHGetFolderPathA" _
(ByVal hwndOwner As Long, _
ByVal nFolder As Long, _
ByVal hToken As Long, _
ByVal dwReserved As Long, _
ByVal lpszPath As String) As Long

Public Function GetFolderPath() As Long

Dim buff As String
buff = Space$(MAX_LENGTH)

If SHGetFolderPath(0, CSIDL_COMMON_DOCUMENTS, 0, SHGFP_TYPE_CURRENT, buff) = S_OK Then
Session.TargetPath("COMMONDOCSALL") = TrimNull(buff)
GetFolderPath = 1
Else
GetFolderPath = 0
End If

End Function

Private Function TrimNull(startstr As String) As String
TrimNull = Left$(startstr, InStr(startstr, Chr(0)) - 1)
End Function


I am attaching a screen shot ()of the directory tree from the Files view. If I right-click on the CommonDocsAll folder, I see that Location: "COMMONDOCSALL".

Here is my Custom Action view:


It seems to me that this should work. I must be doing something really stupid.
Thank you for your patience, and any further help you can give.
0 Kudos
hidenori
Level 17

You need to translate your VB code to VBScript. Try this one and see if it works:

Const ssfCOMMONDOCUMENTS = &H2E
Const ssfCOMMONAPPDATA = &H23
Const ssfAPPDATA = &H1A

Function GetFolderPath()

If getSHSF(ssfCOMMONDOCUMENTS, sPath) Then
Session.TargetPath("COMMONDOCSALL") = sPath
GetFolderPath = 1
Else
GetFolderPath = 0
End If

End Function

Function getSHSF(ssfConst,sPRet)

Set oSHA = CreateObject("Shell.Application")
Set oSpecFold = oSHA.Namespace (ssfConst)

If TypeName(oSpecFold) = "Nothing" then
getSHSF = false : sPRet = ""
Else
getSHSF = true : sPRet = oSpecFold.Self.Path 'XP/2000/ME only
End If

End Function
0 Kudos
rogdawg
Level 4

Wow! Your code is very different from my implementation.

I will have to wait until tomorrow morning to get into the office and try your code.

I can't thank you enough for your help with this problem. I have tried to find out as much as I could about this issue but I am obviously way off base.

Thank you for all your work on this.

I will respond in the morning as soon as I have tested your code.
0 Kudos
rogdawg
Level 4

What happens when this code runs on a Vista machine? Your comment ('XP/2000/Me only) leads me to believe that this code won't work on Vista. Is that correct?

The entire reason I am doing this is to get my app to run on Vista by putting certain files in the Common_Documents folder.

Thanks agian.
0 Kudos
hidenori
Level 17

I borrowed the code from this page, and it is actually working on my Vista test box. You cannot use the SHGetFolderPath function in your code because VBScript cannot call exported DLL functions. For more information, please look at this page, and/or search the word for "VBScript COMMONAPPDATA" or "VBScript Common_Documents" to find code like that.
0 Kudos
rogdawg
Level 4

I can't thank you enough for your help. Your script works perfectly on the Vista test machine I have.

What a relief!

I had actually spent the morning writing a .dll in C++ (its been a long time since I did any of that!) but, I will use your script instead. My C++ still needs a little more work, and the VBScript seems much more straight-forward.

Thank you for all your continued work with this. You really went above and beyond the call of duty!

I will continue looking at this, though. I am disappointed that I wasn't able to find the answer myself but, your code looks very different from any examples I encountered. My searching skills must need some work.

Thanks again. You are a life-saver.
0 Kudos