{"id":20261333,"url":"https://github.com/parsiya/golnk","last_synced_at":"2025-04-11T01:45:09.518Z","repository":{"id":57489976,"uuid":"156171314","full_name":"parsiya/golnk","owner":"parsiya","description":"Golang package for parsing Windows shell link binary (lnk or Windows shortcut) files.","archived":false,"fork":false,"pushed_at":"2022-11-03T09:51:32.000Z","size":133,"stargazers_count":37,"open_issues_count":2,"forks_count":15,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-24T22:51:21.533Z","etag":null,"topics":["go","golang","lnk-files"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/parsiya.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-05T06:32:48.000Z","updated_at":"2024-12-18T21:52:01.000Z","dependencies_parsed_at":"2022-09-02T10:51:09.210Z","dependency_job_id":null,"html_url":"https://github.com/parsiya/golnk","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parsiya%2Fgolnk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parsiya%2Fgolnk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parsiya%2Fgolnk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parsiya%2Fgolnk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/parsiya","download_url":"https://codeload.github.com/parsiya/golnk/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248327863,"owners_count":21085258,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","lnk-files"],"created_at":"2024-11-14T11:25:18.443Z","updated_at":"2025-04-11T01:45:09.501Z","avatar_url":"https://github.com/parsiya.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lnk - lnk Parser for Go\r\nlnk is a package for parsing Windows Shell Link (.lnk) files.\r\n\r\nIt's based on version 5.0 of the [MS-SHLLINK] document:\r\n\r\n* Reference: https://msdn.microsoft.com/en-us/library/dd871305.aspx\r\n* Version 5.0: https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-SHLLINK/[MS-SHLLINK].pdf\r\n\r\nIf the lnk file does not adhere to this specification (either corrupted or from an earlier version), it might not be parsed.\r\n\r\n## Shell Link Structure\r\nEach file has at least one header (`SHELL_LINK_HEADER`) and one or more optional sections. \r\n\r\n```\r\nSHELL_LINK = SHELL_LINK_HEADER [LINKTARGET_IDLIST] [LINKINFO]\r\n              [STRING_DATA] *EXTRA_DATA\r\n```\r\n\r\nThe existence of these sections are defined by the `LinkFlags` uint32 in the header (mapped to `HEADER.LinkFlags`). To see all flags, look at `linkFlags` in [header.go](header.go).\r\n\r\nNote about size fields: \"Unless otherwise specified, the value contained by size fields includes the size of size field itself.\"\r\n\r\nCurrently lnk parses every section except `EXTRA_DATA`. Different data blocks are identified and stored but it does not parse any of them other than identifying the type (via their signature) and storing the content. Data blocks are defined in section 2.5 of the specification.\r\n\r\n## Setup\r\nPackage has only one dependency: https://github.com/olekukonko/tablewriter. It's used to create tables in section stringers.\r\n\r\n## Usage\r\nPass a filename to `lnk.File` or an `io.Reader` with its contents to `lnk.Read`. Both return `LnkFile`:\r\n\r\n``` go\r\ntype LnkFile struct {\r\n\tHeader     ShellLinkHeaderSection  // File header.\r\n\tIDList     LinkTargetIDListSection // LinkTargetIDList.\r\n\tLinkInfo   LinkInfoSection         // LinkInfo.\r\n\tStringData StringDataSection       // StringData.\r\n\tDataBlocks ExtraDataSection        // ExtraData blocks.\r\n}\r\n```\r\n\r\nEach section is a struct that is populated. See their fields in their respective source files.\r\n\r\n``` go\r\npackage main\r\n\r\nimport (\r\n\t\"fmt\"\r\n\r\n\t\"github.com/parsiya/golnk\"\r\n)\r\n\r\nfunc main() {\r\n\r\n\tLnk, err := lnk.File(\"test.lnk\")\r\n\tif err != nil {\r\n\t\tpanic(err)\r\n\t}\r\n\r\n\t// Print header.\r\n\tfmt.Println(Lnk.Header)\r\n\r\n\t// Path to the target file is usually in LinkInfo.LocalBasePath.\r\n\tfmt.Println(\"BasePath\", Lnk.LinkInfo.LocalBasePath)\r\n\r\n\t// fmt.Println(Lnk.LinkInfo)\r\n\r\n\t// fmt.Println(Lnk.StringData)\r\n\r\n\t// fmt.Println(Lnk.DataBlocks)\r\n}\r\n```\r\n\r\n![header printed](img/example01.png)\r\n\r\nEach section has a [Stringer](https://golang.org/pkg/fmt/#Stringer) that prints the fields in a [table](https://github.com/olekukonko/tablewriter).\r\n\r\n![link info printed](img/example02.png)\r\n\r\nExtra Data Blocks are not parsed but can be dumped or accessed manually.\r\n\r\n![extra data block dump](img/example03.png)\r\n\r\n**Parse the Windows start menu and extract the base path for all lnk files.**\r\n\r\nSee [test/parseStartMenu.go](test/parseStartMenu.go):\r\n\r\n``` go\r\npackage main\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"os\"\r\n\t\"path/filepath\"\r\n\r\n\t\"github.com/parsiya/golnk\"\r\n)\r\n\r\n// Sample program to parse all lnk files in the \"All Users\" start menu at\r\n// C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs.\r\n\r\nfunc main() {\r\n\tstartMenu := \"C:/ProgramData/Microsoft/Windows/Start Menu/Programs\"\r\n\tbasePaths := []string{}\r\n\terr := filepath.Walk(startMenu, func(path string, info os.FileInfo, walkErr error) error {\r\n\t\t// Only look for lnk files.\r\n\t\tif filepath.Ext(info.Name()) == \".lnk\" {\r\n\t\t\tf, lnkErr := lnk.File(path)\r\n\t\t\t// Print errors and move on to the next file.\r\n\t\t\tif lnkErr != nil {\r\n\t\t\t\tfmt.Println(lnkErr)\r\n\t\t\t\treturn nil\r\n\t\t\t}\r\n\t\t\tvar targetPath = \"\"\r\n\t\t\tif f.LinkInfo.LocalBasePath != \"\" {\r\n\t\t\t\ttargetPath = f.LinkInfo.LocalBasePath\r\n\t\t\t}\r\n\t\t\tif f.LinkInfo.LocalBasePathUnicode != \"\" {\r\n\t\t\t\ttargetPath = f.LinkInfo.LocalBasePathUnicode\r\n\t\t\t}\r\n\t\t\tif targetPath != \"\" {\r\n\t\t\t\tfmt.Println(\"Found\", targetPath)\r\n\t\t\t\tbasePaths = append(basePaths, targetPath)\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn nil\r\n\t})\r\n\tif err != nil {\r\n\t\tpanic(err)\r\n\t}\r\n\r\n\t// Print everything.\r\n\tfmt.Println(\"------------------------\")\r\n\tfor _, p := range basePaths {\r\n\t\tfmt.Println(p)\r\n\t}\r\n}\r\n```\r\n\r\n## TODO\r\n1. Use `dep`?\r\n2. Identify ExtraDataBlocks.\r\n3. Clean up code.\r\n4. Write more unit tests.\r\n5. Test it on more lnk files.\r\n6. ~~Add a `Data` field to each section and store raw bytes there. Then add a `Dump` method to each section and use `hex.Dump` to dump the raw bytes.~~\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparsiya%2Fgolnk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fparsiya%2Fgolnk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparsiya%2Fgolnk/lists"}