1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/win/shortcut.h"
11 #include "base/file_util.h"
12 #include "base/threading/thread_restrictions.h"
13 #include "base/win/scoped_comptr.h"
14 #include "base/win/scoped_propvariant.h"
15 #include "base/win/win_util.h"
16 #include "base/win/windows_version.h"
23 // Initializes |i_shell_link| and |i_persist_file| (releasing them first if they
24 // are already initialized).
25 // If |shortcut| is not NULL, loads |shortcut| into |i_persist_file|.
26 // If any of the above steps fail, both |i_shell_link| and |i_persist_file| will
28 void InitializeShortcutInterfaces(
29 const wchar_t* shortcut
,
30 ScopedComPtr
<IShellLink
>* i_shell_link
,
31 ScopedComPtr
<IPersistFile
>* i_persist_file
) {
32 i_shell_link
->Release();
33 i_persist_file
->Release();
34 if (FAILED(i_shell_link
->CreateInstance(CLSID_ShellLink
, NULL
,
35 CLSCTX_INPROC_SERVER
)) ||
36 FAILED(i_persist_file
->QueryFrom(*i_shell_link
)) ||
37 (shortcut
&& FAILED((*i_persist_file
)->Load(shortcut
, STGM_READWRITE
)))) {
38 i_shell_link
->Release();
39 i_persist_file
->Release();
45 bool CreateOrUpdateShortcutLink(const FilePath
& shortcut_path
,
46 const ShortcutProperties
& properties
,
47 ShortcutOperation operation
) {
48 base::ThreadRestrictions::AssertIOAllowed();
50 // A target is required unless |operation| is SHORTCUT_UPDATE_EXISTING.
51 if (operation
!= SHORTCUT_UPDATE_EXISTING
&&
52 !(properties
.options
& ShortcutProperties::PROPERTIES_TARGET
)) {
57 bool shortcut_existed
= PathExists(shortcut_path
);
59 // Interfaces to the old shortcut when replacing an existing shortcut.
60 ScopedComPtr
<IShellLink
> old_i_shell_link
;
61 ScopedComPtr
<IPersistFile
> old_i_persist_file
;
63 // Interfaces to the shortcut being created/updated.
64 ScopedComPtr
<IShellLink
> i_shell_link
;
65 ScopedComPtr
<IPersistFile
> i_persist_file
;
67 case SHORTCUT_CREATE_ALWAYS
:
68 InitializeShortcutInterfaces(NULL
, &i_shell_link
, &i_persist_file
);
70 case SHORTCUT_UPDATE_EXISTING
:
71 InitializeShortcutInterfaces(shortcut_path
.value().c_str(), &i_shell_link
,
74 case SHORTCUT_REPLACE_EXISTING
:
75 InitializeShortcutInterfaces(shortcut_path
.value().c_str(),
76 &old_i_shell_link
, &old_i_persist_file
);
77 // Confirm |shortcut_path| exists and is a shortcut by verifying
78 // |old_i_persist_file| was successfully initialized in the call above. If
79 // so, initialize the interfaces to begin writing a new shortcut (to
80 // overwrite the current one if successful).
81 if (old_i_persist_file
.get())
82 InitializeShortcutInterfaces(NULL
, &i_shell_link
, &i_persist_file
);
88 // Return false immediately upon failure to initialize shortcut interfaces.
89 if (!i_persist_file
.get())
92 if ((properties
.options
& ShortcutProperties::PROPERTIES_TARGET
) &&
93 FAILED(i_shell_link
->SetPath(properties
.target
.value().c_str()))) {
97 if ((properties
.options
& ShortcutProperties::PROPERTIES_WORKING_DIR
) &&
98 FAILED(i_shell_link
->SetWorkingDirectory(
99 properties
.working_dir
.value().c_str()))) {
103 if (properties
.options
& ShortcutProperties::PROPERTIES_ARGUMENTS
) {
104 if (FAILED(i_shell_link
->SetArguments(properties
.arguments
.c_str())))
106 } else if (old_i_persist_file
.get()) {
107 wchar_t current_arguments
[MAX_PATH
] = {0};
108 if (SUCCEEDED(old_i_shell_link
->GetArguments(current_arguments
,
110 i_shell_link
->SetArguments(current_arguments
);
114 if ((properties
.options
& ShortcutProperties::PROPERTIES_DESCRIPTION
) &&
115 FAILED(i_shell_link
->SetDescription(properties
.description
.c_str()))) {
119 if ((properties
.options
& ShortcutProperties::PROPERTIES_ICON
) &&
120 FAILED(i_shell_link
->SetIconLocation(properties
.icon
.value().c_str(),
121 properties
.icon_index
))) {
126 (properties
.options
& ShortcutProperties::PROPERTIES_APP_ID
) != 0;
128 (properties
.options
& ShortcutProperties::PROPERTIES_DUAL_MODE
) != 0;
129 if ((has_app_id
|| has_dual_mode
) &&
130 GetVersion() >= VERSION_WIN7
) {
131 ScopedComPtr
<IPropertyStore
> property_store
;
132 if (FAILED(property_store
.QueryFrom(i_shell_link
)) || !property_store
.get())
136 !SetAppIdForPropertyStore(property_store
, properties
.app_id
.c_str())) {
140 !SetBooleanValueForPropertyStore(property_store
,
141 PKEY_AppUserModel_IsDualMode
,
142 properties
.dual_mode
)) {
147 // Release the interfaces to the old shortcut to make sure it doesn't prevent
148 // overwriting it if needed.
149 old_i_persist_file
.Release();
150 old_i_shell_link
.Release();
152 HRESULT result
= i_persist_file
->Save(shortcut_path
.value().c_str(), TRUE
);
154 // Release the interfaces in case the SHChangeNotify call below depends on
155 // the operations above being fully completed.
156 i_persist_file
.Release();
157 i_shell_link
.Release();
159 // If we successfully created/updated the icon, notify the shell that we have
161 const bool succeeded
= SUCCEEDED(result
);
163 if (shortcut_existed
) {
164 // TODO(gab): SHCNE_UPDATEITEM might be sufficient here; further testing
166 SHChangeNotify(SHCNE_ASSOCCHANGED
, SHCNF_IDLIST
, NULL
, NULL
);
168 SHChangeNotify(SHCNE_CREATE
, SHCNF_PATH
, shortcut_path
.value().c_str(),
176 bool ResolveShortcutProperties(const FilePath
& shortcut_path
,
178 ShortcutProperties
* properties
) {
179 DCHECK(options
&& properties
);
180 base::ThreadRestrictions::AssertIOAllowed();
182 if (options
& ~ShortcutProperties::PROPERTIES_ALL
)
183 NOTREACHED() << "Unhandled property is used.";
185 ScopedComPtr
<IShellLink
> i_shell_link
;
187 // Get pointer to the IShellLink interface.
188 if (FAILED(i_shell_link
.CreateInstance(CLSID_ShellLink
, NULL
,
189 CLSCTX_INPROC_SERVER
))) {
193 ScopedComPtr
<IPersistFile
> persist
;
194 // Query IShellLink for the IPersistFile interface.
195 if (FAILED(persist
.QueryFrom(i_shell_link
)))
198 // Load the shell link.
199 if (FAILED(persist
->Load(shortcut_path
.value().c_str(), STGM_READ
)))
202 // Reset |properties|.
203 properties
->options
= 0;
205 wchar_t temp
[MAX_PATH
];
206 if (options
& ShortcutProperties::PROPERTIES_TARGET
) {
207 // Try to find the target of a shortcut.
208 if (FAILED(i_shell_link
->Resolve(0, SLR_NO_UI
| SLR_NOSEARCH
)))
210 if (FAILED(i_shell_link
->GetPath(temp
, MAX_PATH
, NULL
, SLGP_UNCPRIORITY
)))
212 properties
->set_target(FilePath(temp
));
215 if (options
& ShortcutProperties::PROPERTIES_WORKING_DIR
) {
216 if (FAILED(i_shell_link
->GetWorkingDirectory(temp
, MAX_PATH
)))
218 properties
->set_working_dir(FilePath(temp
));
221 if (options
& ShortcutProperties::PROPERTIES_ARGUMENTS
) {
222 if (FAILED(i_shell_link
->GetArguments(temp
, MAX_PATH
)))
224 properties
->set_arguments(temp
);
227 if (options
& ShortcutProperties::PROPERTIES_DESCRIPTION
) {
228 // Note: description length constrained by MAX_PATH.
229 if (FAILED(i_shell_link
->GetDescription(temp
, MAX_PATH
)))
231 properties
->set_description(temp
);
234 if (options
& ShortcutProperties::PROPERTIES_ICON
) {
236 if (FAILED(i_shell_link
->GetIconLocation(temp
, MAX_PATH
, &temp_index
)))
238 properties
->set_icon(FilePath(temp
), temp_index
);
241 // Windows 7+ options, avoiding unnecessary work.
242 if ((options
& ShortcutProperties::PROPERTIES_WIN7
) &&
243 GetVersion() >= VERSION_WIN7
) {
244 ScopedComPtr
<IPropertyStore
> property_store
;
245 if (FAILED(property_store
.QueryFrom(i_shell_link
)))
248 if (options
& ShortcutProperties::PROPERTIES_APP_ID
) {
249 ScopedPropVariant pv_app_id
;
250 if (property_store
->GetValue(PKEY_AppUserModel_ID
,
251 pv_app_id
.Receive()) != S_OK
) {
254 switch (pv_app_id
.get().vt
) {
256 properties
->set_app_id(L
"");
259 properties
->set_app_id(pv_app_id
.get().pwszVal
);
262 NOTREACHED() << "Unexpected variant type: " << pv_app_id
.get().vt
;
267 if (options
& ShortcutProperties::PROPERTIES_DUAL_MODE
) {
268 ScopedPropVariant pv_dual_mode
;
269 if (property_store
->GetValue(PKEY_AppUserModel_IsDualMode
,
270 pv_dual_mode
.Receive()) != S_OK
) {
273 switch (pv_dual_mode
.get().vt
) {
275 properties
->set_dual_mode(false);
278 properties
->set_dual_mode(pv_dual_mode
.get().boolVal
== VARIANT_TRUE
);
281 NOTREACHED() << "Unexpected variant type: " << pv_dual_mode
.get().vt
;
290 bool ResolveShortcut(const FilePath
& shortcut_path
,
291 FilePath
* target_path
,
295 options
|= ShortcutProperties::PROPERTIES_TARGET
;
297 options
|= ShortcutProperties::PROPERTIES_ARGUMENTS
;
300 ShortcutProperties properties
;
301 if (!ResolveShortcutProperties(shortcut_path
, options
, &properties
))
305 *target_path
= properties
.target
;
307 *args
= properties
.arguments
;
311 bool TaskbarPinShortcutLink(const wchar_t* shortcut
) {
312 base::ThreadRestrictions::AssertIOAllowed();
314 // "Pin to taskbar" is only supported after Win7.
315 if (GetVersion() < VERSION_WIN7
)
318 int result
= reinterpret_cast<int>(ShellExecute(NULL
, L
"taskbarpin", shortcut
,
323 bool TaskbarUnpinShortcutLink(const wchar_t* shortcut
) {
324 base::ThreadRestrictions::AssertIOAllowed();
326 // "Unpin from taskbar" is only supported after Win7.
327 if (GetVersion() < VERSION_WIN7
)
330 int result
= reinterpret_cast<int>(ShellExecute(NULL
, L
"taskbarunpin",
331 shortcut
, NULL
, NULL
, 0));