win_user_profile - new module (#50637)

* win_user_profile - new module

* Fix typo

* Fix 2012 CI issues

* changed bool in docs and revert other test changes
This commit is contained in:
Jordan Borean 2019-01-22 11:04:22 +10:00 committed by GitHub
commit 6c26256945
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 702 additions and 0 deletions

View file

@ -196,6 +196,7 @@ seealso:
- module: win_domain_group
- module: win_domain_membership
- module: win_user
- module: win_user_profile
author:
- Nick Chandler (@nwchandler)
'''

View file

@ -109,6 +109,7 @@ seealso:
- module: win_domain_user
- module: win_group
- module: win_group_membership
- module: win_user_profile
author:
- Paul Durivage (@angstwad)
- Chris Church (@cchurch)

View file

@ -0,0 +1,170 @@
#!powershell
# Copyright: (c) 2019, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
$spec = @{
options = @{
name = @{ type = "str" }
remove_multiple = @{ type = "bool"; default = $false }
state = @{ type = "str"; default = "present"; choices = @("absent", "present") }
username = @{ type = "sid"; }
}
required_if = @(
@("state", "present", @("username")),
@("state", "absent", @("name", "username"), $true)
)
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$module.Result.path = $null
$name = $module.Params.name
$remove_multiple = $module.Params.remove_multiple
$state = $module.Params.state
$username = $module.Params.username
Add-CSharpType -AnsibleModule $module -References @'
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Ansible.WinUserProfile
{
public class NativeMethods
{
[DllImport("Userenv.dll", CharSet = CharSet.Unicode)]
public static extern int CreateProfile(
[MarshalAs(UnmanagedType.LPWStr)] string pszUserSid,
[MarshalAs(UnmanagedType.LPWStr)] string pszUserName,
[Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszProfilePath,
UInt32 cchProfilePath);
[DllImport("Userenv.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool DeleteProfileW(
[MarshalAs(UnmanagedType.LPWStr)] string lpSidString,
IntPtr lpProfile,
IntPtr lpComputerName);
[DllImport("Userenv.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool GetProfilesDirectoryW(
[Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpProfileDir,
ref UInt32 lpcchSize);
}
}
'@
Function Get-LastWin32ExceptionMessage {
param([int]$ErrorCode)
# Need to throw a Win32Exception with the error code to get the actual error message assigned to that code
try {
throw [System.ComponentModel.Win32Exception]$ErrorCode
} catch [System.ComponentModel.Win32Exception] {
$exp_msg = "{0} (Win32 ErrorCode {1} - 0x{1:X8})" -f $_.Exception.Message, $ErrorCode
}
return $exp_msg
}
Function Get-ExpectedProfilePath {
param([String]$BaseName)
# Environment.GetFolderPath does not have an enumeration to get the base profile dir, use PInvoke instead
# and combine with the base name to return back to the user - best efforts
$raw_profile_path = New-Object -TypeName System.Text.StringBuilder -ArgumentList 0
$profile_path_length = 0
[Ansible.WinUserProfile.NativeMethods]::GetProfilesDirectoryW($raw_profile_path,
[ref]$profile_path_length) > $null
$raw_profile_path.EnsureCapacity($profile_path_length) > $null
$res = [Ansible.WinUserProfile.NativeMethods]::GetProfilesDirectoryW($raw_profile_path,
[ref]$profile_path_length)
if ($res -eq $false) {
$msg = Get-LastWin32ExceptionMessage -Error ([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())
$module.FailJson("Failed to determine profile path with the base name '$BaseName': $msg")
}
$profile_path = Join-Path -Path $raw_profile_path.ToString() -ChildPath $BaseName
return $profile_path
}
$profiles = Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
if ($state -eq "absent") {
if ($null -ne $username) {
$user_profiles = $profiles | Where-Object { $_.PSChildName -eq $username.Value }
} else {
# If the username was not provided, or we are removing a profile for a deleted user, we need to try and find
# the correct SID to delete. We just verify that the path matches based on the name passed in
$expected_profile_path = Get-ExpectedProfilePath -BaseName $name
$user_profiles = $profiles | Where-Object {
$profile_path = (Get-ItemProperty -Path $_.PSPath -Name ProfileImagePath).ProfileImagePath
$profile_path -eq $expected_profile_path
}
if ($user_profiles.Length -gt 1 -and -not $remove_multiple) {
$module.FailJson("Found multiple profiles matching the path '$expected_profile_path', set 'remove_multiple=True' to remove all the profiles for this match")
}
}
foreach ($user_profile in $user_profiles) {
$profile_path = (Get-ItemProperty -Path $user_profile.PSPath -Name ProfileImagePath).ProfileImagePath
if (-not $module.CheckMode) {
$res = [Ansible.WinUserProfile.NativeMethods]::DeleteProfileW($user_profile.PSChildName, [IntPtr]::Zero,
[IntPtr]::Zero)
if ($res -eq $false) {
$msg = Get-LastWin32ExceptionMessage -Error ([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())
$module.FailJson("Failed to delete the profile for $($user_profile.PSChildName): $msg")
}
}
# While we may have multiple profiles when the name option was used, it will always be the same path due to
# how we match name to a profile so setting it mutliple time sis fine
$module.Result.path = $profile_path
$module.Result.changed = $true
}
} elseif ($state -eq "present") {
# Now we know the SID, see if the profile already exists
$user_profile = $profiles | Where-Object { $_.PSChildName -eq $username.Value }
if ($null -eq $user_profile) {
# In case a SID was set as the username we still need to make sure the SID is mapped to a valid local account
try {
$account_name = $username.Translate([System.Security.Principal.NTAccount])
} catch [System.Security.Principal.IdentityNotMappedException] {
$module.FailJson("Fail to map the account '$($username.Value)' to a valid user")
}
# If the basename was not provided, determine it from the actual username
if ($null -eq $name) {
$name = $account_name.Value.Split('\', 2)[-1]
}
if ($module.CheckMode) {
$profile_path = Get-ExpectedProfilePath -BaseName $name
} else {
$raw_profile_path = New-Object -TypeName System.Text.StringBuilder -ArgumentList 260
$res = [Ansible.WinUserProfile.NativeMethods]::CreateProfile($username.Value, $name, $raw_profile_path,
$raw_profile_path.Capacity)
if ($res -ne 0) {
$exp = [System.Runtime.InteropServices.Marshal]::GetExceptionForHR($res)
$module.FailJson("Failed to create profile for user '$username': $($exp.Message)")
}
$profile_path = $raw_profile_path.ToString()
}
$module.Result.changed = $true
$module.Result.path = $profile_path
} else {
$module.Result.path = (Get-ItemProperty -Path $user_profile.PSPath -Name ProfileImagePath).ProfileImagePath
}
}
$module.ExitJson()

View file

@ -0,0 +1,113 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_user_profile
version_added: '2.8'
short_description: Manages the Windows user profiles.
description:
- Used to create or remove user profiles on a Windows host.
- This can be used to create a profile before a user logs on or delete a
profile when removing a user account.
- A profile can be created for both a local or domain account.
options:
name:
description:
- Specifies the base name for the profile path.
- When I(state) is C(present) this is used to create the profile for
I(username) at a specific path within the profile directory.
- This cannot be used to specify a path outside of the profile directory
but rather it specifies a folder(s) within this directory.
- If a profile for another user already exists at the same path, then a 3
digit incremental number is appended by Windows automatically.
- When I(state) is C(absent) and I(username) is not set, then the module
will remove all profiles that point to the profile path derived by this
value.
- This is useful if the account no longer exists but the profile still
remains.
type: str
remove_multiple:
description:
- When I(state) is C(absent) and the value for I(name) matches multiple
profiles the module will fail.
- Set this value to C(yes) to force the module to delete all the profiles
found.
default: no
type: bool
state:
description:
- Will ensure the profile exists when set to C(present).
- When creating a profile the I(username) option must be set to a valid
account.
- Will remove the profile(s) when set to C(absent).
- When removing a profile either I(username) must be set to a valid
account, or I(name) is set to the profile's base name.
default: present
choices:
- absent
- present
type: str
username:
description:
- The account name of security identifier (SID) for the profile.
- This must be set when I(state) is C(present) and must be a valid account
or the SID of a valid account.
- When I(state) is C(absent) then this must still be a valid account number
but the SID can be a deleted user's SID.
seealso:
- module: win_user
- module: win_domain_user
author:
- Jordan Borean (@jborean93)
'''
EXAMPLES = r'''
- name: Create a profile for an account
win_user_profile:
username: ansible-account
state: present
- name: Create a profile for an account at C:\Users\ansible
win_user_profile:
username: ansible-account
name: ansible
state: present
- name: Remove a profile for a still valid account
win_user_profile:
username: ansible-account
state: absent
- name: Remove a profile for a deleted account
win_user_profile:
name: ansible
state: absent
- name: Remove a profile for a deleted account based on the SID
win_user_profile:
username: S-1-5-21-3233007181-2234767541-1895602582-1305
state: absent
- name: Remove multiple profiles that exist at the basename path
win_user_profile:
name: ansible
state: absent
remove_multiple: yes
'''
RETURN = r'''
path:
description: The full path to the profile for the account. This will be null
if C(state=absent) and no profile was deleted.
returned: always
type: str
sample: C:\Users\ansible
'''