mirror of https://github.com/ansible/ansible.git
New module: Add module or managing Windows Active Directory users (windows/win_domain_user) (#24075)
* Initial win_domain_user module support * Add return information * Update return values * Add try/catch for PS module import * Improve win_domain_user module * Fix bad merge * Fix pep8 failure * Actually fix pep8 failure * Update win_domain_user.py to meet standards * Add check_mode support for win_domain_user * Updated documentation before mergepull/28047/head
parent
7c59b66802
commit
22533c0932
@ -0,0 +1,280 @@
|
||||
#!powershell
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
# WANT_JSON
|
||||
# POWERSHELL_COMMON
|
||||
|
||||
########
|
||||
try {
|
||||
Import-Module ActiveDirectory
|
||||
}
|
||||
catch {
|
||||
Fail-Json $result "Failed to import ActiveDirectory PowerShell module. This module should be run on a domain controller, and the ActiveDirectory module must be available."
|
||||
}
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
password_updated = $false
|
||||
}
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -default $false
|
||||
|
||||
# Module control parameters
|
||||
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent","query"
|
||||
$update_password = Get-AnsibleParam -obj $params -name "update_password" -type "str" -default "always" -validateset "always","on_create"
|
||||
$groups_action = Get-AnsibleParam -obj $params -name "groups_action" -type "str" -default "replace" -validateset "add","remove","replace"
|
||||
|
||||
# User account parameters
|
||||
$username = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
|
||||
$description = Get-AnsibleParam -obj $params -name "description" -type "str"
|
||||
$password = Get-AnsibleParam -obj $params -name "password" -type "str"
|
||||
$password_expired = Get-AnsibleParam -obj $params -name "password_expired" -type "bool"
|
||||
$password_never_expires = Get-AnsibleParam -obj $params -name "password_never_expires" -type "bool"
|
||||
$user_cannot_change_password = Get-AnsibleParam -obj $params -name "user_cannot_change_password" -type "bool"
|
||||
$account_locked = Get-AnsibleParam -obj $params -name "account_locked" -type "bool"
|
||||
$groups = Get-AnsibleParam -obj $params -name "groups" -type "list"
|
||||
$enabled = Get-AnsibleParam -obj $params -name "enabled" -type "bool" -default $true
|
||||
$path = Get-AnsibleParam -obj $params -name "path" -type "str"
|
||||
$upn = Get-AnsibleParam -obj $params -name "upn" -type "str"
|
||||
|
||||
# User informational parameters
|
||||
$user_info = @{
|
||||
GivenName = Get-AnsibleParam -obj $params -name "firstname" -type "str"
|
||||
Surname = Get-AnsibleParam -obj $params -name "surname" -type "str"
|
||||
Company = Get-AnsibleParam -obj $params -name "company" -type "str"
|
||||
EmailAddress = Get-AnsibleParam -obj $params -name "email" -type "str"
|
||||
StreetAddress = Get-AnsibleParam -obj $params -name "street" -type "str"
|
||||
City = Get-AnsibleParam -obj $params -name "city" -type "str"
|
||||
State = Get-AnsibleParam -obj $params -name "state_province" -type "str"
|
||||
PostalCode = Get-AnsibleParam -obj $params -name "postal_code" -type "str"
|
||||
Country = Get-AnsibleParam -obj $params -name "country" -type "str"
|
||||
}
|
||||
|
||||
# Parameter validation
|
||||
If ($account_locked -ne $null -and $account_locked) {
|
||||
Fail-Json $result "account_locked must be set to 'no' if provided"
|
||||
}
|
||||
If (($password_expired -ne $null) -and ($password_never_expires -ne $null)) {
|
||||
Fail-Json $result "password_expired and password_never_expires are mutually exclusive but have both been set"
|
||||
}
|
||||
|
||||
try {
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
}
|
||||
catch {
|
||||
$user_obj = $null
|
||||
}
|
||||
|
||||
If ($state -eq 'present') {
|
||||
# Ensure user exists
|
||||
try {
|
||||
$new_user = $false
|
||||
|
||||
# If the account does not exist, create it
|
||||
If (-not $user_obj) {
|
||||
If ($path -ne $null){
|
||||
New-ADUser -Name $username -Path $path -WhatIf:$check_mode
|
||||
}
|
||||
Else {
|
||||
New-ADUser -Name $username -WhatIf:$check_mode
|
||||
}
|
||||
$new_user = $true
|
||||
$result.changed = $true
|
||||
If ($check_mode) {
|
||||
Exit-Json $result
|
||||
}
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
}
|
||||
|
||||
# Set the password if required
|
||||
If ($password -and (($new_user -and $update_password -eq "on_create") -or $update_password -eq "always")) {
|
||||
$secure_password = ConvertTo-SecureString $password -AsPlainText -Force
|
||||
Set-ADAccountPassword -Identity $username -Reset:$true -Confirm:$false -NewPassword $secure_password -WhatIf:$check_mode
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
$result.password_updated = $true
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
# Configure password policies
|
||||
If (($password_never_expires -ne $null) -and ($password_never_expires -ne $user_obj.PasswordNeverExpires)) {
|
||||
Set-ADUser -Identity $username -PasswordNeverExpires $password_never_expires -WhatIf:$check_mode
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
$result.changed = $true
|
||||
}
|
||||
If (($password_expired -ne $null) -and ($password_expired -ne $user_obj.PasswordExpired)) {
|
||||
Set-ADUser -Identity $username -ChangePasswordAtLogon $password_expired -WhatIf:$check_mode
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
$result.changed = $true
|
||||
}
|
||||
If (($user_cannot_change_password -ne $null) -and ($user_cannot_change_password -ne $user_obj.CannotChangePassword)) {
|
||||
Set-ADUser -Identity $username -CannotChangePassword $user_cannot_change_password -WhatIf:$check_mode
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
# Assign other account settings
|
||||
If (($upn -ne $null) -and ($upn -ne $user_obj.UserPrincipalName)) {
|
||||
Set-ADUser -Identity $username -UserPrincipalName $upn -WhatIf:$check_mode
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
$result.changed = $true
|
||||
}
|
||||
If (($description -ne $null) -and ($description -ne $user_obj.Description)) {
|
||||
Set-ADUser -Identity $username -description $description -WhatIf:$check_mode
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
$result.changed = $true
|
||||
}
|
||||
If ($enabled -ne $user_obj.Enabled) {
|
||||
Set-ADUser -Identity $username -Enabled $enabled -WhatIf:$check_mode
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
$result.changed = $true
|
||||
}
|
||||
If ((-not $account_locked) -and ($user_obj.LockedOut -eq $true)) {
|
||||
Unlock-ADAccount -Identity $username -WhatIf:$check_mode
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
# Set user information
|
||||
Foreach ($key in $user_info.Keys) {
|
||||
If ($user_info[$key] -eq $null) {
|
||||
continue
|
||||
}
|
||||
$value = $user_info[$key]
|
||||
If ($value -ne $user_obj.$key) {
|
||||
$expression = "Set-ADUser -Identity $username -$key '$value'"
|
||||
If (-not $check_mode) {
|
||||
Invoke-Expression $expression
|
||||
}
|
||||
$result.changed = $true
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
}
|
||||
}
|
||||
|
||||
# Configure group assignment
|
||||
If ($groups -ne $null) {
|
||||
$group_list = $groups
|
||||
|
||||
$groups = @()
|
||||
Foreach ($group in $group_list) {
|
||||
$groups += (Get-ADGroup -Identity $group).DistinguishedName
|
||||
}
|
||||
|
||||
$assigned_groups = @()
|
||||
Foreach ($group in (Get-ADPrincipalGroupMembership -Identity $username)) {
|
||||
$assigned_groups += $group.DistinguishedName
|
||||
}
|
||||
|
||||
switch ($groups_action) {
|
||||
"add" {
|
||||
Foreach ($group in $groups) {
|
||||
If (-not ($assigned_groups -Contains $group)) {
|
||||
Add-ADGroupMember -Identity $group -Members $username -WhatIf:$check_mode
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
"remove" {
|
||||
Foreach ($group in $groups) {
|
||||
If ($assigned_groups -Contains $group) {
|
||||
Remove-ADGroupMember -Identity $group -Members $username -Confirm:$false -WhatIf:$check_mode
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
"replace" {
|
||||
Foreach ($group in $assigned_groups) {
|
||||
If (($group -ne $user_obj.PrimaryGroup) -and -not ($groups -Contains $group)) {
|
||||
Remove-ADGroupMember -Identity $group -Members $username -Confirm:$false -WhatIf:$check_mode
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
Foreach ($group in $groups) {
|
||||
If (-not ($assigned_groups -Contains $group)) {
|
||||
Add-ADGroupMember -Identity $group -Members $username -WhatIf:$check_mode
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch {
|
||||
Fail-Json $result $_.Exception.Message
|
||||
}
|
||||
} ElseIf ($state -eq 'absent') {
|
||||
# Ensure user does not exist
|
||||
try {
|
||||
If ($user_obj) {
|
||||
Remove-ADUser $user_obj -Confirm:$false -WhatIf:$check_mode
|
||||
$result.changed = $true
|
||||
If ($check_mode) {
|
||||
Exit-Json $result
|
||||
}
|
||||
$user_obj = $null
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Fail-Json $result $_.Exception.Message
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
If ($user_obj) {
|
||||
$user_obj = Get-ADUser -Identity $username -Properties *
|
||||
$result.name = $user_obj.Name
|
||||
$result.firstname = $user_obj.GivenName
|
||||
$result.surname = $user_obj.Surname
|
||||
$result.enabled = $user_obj.Enabled
|
||||
$result.company = $user_obj.Company
|
||||
$result.street = $user_obj.StreetAddress
|
||||
$result.email = $user_obj.EmailAddress
|
||||
$result.city = $user_obj.City
|
||||
$result.state_province = $user_obj.State
|
||||
$result.country = $user_obj.Country
|
||||
$result.postal_code = $user_obj.PostalCode
|
||||
$result.distinguished_name = $user_obj.DistinguishedName
|
||||
$result.description = $user_obj.Description
|
||||
$result.password_expired = $user_obj.PasswordExpired
|
||||
$result.password_never_expires = $user_obj.PasswordNeverExpires
|
||||
$result.user_cannot_change_password = $user_obj.CannotChangePassword
|
||||
$result.account_locked = $user_obj.LockedOut
|
||||
$result.sid = [string]$user_obj.SID
|
||||
$result.upn = $user_obj.UserPrincipalName
|
||||
$user_groups = @()
|
||||
Foreach ($group in (Get-ADPrincipalGroupMembership $username)) {
|
||||
$user_groups += $group.name
|
||||
}
|
||||
$result.groups = $user_groups
|
||||
$result.msg = "User '$username' is present"
|
||||
$result.state = "present"
|
||||
}
|
||||
Else {
|
||||
$result.name = $username
|
||||
$result.msg = "User '$username' is absent"
|
||||
$result.state = "absent"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Fail-Json $result $_.Exception.Message
|
||||
}
|
||||
|
||||
Exit-Json $result
|
@ -0,0 +1,310 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.0',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_domain_user
|
||||
version_added: '2.4'
|
||||
short_description: Manages Windows Active Directory user accounts
|
||||
description:
|
||||
- Manages Windows Active Directory user accounts.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the user to create, remove or modify.
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- When C(present), creates or updates the user account. When C(absent),
|
||||
removes the user account if it exists. When C(query),
|
||||
retrieves the user account details without making any changes.
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
- query
|
||||
default: present
|
||||
enabled:
|
||||
description:
|
||||
- C(yes) will enable the user account. C(no) will disable the account.
|
||||
type: bool
|
||||
default: 'yes'
|
||||
account_locked:
|
||||
description:
|
||||
- C(no) will unlock the user account if locked. Note that there is not a
|
||||
way to lock an account as an administrator. Accounts are locked due to
|
||||
user actions; as an admin, you may only unlock a locked account. If you
|
||||
wish to administratively disable an account, set 'enabled' to 'no'.
|
||||
choices: [ 'no' ]
|
||||
type: bool
|
||||
description:
|
||||
description:
|
||||
- Description of the user
|
||||
groups:
|
||||
description:
|
||||
- Adds or removes the user from this list of groups,
|
||||
depending on the value of I(groups_action). To remove all but the
|
||||
Principal Group, set C(groups=<principal group name>) and
|
||||
I(groups_action=replace). Note that users cannot be removed from
|
||||
their principal group (for example, "Domain Users").
|
||||
groups_action:
|
||||
description:
|
||||
- If C(replace), the user is added as a member of each group in
|
||||
I(groups) and removed from any other groups. If C(add), the user is
|
||||
added to each group in I(groups) where not already a member. If
|
||||
C(remove), the user is removed from each group in I(groups).
|
||||
choices: [ 'replace', 'add', 'remove' ]
|
||||
default: replace
|
||||
password:
|
||||
description:
|
||||
- Optionally set the user's password to this (plain text) value. In order
|
||||
to enable an account - I(enabled) - a password must already be
|
||||
configured on the account, or you must provide a password here.
|
||||
update_password:
|
||||
description:
|
||||
- C(always) will update passwords if they differ. C(on_create) will
|
||||
only set the password for newly created users. Note that C(always) will
|
||||
always report an Ansible status of 'changed' because we cannot
|
||||
determine whether the new password differs from the old password.
|
||||
choices: [ 'always', 'on_create' ]
|
||||
default: always
|
||||
password_expired:
|
||||
description:
|
||||
- C(yes) will require the user to change their password at next login.
|
||||
C(no) will clear the expired password flag. This is mutually exclusive
|
||||
with I(password_never_expires).
|
||||
type: bool
|
||||
password_never_expires:
|
||||
description:
|
||||
- C(yes) will set the password to never expire. C(no) will allow the
|
||||
password to expire. This is mutually exclusive with I(password_expired)
|
||||
type: bool
|
||||
user_cannot_change_password:
|
||||
description:
|
||||
- C(yes) will prevent the user from changing their password. C(no) will
|
||||
allow the user to change their password.
|
||||
type: bool
|
||||
firstname:
|
||||
description:
|
||||
- Configures the user's first name (given name)
|
||||
surname:
|
||||
description:
|
||||
- Configures the user's last name (surname)
|
||||
company:
|
||||
description:
|
||||
- Configures the user's company name
|
||||
upn:
|
||||
description:
|
||||
- Configures the User Principal Name (UPN) for the account. This is not
|
||||
required, but is best practice to configure for modern versions of
|
||||
Active Directory. The format is "<username>@<domain>".
|
||||
email:
|
||||
description:
|
||||
- Configures the user's email address. This is a record in AD and does
|
||||
not do anything to configure any email servers or systems.
|
||||
street:
|
||||
description:
|
||||
- Configures the user's street address
|
||||
city:
|
||||
description:
|
||||
- Configures the user's city
|
||||
state_province:
|
||||
description:
|
||||
- Configures the user's state or province
|
||||
postal_code:
|
||||
description:
|
||||
- Configures the user's postal code / zip code
|
||||
country:
|
||||
description:
|
||||
- Configures the user's country code. Note that this is a two-character
|
||||
ISO 3166 code.
|
||||
path:
|
||||
description:
|
||||
- Container or OU for the new user; if you do not specify this, the
|
||||
user will be placed in the default container for users in the domain.
|
||||
Setting the path is only available when a new user is created;
|
||||
if you specify a path on an existing user, the user's path will not
|
||||
be updated - you must delete (e.g., state=absent) the user and
|
||||
then re-add the user with the appropriate path.
|
||||
notes:
|
||||
- Works with Windows 2012R2 and newer.
|
||||
- If running on a server that is not a Domain Controller, credential
|
||||
delegation through CredSSP or Kerberos with delegation must be used.
|
||||
- Note that some individuals have confirmed successful operation on Windows
|
||||
2008R2 servers with AD and AD Web Services enabled, but this has not
|
||||
received the same degree of testing as Windows 2012R2.
|
||||
author:
|
||||
- Nick Chandler (@nwchandler)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Ensure user bob is present with address information
|
||||
win_domain_user:
|
||||
name: bob
|
||||
firstname: Bob
|
||||
surname: Smith
|
||||
company: BobCo
|
||||
password: B0bP4ssw0rd
|
||||
state: present
|
||||
groups:
|
||||
- Domain Admins
|
||||
street: 123 4th St.
|
||||
city: Sometown
|
||||
state_province: IN
|
||||
postal_code: 12345
|
||||
country: US
|
||||
|
||||
- name: Ensure user bob is present in OU ou=test,dc=domain,dc=local
|
||||
win_domain_user:
|
||||
name: bob
|
||||
password: B0bP4ssw0rd
|
||||
state: present
|
||||
path: ou=test,dc=domain,dc=local
|
||||
groups:
|
||||
- Domain Admins
|
||||
|
||||
- name: Ensure user bob is absent
|
||||
win_domain_user:
|
||||
name: bob
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
account_locked:
|
||||
description: true if the account is locked
|
||||
returned: always
|
||||
type: boolean
|
||||
sample: false
|
||||
changed:
|
||||
description: true if the account changed during execution
|
||||
returned: always
|
||||
type: boolean
|
||||
sample: false
|
||||
city:
|
||||
description: The user city
|
||||
returned: always
|
||||
type: string
|
||||
sample: Indianapolis
|
||||
company:
|
||||
description: The user company
|
||||
returned: always
|
||||
type: string
|
||||
sample: RedHat
|
||||
country:
|
||||
description: The user country
|
||||
returned: always
|
||||
type: string
|
||||
sample: US
|
||||
description:
|
||||
description: A description of the account
|
||||
returned: always
|
||||
type: string
|
||||
sample: Server Administrator
|
||||
distinguished_name:
|
||||
description: DN of the user account
|
||||
returned: always
|
||||
type: string
|
||||
sample: CN=nick,OU=test,DC=domain,DC=local
|
||||
email:
|
||||
description: The user email address
|
||||
returned: always
|
||||
type: string
|
||||
sample: nick@domain.local
|
||||
enabled:
|
||||
description: true if the account is enabled and false if disabled
|
||||
returned: always
|
||||
type: string
|
||||
sample: true
|
||||
firstname:
|
||||
description: The user first name
|
||||
returned: always
|
||||
type: string
|
||||
sample: Nick
|
||||
groups:
|
||||
description: AD Groups to which the account belongs
|
||||
returned: always
|
||||
type: list
|
||||
sample: [ "Domain Admins", "Domain Users" ]
|
||||
msg:
|
||||
description: Summary message of whether the user is present or absent
|
||||
returned: always
|
||||
type: string
|
||||
sample: User nick is present
|
||||
name:
|
||||
description: The username on the account
|
||||
returned: always
|
||||
type: string
|
||||
sample: nick
|
||||
password_expired:
|
||||
description: true if the account password has expired
|
||||
returned: always
|
||||
type: boolean
|
||||
sample: false
|
||||
password_updated:
|
||||
description: true if the password changed during this execution
|
||||
returned: always
|
||||
type: boolean
|
||||
sample: true
|
||||
postal_code:
|
||||
description: The user postal code
|
||||
returned: always
|
||||
type: string
|
||||
sample: 46033
|
||||
sid:
|
||||
description: The SID of the account
|
||||
returned: always
|
||||
type: string
|
||||
sample: S-1-5-21-2752426336-228313920-2202711348-1175
|
||||
state:
|
||||
description: The state of the user account
|
||||
returned: always
|
||||
type: string
|
||||
sample: present
|
||||
state_province:
|
||||
description: The user state or province
|
||||
returned: always
|
||||
type: string
|
||||
sample: IN
|
||||
street:
|
||||
description: The user street address
|
||||
returned: always
|
||||
type: string
|
||||
sample: 123 4th St.
|
||||
surname:
|
||||
description: The user last name
|
||||
returned: always
|
||||
type: string
|
||||
sample: Doe
|
||||
upn:
|
||||
description: The User Principal Name of the account
|
||||
returned: always
|
||||
type: string
|
||||
sample: nick@domain.local
|
||||
user_cannot_change_password:
|
||||
description: true if the user is not allowed to change password
|
||||
returned: always
|
||||
type: string
|
||||
sample: false
|
||||
'''
|
Loading…
Reference in New Issue