From b369ea570a6ffd05bceb3758743d28a5a31d7045 Mon Sep 17 00:00:00 2001 From: Dag Wieers Date: Mon, 16 Jan 2017 19:51:56 +0100 Subject: [PATCH] win_shortcut: Create, manage, remove Windows shortcuts (#20164) * win_shortcut: Create, manage, remove Windows shortcuts This modules manages Windows shortcuts and all its properties. The module is idempotent and supports check-mode. This relates to #19694 * Changes required after @nitzmahone review * Added -type "path" to parameter definitions * Small fixes - Add conversion from window style name to window style id - Fix error message output (Why didn't the original work ?) --- lib/ansible/module_utils/powershell.ps1 | 21 ++- lib/ansible/modules/windows/win_shortcut.ps1 | 148 +++++++++++++++++++ lib/ansible/modules/windows/win_shortcut.py | 106 +++++++++++++ 3 files changed, 270 insertions(+), 5 deletions(-) create mode 100644 lib/ansible/modules/windows/win_shortcut.ps1 create mode 100644 lib/ansible/modules/windows/win_shortcut.py diff --git a/lib/ansible/module_utils/powershell.ps1 b/lib/ansible/module_utils/powershell.ps1 index 73a9954610..c489182d0e 100644 --- a/lib/ansible/module_utils/powershell.ps1 +++ b/lib/ansible/module_utils/powershell.ps1 @@ -98,6 +98,11 @@ Function Fail-Json($obj, $message = $null) Exit 1 } +Function Expand-Environment($value) +{ + [System.Environment]::ExpandEnvironmentVariables($value) +} + # Helper function to get an "attribute" from a psobject instance in powershell. # This is a convenience to make getting Members from an object easier and # slightly more pythonic @@ -105,7 +110,7 @@ Function Fail-Json($obj, $message = $null) #Get-AnsibleParam also supports Parameter validation to save you from coding that manually: #Example: Get-AnsibleParam -obj $params -name "State" -default "Present" -ValidateSet "Present","Absent" -resultobj $resultobj -failifempty $true #Note that if you use the failifempty option, you do need to specify resultobject as well. -Function Get-AnsibleParam($obj, $name, $default = $null, $resultobj, $failifempty=$false, $emptyattributefailmessage, $ValidateSet, $ValidateSetErrorMessage) +Function Get-AnsibleParam($obj, $name, $default = $null, $resultobj, $failifempty=$false, $emptyattributefailmessage, $ValidateSet, $ValidateSetErrorMessage, $type=$null) { # Check if the provided Member $name exists in $obj and return it or the default. Try @@ -119,7 +124,7 @@ Function Get-AnsibleParam($obj, $name, $default = $null, $resultobj, $failifempt { if ($ValidateSet -contains ($obj.$name)) { - $obj.$name + $value = $obj.$name } Else { @@ -133,15 +138,14 @@ Function Get-AnsibleParam($obj, $name, $default = $null, $resultobj, $failifempt } Else { - $obj.$name + $value = $obj.$name } - } Catch { If ($failifempty -eq $false) { - $default + $value = $default } Else { @@ -152,6 +156,13 @@ Function Get-AnsibleParam($obj, $name, $default = $null, $resultobj, $failifempt Fail-Json -obj $resultobj -message $emptyattributefailmessage } } + + # Expand environment variables on path-type (Beware: turns $null into "") + If ($value -ne $null -and $type -eq "path") { + $value = Expand-Environment($value) + } + + $value } #Alias Get-attr-->Get-AnsibleParam for backwards compat. Only add when needed to ease debugging of scripts diff --git a/lib/ansible/modules/windows/win_shortcut.ps1 b/lib/ansible/modules/windows/win_shortcut.ps1 new file mode 100644 index 0000000000..a83a6a19ab --- /dev/null +++ b/lib/ansible/modules/windows/win_shortcut.ps1 @@ -0,0 +1,148 @@ +#!powershell +# (c) 2016, Dag Wieers +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# WANT_JSON +# POWERSHELL_COMMON + +# Based on: http://powershellblogger.com/2016/01/create-shortcuts-lnk-or-url-files-with-powershell/ + +# TODO: Add debug information with what has changed using windows functions +# TODO: Ensure that by default variables default to $null automatically (so that they can be tested) + +$ErrorActionPreference = "Stop" + +$params = Parse-Args $args -supports_check_mode $true; + +$_ansible_check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -default $false + +$src = Get-AnsibleParam -obj $params -name "src" -type "path" -default $null +$dest = Get-AnsibleParam -obj $params -name "dest" -type "path" -failifempty $true +$state = Get-AnsibleParam -obj $params -name "state" -type "string" -default "present" +$orig_args = Get-AnsibleParam -obj $params -name "args" -type "string" -default $null +$directory = Get-AnsibleParam -obj $params -name "directory" -type "path" -default $null +$hotkey = Get-AnsibleParam -obj $params -name "hotkey" -type "string" -default $null +$icon = Get-AnsibleParam -obj $params -name "icon" -type "path" -default $null +$orig_description = Get-AnsibleParam -obj $params -name "description" -type "string" -default $null +$windowstyle = Get-AnsibleParam -obj $params -name "windowstyle" -type "string" -validateset "default","maximized","minimized" -default $null + +# Expand environment variables on non-path types (Beware: turns $null into "") +#$src = [System.Environment]::ExpandEnvironmentVariables($orig_src) +#$dest = [System.Environment]::ExpandEnvironmentVariables($orig_dest) +$args = Expand-Environment($orig_args) +#$directory = [System.Environment]::ExpandEnvironmentVariables($orig_directory) +#$icon = [System.Environment]::ExpandEnvironmentVariables($orig_icon) +$description = Expand-Environment($orig_description) + +$result = @{ + changed = $false + dest = $dest + state = $state +} + +# Convert from window style name to window style id +$windowstyles = @{ + default = 1 + maximized = 3 + minimized = 7 +} + +# Convert from window style id to window style name +$windowstyleids = @( "", "default", "", "maximized", "", "", "", "minimized" ) + +If ($state -eq "absent") { + If (Test-Path "$dest") { + # If the shortcut exists, try to remove it + Try { + Remove-Item -Path "$dest"; + } Catch { + # Report removal failure + Fail-Json $result "Failed to remove shortcut $dest. (" + $_.Exception.Message + ")" + } + # Report removal success + $result.changed = $true + } Else { + # Nothing to report, everything is fine already + } +} ElseIf ($state -eq "present") { + # Create an in-memory object based on the existing shortcut (if any) + $Shell = New-Object -ComObject ("WScript.Shell") + $ShortCut = $Shell.CreateShortcut($dest) + + # Compare existing values with new values, report as changed if required + + If ($src -ne $null -and $ShortCut.TargetPath -ne $src) { + $result.changed = $true + $ShortCut.TargetPath = $src + } + $result.src = $ShortCut.TargetPath + + # Determine if we have a WshShortcut or WshUrlShortcut by checking the Arguments property + # A WshUrlShortcut objects only consists of a TargetPath property + + # TODO: Find a better way to do has_attr() or isinstance() ? + If (Get-Member -InputObject $ShortCut -Name Arguments) { + + # This is a full-featured application shortcut ! + If ($orig_args -ne $null -and $ShortCut.Arguments -ne $args) { + $result.changed = $true + $ShortCut.Arguments = $args + } + $result.args = $ShortCut.Arguments + + If ($directory -ne $null -and $ShortCut.WorkingDirectory -ne $directory) { + $result.changed = $true + $ShortCut.WorkingDirectory = $directory + } + $result.directory = $ShortCut.WorkingDirectory + + # FIXME: Not all values are accepted here ! + If ($hotkey -ne $null -and $ShortCut.Hotkey -ne $hotkey) { + $result.changed = $true + $ShortCut.Hotkey = $hotkey + } + $result.hotkey = $ShortCut.Hotkey + + If ($icon -ne $null -and $ShortCut.IconLocation -ne $icon) { + $result.changed = $true + $ShortCut.IconLocation = $icon + } + $result.icon = $ShortCut.IconLocation + + If ($orig_description -ne $null -and $ShortCut.Description -ne $description) { + $result.changed = $true + $ShortCut.Description = $description + } + $result.description = $ShortCut.Description + + If ($windowstyle -ne $null -and $ShortCut.WindowStyle -ne $windowstyles.$windowstyle) { + $result.changed = $true + $ShortCut.WindowStyle = $windowstyles.$windowstyle + } + $result.windowstyle = $windowstyleids[$ShortCut.WindowStyle] + } + + If ($result.changed -eq $true -and $_ansible_check_mode -ne $true) { + Try { + $ShortCut.Save() + } Catch { + Fail-Json $result "Failed to create shortcut $dest. (" + $_.Exception.Message + ")" + } + } +} + +Exit-Json $result diff --git a/lib/ansible/modules/windows/win_shortcut.py b/lib/ansible/modules/windows/win_shortcut.py new file mode 100644 index 0000000000..b54669553f --- /dev/null +++ b/lib/ansible/modules/windows/win_shortcut.py @@ -0,0 +1,106 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Dag Wieers +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +DOCUMENTATION = ''' +--- +module: win_shortcut +version_added: '2.3' +short_description: Manage shortcuts on Windows +description: +- Create, manage and delete Windows shortcuts +options: + src: + description: + - Executable or URL the shortcut points to. + description: + description: + - Description for the shortcut. + - This is usually shown when hoovering the icon. + dest: + description: + - Destination file for the shortcuting file. + - File name should have a C(.lnk) or C(.url) extension. + required: true + args: + description: + - Additional arguments for the executable defined in C(src). + directory: + description: + - Working directory for executable defined in C(src). + icon: + description: + - Icon used for the shortcut + - File name should have a C(.ico) extension. + - The file name is followed by a comma and the number in the library file (.dll) or use 0 for an image file. + hotkey: + description: + - Key combination for the shortcut. + windowstyle: + description: + - Influences how the application is displayed when it is launched. + choices: + - default + - maximized + - minimized + state: + description: + - When C(present), creates or updates the shortcut. When C(absent), + removes the shortcut if it exists. + choices: + - present + - absent + default: 'present' +author: Dag Wieers (@dagwieers) +notes: +- 'The following options can include Windows environment variables: C(dest), C(args), C(description), C(dest), C(directory), C(icon) C(src)' +- 'Windows has two types of shortcuts: Application and URL shortcuts. URL shortcuts only consists of C(dest) and C(src)' +''' + +EXAMPLES = r''' +# Create an application shortcut on the desktop +- win_shortcut: + src: C:\Program Files\Mozilla Firefox\Firefox.exe + dest: C:\Users\Public\Desktop\Mozilla Firefox.lnk + icon: C:\Program Files\Mozilla Firefox\Firefox.exe,0 + +# Create the same shortcut using environment variables +- win_shortcut: + description: The Mozilla Firefox web browser + src: '%PROGRAMFILES%\Mozilla Firefox\Firefox.exe' + dest: '%PUBLIC%\Desktop\Mozilla Firefox.lnk' + icon: '%PROGRAMFILES\Mozilla Firefox\Firefox.exe,0' + directory: '%PROGRAMFILES%\Mozilla Firefox' + +# Create a URL shortcut to the Ansible website +- win_shortcut: + src: 'https://ansible.com/' + dest: '%PUBLIC%\Desktop\Ansible website.url' + +# Create an application shortcut for the Ansible website +- win_shortcut: + src: '%PROGRAMFILES%\Google\Chrome\Application\chrome.exe' + dest: '%PUBLIC%\Desktop\Ansible website.lnk' + args: '--new-window https://ansible.com/' + directory: '%PROGRAMFILES%\Google\Chrome\Application' + icon: '%PROGRAMFILES%\Google\Chrome\Application\chrome.exe,0' +''' + +RETURN = ''' +''' \ No newline at end of file