Calling SHGetKnownFolderPath in VFP - Microsoft: FoxPro (2024)

INTELLIGENT WORK FORUMS
FOR COMPUTER PROFESSIONALS

Thanks. We have received your request and will respond promptly.

Log In

Come Join Us!

Are you a
Computer / IT professional?
Join Tek-Tips Forums!

  • Talk With Other Members
  • Be Notified Of Responses
    To Your Posts
  • Keyword Search
  • One-Click Access To Your
    Favorite Forums
  • Automated Signatures
    On Your Posts
  • Best Of All, It's Free!

Join Us!

*Tek-Tips's functionality depends on members receiving e-mail. By joining you are opting in to receive e-mail.

Posting Guidelines

Promoting, selling, recruiting, coursework and thesis posting is forbidden.

Students Click Here

Tek-Tips Posting Policies

Contact US

thread184-1723041

ForumSearchFAQsLinksMVPs

Calling SHGetKnownFolderPath in VFP

Calling SHGetKnownFolderPath in VFP

RobSpencer (Programmer)

(OP)

In thread184-1539853: Installed database location? Olaf listed links to the Microsoft documentation page for SHGetKnownFolderPath but I can't work out how to call it as the first parameter is a reference to a GUID. It's the only function I know that will give me access to the full array of pre-defined folders in Windows 8.1 (eg Skydrive Pictures and Camera Roll).

Has anyone got this to work in VFP?

Rob Spencer
Caliptor Pty Ltd
http://www.caliptor.com.au/

RE: Calling SHGetKnownFolderPath in VFP

Olaf Doschke (Programmer)

The GUIDS are listed in the referenced Link on KNONFOLDERIDs:
http://msdn.microsoft.com/en-us/library/dd378457(v...

MSDN articles are well documented, so what is your problem, really?

Bye, Olaf.

RE: Calling SHGetKnownFolderPath in VFP

RobSpencer (Programmer)

(OP)

What I need is a working declare statement and maybe an example :)

This is my most recent try...

CODE --> VFP

#define k_FID_CAMERAROLL"{AB5FB87B-7CE2-4F83-915D-550846C9537B}"#define k_FID_DESKTOP"{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}"#define k_FID_DOCUMENTS"{FDD39AD0-238F-46AF-ADB4-6C85480369C7}"#define k_FID_PICTURES"{33E28130-4E1E-4676-835A-98395C3BC3BB}"#define k_FID_SKYCAMERAROLL"{767E6811-49CB-4273-87C2-20F355E1085B}"#define k_FID_SKYDOCUMENTS"{24D89E24-2F19-4534-9DDE-6A6671FBB8FE}"#define k_FID_SKYPICTURES"{339719B5-8C47-4894-94C2-D8F77ADD44A6}"declare short FormatMessage ; in win32api ; integer dwFlags ,; integer lpvSource,; integer dwMsgId,; integer dwLangId,; STRING @lpBuffer,; integer nSize,; integer Argumentdeclare short SHGetKnownFolderPath ;in shell32.dll ;string @ cRFID,;integer dwFlags,;integer hToken,;string @ ppszPathcDefFoldID= k_FID_DESKTOPcInBuff= space(512)nResult= SHGetKnownFolderPath(@cDefFoldID, 0x2000 + 0x4000, 0, @cInBuff)if m.nResult # 0nSize= FormatMessage(0x1000, 0, m.nResult, 0, @cInBuff, len(cInBuff), 0)? left(m.cInBuff, m.nSize)endif 

This outputs "The system cannot find the file specified." which I doubt is because I don't have one as I can get the desktop folder from WScript.Shell object.

Hope you can help. I was hoping you'd see this Olaf :)

Rob Spencer
Caliptor Pty Ltd
http://www.caliptor.com.au/

RE: Calling SHGetKnownFolderPath in VFP

Mike Lewis (Programmer)

Rob,

There's an example of a fully-working function, along with a list of the GUIDs, here:

Finding the paths to Windows' special folders in VFP

However, the list of GUIDs doesn't mention the Windows 8-specific folders, such as CameraRoll as far as I can see.

Mike
P.S. Welcome to the forum.

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads

RE: Calling SHGetKnownFolderPath in VFP

Olaf Doschke (Programmer)

Nice Rob,

don't forget the mention of CoTaskMemFree.

The SHGetKnownFolderPath just allocates a few bytes, and you may never had any problems with non freed orphaned memory, but it's a potential risc.
The way you defined the API call you never know the address of cInBuff to be able to free it. Indeed VFP copies the string from where the OS API function stores it to the cInBuff memory allocated by VFP and you never get the address. Instead you would define the parameter as LONG and either make use of SYS(2600) or RtlMoveMemory.

Bye, Olaf.

RE: Calling SHGetKnownFolderPath in VFP

RobSpencer (Programmer)

(OP)

Thanks Mike. I looked at your code and it is an easier way of doing it than I was using so I've modified my code. Using your method I avoid having to use sys(2600) (Thanks Olaf for pointing it out though :))

Sadly it doesn't help me as I still get the same result :(

The new version of that function SHGetKnownFolderIDList() also takes a REFKNOWNFOLDERID which is a reference to a KNOWNFOLDERID. Most of the entries that I'm interested in don't have CSIDL equivalents so I can't use the older form of the call.

Again, I've included my reworked code (sorry, I've wrapped the error handler which makes it look more complicated but it isn't really).

CODE --> VFP

**********************************************function getKnownFolderPath (cDefFoldID as string) as string**********************************************local ok, nOutBuff, cErrMsg, cOutStrif vartype(__G_DLL_SHGETKNOWNFOLDERIDLIST_) = "U"declare long SHGetKnownFolderIDList ;in shell32.dll ;string @ cRFID,;long dwFlags,;long hToken,;long @ ppidlpublic __G_DLL_SHGETKNOWNFOLDERIDLIST_endifif vartype(__G_DLL_SHGETPATHFROMIDLIST_) = "U"declare short SHGetPathFromIDList ;in shell32.dll ;long ppidl, ;string @ cPathpublic __G_DLL_SHGETPATHFROMIDLIST_endifif vartype(__G_DLL_COTASKMEMFREE_) = "U"declare CoTaskMemFree ;in ole32.dll ;long pVoidpublic __G_DLL_COTASKMEMFREE_endifok= .f.&& Pessimist!nOutBuff= 0cErrMsg= checkSysError(SHGetKnownFolderIDList(@m.cDefFoldID, 0x2000 + 0x4000, 0, @nOutBuff))if empty(m.cErrMsg)cOutStr= space(512)cErrMsg= checkSysError(SHGetPathFromIDList(m.nOutBuff, @cOutStr))if empty(m.cErrMsg)cOutStr= left(m.cOutStr, at(chr(0), m.cOutStr) - 1)ok= .t.endifendifif ! m.ok? m.cErrMsgendif* Clean up the memory used in the above callsCoTaskMemFree(m.nOutBuff)return iif(m.ok, m.cOutStr, "")endfunc&& getKnownFolderPath()**********************************************function checkSysError (nResult as integer) as string**********************************************local cMsg, cInBuff, nSizeif empty(m.nResult) or m.nResult = 1&& covers missing parameter, 0 (S_OK) or 1 (S_FALSE)* It must have worked :)cMsg= ""elseif vartype(__G_DLL_FORMATMESSAGE_) = "U"declare long FormatMessage ;in win32api ;long dwFlags ,;long lpvSource,;long dwMsgId,;long dwLangId,;string @lpBuffer,;long nSize,;long Argumentpublic __G_DLL_FORMATMESSAGE_endifcInBuff= space(1024)nSize= FormatMessage(0x1000, 0, m.nResult, 0, @cInBuff, len(m.cInBuff), 0)if m.nSize > 0cMsg= left(m.cInBuff, m.nSize)elsecMsg= "Unknown error - code " + ltrim(str(m.nResult))endifendifreturn m.cMsgendfunc&& checkSysError()* Sample call? GetKnownFolderPath("{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}") 

The sample call should return my desktop folder but simply outputs "The system cannot find the file specified." which is odd.

I'm at a loss on what I'm doing wrong. Any help would be very gratefully accepted :)

Rob Spencer
Caliptor Pty Ltd
http://www.caliptor.com.au/

RE: Calling SHGetKnownFolderPath in VFP

Olaf Doschke (Programmer)

I think the solution is easy: You have to provide the GUID in binary form, not as hex string. I haven't tried, though.

When you create a guid you get a GUID in binary form and can format it in the hex form we all know, there also should be an inverse API call to take a formatted GUID string and return it in 16 byte binary form. Be warned: The byte order is not the same as in the hexstring, so a hex to binary conversion of the string without the curly braces and minuses won't work, you have to find the API call.

Bye, Olaf.

RE: Calling SHGetKnownFolderPath in VFP

RobSpencer (Programmer)

(OP)

Thanks Olaf. One step closer but still no cigar!

Latest code listed below. I've converted the GUID string to UNICODE and null terminated it to get a successful CLSIDFromString() result. You'll notice that I've replaced the GUID parameter with a double in both of the first two calls. If I change both references to long, I get a "Declare DLL call caused an exception" when it runs the SHGetKnownFolderIDList() call. The nGUID parameter is negative (but I'm not sure that I care since I'm just passing on what I get given back by the previous call???? I only care because it crashes!). With the parameter declared as a double, I get a "The parameter is incorrect" result instead but at least that is a return code from the call.

I have tried several other variations but nothing that I've tried has helped get any closer.

CODE --> VFP

**********************************************function getKnownFolderPath (cDefFoldID as string) as string**********************************************local ok, nOutBuff, cErrMsg, cOutStr, cUCStr, nGUIDif vartype(__G_DLL_CLSIDFROMSTRING_) = "U"declare long CLSIDFromString ;in ole32.dll ;string cGUID, ;double @ nRFIDpublic __G_DLL_CLSIDFROMSTRING_endifif vartype(__G_DLL_SHGETKNOWNFOLDERIDLIST_) = "U"declare long SHGetKnownFolderIDList ;in shell32.dll ;double nRFID,;long dwFlags,;long hToken,;long @ ppidlpublic __G_DLL_SHGETKNOWNFOLDERIDLIST_endifif vartype(__G_DLL_SHGETPATHFROMIDLISTW_) = "U"declare long SHGetPathFromIDListW ;in shell32.dll ;long ppidl, ;string @ cPathpublic __G_DLL_SHGETPATHFROMIDLISTW_endifif vartype(__G_DLL_COTASKMEMFREE_) = "U"declare CoTaskMemFree ;in ole32.dll ;long pVoidpublic __G_DLL_COTASKMEMFREE_endifok= .f.&& Pessimist!nGUID= 0cUCStr= strconv(m.cDefFoldID, 5) + chr(0)cErrMsg= checkSysError(CLSIDFromString(m.cUCStr, @nGUID))if empty(m.cErrMsg)nOutBuff= 0cErrMsg= checkSysError(SHGetKnownFolderIDList(m.nGUID, 0x2000 + 0x4000, 0, @nOutBuff))endifif empty(m.cErrMsg)cOutStr= space(512)cErrMsg= checkSysError(SHGetPathFromIDListW(m.nOutBuff, @cOutStr))if empty(m.cErrMsg)cOutStr= left(m.cOutStr, at(chr(0), m.cOutStr) - 1)ok= .t.endifendifif ! m.ok? m.cErrMsgendif* Clean up the memory used in the above callsCoTaskMemFree(m.nOutBuff)return iif(m.ok, m.cOutStr, "")endfunc&& getKnownFolderPath()**********************************************function checkSysError (nResult as integer) as string**********************************************local cMsg, cInBuff, nSizeif empty(m.nResult) or m.nResult = 1&& covers missing parameter, 0 (S_OK) or 1 (S_FALSE)* It must have worked :)cMsg= ""elseif vartype(__G_DLL_FORMATMESSAGE_) = "U"declare long FormatMessage ;in win32api ;long dwFlags ,;long lpvSource,;long dwMsgId,;long dwLangId,;string @lpBuffer,;long nSize,;long Argumentpublic __G_DLL_FORMATMESSAGE_endifcInBuff= space(1024)nSize= FormatMessage(0x1000, 0, m.nResult, 0, @cInBuff, len(m.cInBuff), 0)if m.nSize > 0cMsg= left(m.cInBuff, m.nSize)elsecMsg= "Unknown error - code " + ltrim(str(m.nResult))endifendifreturn m.cMsgendfunc&& checkSysError() 

Sorry to still be asking for help :( I should be able to sort this out for myself given the advice I've received so far.

Thanks again for your help.

Rob Spencer
Caliptor Pty Ltd
http://www.caliptor.com.au/

RE: Calling SHGetKnownFolderPath in VFP

Olaf Doschke (Programmer)

16 byte is too long for a long, you have to keep that at String. Just because it's binary now, it's not a number, not an int, not a long, not a double, it's 16 bytes memory, and that as transported by a string, also in many, many other API function calls.

In regard to the output I still got only "C" as a first result, but that's because you use the UNICODE variant SHGetPathFromIDListW, in VFP use SHGetPathFromIDListA. You're cuting off at the first chr(0), but chr(0) are just normal for Unicode strings, normal letters cause each second byte of the unicode string to be chr(0).

Overall I changed your code and this works on Win7 (it's the temp path for CD burning data), you might put in a Win8 or Win 8.1 GUID to see yourself, if you get these paths, too, now.

CODE -->

? getKnownFolderPath('{9E52AB10-F80D-49DF-ACB8-4330F5687855}')**********************************************function getKnownFolderPath (cDefFoldID as string) as string**********************************************local ok, nOutBuff, cErrMsg, cOutStr, cUCStr, nGUIDif vartype(__G_DLL_CLSIDFROMSTRING_) = "U"declare long CLSIDFromString ;in ole32.dll ;string cGUID, ;string @ cCLSIDpublic __G_DLL_CLSIDFROMSTRING_endifif vartype(__G_DLL_SHGETKNOWNFOLDERIDLIST_) = "U"declare long SHGetKnownFolderIDList ;in shell32.dll ;string cbCLSID,;long dwFlags,;long hToken,;long @ ppidlpublic __G_DLL_SHGETKNOWNFOLDERIDLIST_endifif vartype(__G_DLL_SHGETPATHFROMIDLISTA_) = "U"declare long SHGetPathFromIDListA ;in shell32.dll ;long ppidl, ;string @ cPathpublic __G_DLL_SHGETPATHFROMIDLISTA_endifif vartype(__G_DLL_COTASKMEMFREE_) = "U"declare CoTaskMemFree ;in ole32.dll ;long pVoidpublic __G_DLL_COTASKMEMFREE_endifok= .f.&& Pessimist!cbGUID= Space(16)cUCStr= strconv(m.cDefFoldID, 5) + chr(0)cErrMsg= checkSysError(CLSIDFromString(m.cUCStr, @cbGUID))if empty(m.cErrMsg)nOutBuff= 0cErrMsg= checkSysError(SHGetKnownFolderIDList(m.cbGUID, 0x2000 + 0x4000, 0, @nOutBuff))endifif empty(m.cErrMsg)cOutStr= space(512)cErrMsg= checkSysError(SHGetPathFromIDListA(m.nOutBuff, @cOutStr))if empty(m.cErrMsg)*cOutStr= left(m.cOutStr, at(chr(0), m.cOutStr) - 1)ok= .t.endifendifif ! m.ok? m.cErrMsgendif* Clean up the memory used in the above callsCoTaskMemFree(m.nOutBuff)return iif(m.ok, m.cOutStr, "")endfunc&& getKnownFolderPath()**********************************************function checkSysError (nResult as integer) as string**********************************************local cMsg, cInBuff, nSizeif empty(m.nResult) or m.nResult = 1&& covers missing parameter, 0 (S_OK) or 1 (S_FALSE)* It must have worked :)cMsg= ""elseif vartype(__G_DLL_FORMATMESSAGE_) = "U"declare long FormatMessage ;in win32api ;long dwFlags ,;long lpvSource,;long dwMsgId,;long dwLangId,;string @lpBuffer,;long nSize,;long Argumentpublic __G_DLL_FORMATMESSAGE_endifcInBuff= space(1024)nSize= FormatMessage(0x1000, 0, m.nResult, 0, @cInBuff, len(m.cInBuff), 0)if m.nSize > 0cMsg= left(m.cInBuff, m.nSize)elsecMsg= "Unknown error - code " + ltrim(str(m.nResult))endifendifreturn m.cMsgendfunc&& checkSysError() 

See for yourself what I changed, I haven't marked it, sorry, I was too lazy. It's several places.

Bye, Olaf.

RE: Calling SHGetKnownFolderPath in VFP

Olaf Doschke (Programmer)

In regard to CLSIDFromString usage, also see http://www.news2news.com/vfp/?example=456&func...
You don't need to add chr(0) as terminator to a unicode string. By the way also not to other strings. In most cases parameters are defined string VFP making the real API call adds a chr(0) anyway.

Taken from news2news:

CODE

DO declare LOCAL cGUID, cGUIDString, cGUID1cGUID = REPLICATE(CHR(0), 16) && 128 bits IF CoCreateGuid(@cGUID) = 0 cGUIDString = StringFromGUID(cGUID) ? cGUID ? cGUIDString * converting from String back to GUID cGUID1 = REPLICATE(CHR(0), 16) * Olaf Doschke: Here you see how they called CLSIDFromString, also using STRCONV(...,5) for unicode, but not +chr(0) = CLSIDFromString(STRCONV(cGUIDString,5), @cGUID1) ? cGUID1ENDIF* end of main FUNCTION StringFromGUID(cGUID) LOCAL cBuffer, nBufsize nBufsize=128 cBuffer = REPLICATE(CHR(0), nBufsize*2) = StringFromGUID2(cGUID, @cBuffer, nBufsize) cBuffer = SUBSTR(cBuffer, 1, AT(CHR(0)+CHR(0), cBuffer))RETURN STRCONV(cBuffer, 6) PROCEDURE declare DECLARE INTEGER CoCreateGuid IN ole32 STRING @pguid DECLARE INTEGER CLSIDFromString IN ole32; STRING lpsz, STRING @pclsid DECLARE INTEGER StringFromGUID2 IN ole32; STRING rguid, STRING @lpsz, INTEGER cchMax 

Bye, Olaf.

RE: Calling SHGetKnownFolderPath in VFP

2

RobSpencer (Programmer)

(OP)

Hi Olaf,

Thanks so much. Yes, the use of the SHGetPathFromIDListW version of the function was a left-over from some "clutching at straws" and it should have simply been the basic version. The information about the added chr(0) came from http://www.foxpert.com/knowlbits_200709.htm and in fact the behaviour I've found is that it is required. Without it, I was able to run it once but not multiple times (I'd get "Invalid class string" errors from subsequent calls).

I've tidied up the code including some basic documentation and included it below.

CODE --> VFP

* Define constants to use with SHGetKnownFolderIDList routine* Values copied from MSDN reference site (although not the names)#define k_FID_CAMERAROLL"{AB5FB87B-7CE2-4F83-915D-550846C9537B}"#define k_FID_DESKTOP"{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}"#define k_FID_DOCUMENTS"{FDD39AD0-238F-46AF-ADB4-6C85480369C7}"#define k_FID_PICTURES"{33E28130-4E1E-4676-835A-98395C3BC3BB}"#define k_FID_SKYCAMERAROLL"{767E6811-49CB-4273-87C2-20F355E1085B}"#define k_FID_SKYDOCUMENTS"{24D89E24-2F19-4534-9DDE-6A6671FBB8FE}"#define k_FID_SKYPICTURES"{339719B5-8C47-4894-94C2-D8F77ADD44A6}"? GetKnownFolderPath(k_FID_SKYDOCUMENTS)**********************************************function getKnownFolderPath (cDefFoldID as string, cErrMsg as string) as string**********************************************local nOutBuff, cOutStr, cbGUID* Declare all the required system calls (protected for repeat calls)if vartype(__G_DLL_CLSIDFROMSTRING_) = "U"declare long CLSIDFromString ;in ole32.dll ;string cInStr, ;string @ cGUIDpublic __G_DLL_CLSIDFROMSTRING_endifif vartype(__G_DLL_SHGETKNOWNFOLDERIDLIST_) = "U"declare long SHGetKnownFolderIDList ;in shell32.dll ;string cGUID,;long dwFlags,;long hToken,;long @ ppidlpublic __G_DLL_SHGETKNOWNFOLDERIDLIST_endifif vartype(__G_DLL_SHGETPATHFROMIDLIST_) = "U"declare long SHGetPathFromIDList ;in shell32.dll ;long ppidl, ;string @ cPathpublic __G_DLL_SHGETPATHFROMIDLIST_endifif vartype(__G_DLL_COTASKMEMFREE_) = "U"declare CoTaskMemFree ;in ole32.dll ;long pVoidpublic __G_DLL_COTASKMEMFREE_endif* Convert the string GUID to a binary versioncbGUID= space(16)cErrMsg= checkSysError(CLSIDFromString(strconv(m.cDefFoldID, 5) + chr(0), @cbGUID))if empty(m.cErrMsg)* Get back the folder path in a PIDL structure* Flags - 0x1000 -> KF_FLAG_NO_ALIAS - Expand any alias placeholders, eg. %USERPROFILE%* - 0x4000 -> KF_FLAG_DONT_VERIFY - Don't check that the folder exists before returning the valuenOutBuff= 0cErrMsg= checkSysError(SHGetKnownFolderIDList(m.cbGUID, 0x1000 + 0x4000, 0, @nOutBuff))endifif empty(m.cErrMsg)* Extract the path string from the PIDL structurecOutStr= space(512)cErrMsg= checkSysError(SHGetPathFromIDList(m.nOutBuff, @cOutStr))endif* Clean up the memory used in the above system callsCoTaskMemFree(m.nOutBuff)* Ensure any returned path has the trailing backslashreturn iif(empty(m.cErrMsg), addbs(m.cOutStr), "")endfunc&& getKnownFolderPath()**********************************************function checkSysError (nResult as integer) as string**********************************************local cMsg, cInBuff, nSizeif empty(m.nResult) or m.nResult = 1&& covers missing parameter, 0 (S_OK) or 1 (S_FALSE)* It must have worked :)cMsg= ""else* Declare the required system call (protected for repeat calls)if vartype(__G_DLL_FORMATMESSAGE_) = "U"declare long FormatMessage ;in win32api ;long dwFlags ,;long lpvSource,;long dwMsgId,;long dwLangId,;string @lpBuffer,;long nSize,;long Argumentpublic __G_DLL_FORMATMESSAGE_endif* Retrieve the system message* Flags - 0x1000 -> FORMAT_MESSAGE_FROM_SYSTEM Use the system message-tablecInBuff= space(1024)nSize= FormatMessage(0x1000, 0, m.nResult, 0, @cInBuff, len(m.cInBuff), 0)if m.nSize > 0cMsg= left(m.cInBuff, m.nSize)else* No message came back - maybe it is a application specific messagecMsg= "Unknown error - code " + ltrim(str(m.nResult))endifendifreturn m.cMsgendfunc&& checkSysError() 

On my system it returns C:\Users\Rob\SkyDrive\Documents\ which is exactly what I was looking for. Hoping to use the application on a Surface Pro 2.

Thank you all so much for the help, especially Olaf :)

Rob Spencer
Caliptor Pty Ltd
http://www.caliptor.com.au/

RE: Calling SHGetKnownFolderPath in VFP

Olaf Doschke (Programmer)

Nice hint on the foxperts article.

I'm sure Christof is right. Indeed in this case you have a 16 byte buffer turning to 32 bytes unicode, and VFP adds a CHR(0), which makes it 33 bytes and an odd number of bytes is often bad, sometimes your total length should even be multiple of longs, multiples of 4 bytes, to have an aligned memory addres. Perhaps news2news should then update their routine.

Glad it works and thanks for sharing the brushed up version.

Bye, Olaf.

Red Flag Submitted

Thank you for helping keep Tek-Tips Forums free from inappropriate posts.
The Tek-Tips staff will check this out and take appropriate action.

Reply To This Thread

Posting in the Tek-Tips forums is a member-only feature.

Click Here to join Tek-Tips and talk with other members! Already a Member? Login


Calling SHGetKnownFolderPath in VFP - Microsoft: FoxPro (2024)
Top Articles
Latest Posts
Article information

Author: Terrell Hackett

Last Updated:

Views: 6009

Rating: 4.1 / 5 (72 voted)

Reviews: 87% of readers found this page helpful

Author information

Name: Terrell Hackett

Birthday: 1992-03-17

Address: Suite 453 459 Gibson Squares, East Adriane, AK 71925-5692

Phone: +21811810803470

Job: Chief Representative

Hobby: Board games, Rock climbing, Ghost hunting, Origami, Kabaddi, Mushroom hunting, Gaming

Introduction: My name is Terrell Hackett, I am a gleaming, brainy, courageous, helpful, healthy, cooperative, graceful person who loves writing and wants to share my knowledge and understanding with you.