From 1cc55cb3df689dd4b44aa0a1a53dde95901ffed0 Mon Sep 17 00:00:00 2001 From: Ken Maranion Date: Tue, 15 Oct 2024 14:30:53 -0700 Subject: [PATCH] Import/Update CSV attribute bug fix --- 1 | 1 + PowerShell/JumpCloud Module/JumpCloud.psd1 | 230 +++++++++--------- .../CSV_Import/Import-JCUsersFromCSV.ps1 | 42 +++- .../CSV_Import/Update-JCUsersFromCSV.ps1 | 49 +++- PowerShell/ModuleChangelog.md | 12 + 5 files changed, 205 insertions(+), 129 deletions(-) create mode 100644 1 diff --git a/1 b/1 new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/1 @@ -0,0 +1 @@ +1 diff --git a/PowerShell/JumpCloud Module/JumpCloud.psd1 b/PowerShell/JumpCloud Module/JumpCloud.psd1 index 58029f21d..66f6090b5 100644 --- a/PowerShell/JumpCloud Module/JumpCloud.psd1 +++ b/PowerShell/JumpCloud Module/JumpCloud.psd1 @@ -8,156 +8,156 @@ @{ -# Script module or binary module file associated with this manifest. -RootModule = 'JumpCloud.psm1' - -# Version number of this module. -ModuleVersion = '2.14.1' - -# Supported PSEditions -# CompatiblePSEditions = @() - -# ID used to uniquely identify this module -GUID = '31c023d1-a901-48c4-90a3-082f91b31646' - -# Author of this module -Author = 'JumpCloud Solutions Architect Team' + # Script module or binary module file associated with this manifest. + RootModule = 'JumpCloud.psm1' + + # Version number of this module. + ModuleVersion = '2.14.2' + + # Supported PSEditions + # CompatiblePSEditions = @() + + # ID used to uniquely identify this module + GUID = '31c023d1-a901-48c4-90a3-082f91b31646' + + # Author of this module + Author = 'JumpCloud Solutions Architect Team' -# Company or vendor of this module -CompanyName = 'JumpCloud' + # Company or vendor of this module + CompanyName = 'JumpCloud' -# Copyright statement for this module -Copyright = '(c) JumpCloud. All rights reserved.' + # Copyright statement for this module + Copyright = '(c) JumpCloud. All rights reserved.' -# Description of the functionality provided by this module -Description = 'PowerShell functions to manage a JumpCloud Directory-as-a-Service' + # Description of the functionality provided by this module + Description = 'PowerShell functions to manage a JumpCloud Directory-as-a-Service' -# Minimum version of the PowerShell engine required by this module -PowerShellVersion = '4.0' + # Minimum version of the PowerShell engine required by this module + PowerShellVersion = '4.0' -# Name of the PowerShell host required by this module -# PowerShellHostName = '' + # Name of the PowerShell host required by this module + # PowerShellHostName = '' -# Minimum version of the PowerShell host required by this module -# PowerShellHostVersion = '' + # Minimum version of the PowerShell host required by this module + # PowerShellHostVersion = '' -# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# DotNetFrameworkVersion = '' + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' -# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# ClrVersion = '' + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # ClrVersion = '' -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' -# Modules that must be imported into the global environment prior to importing this module -RequiredModules = @('JumpCloud.SDK.DirectoryInsights', - 'JumpCloud.SDK.V1', - 'JumpCloud.SDK.V2') + # Modules that must be imported into the global environment prior to importing this module + RequiredModules = @('JumpCloud.SDK.DirectoryInsights', + 'JumpCloud.SDK.V1', + 'JumpCloud.SDK.V2') -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @() -# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. -FunctionsToExport = 'Add-JCAssociation', 'Add-JCCommandTarget', 'Add-JCGsuiteMember', - 'Add-JCOffice365Member', 'Add-JCRadiusReplyAttribute', - 'Add-JCSystemGroupMember', 'Add-JCSystemUser', - 'Add-JCUserGroupMember', 'Backup-JCOrganization', 'Connect-JCOnline', - 'Copy-JCAssociation', 'Get-JCAdmin', 'Get-JCAssociation', - 'Get-JCBackup', 'Get-JCCloudDirectory', 'Get-JCCommand', - 'Get-JCCommandResult', 'Get-JCCommandTarget', 'Get-JCEvent', - 'Get-JCEventCount', 'Get-JCGroup', 'Get-JCOrganization', 'Get-JCPolicy', - 'Get-JCPolicyResult', 'Get-JCPolicyTargetGroup', - 'Get-JCPolicyTargetSystem', 'Get-JCRadiusReplyAttribute', - 'Get-JCRadiusServer', 'Get-JCScheduledUserstate', 'Get-JCSystem', - 'Get-JCSystemApp', 'Get-JCSystemGroupMember', 'Get-JCSystemInsights', - 'Get-JCSystemKB', 'Get-JCSystemUser', 'Get-JCUser', - 'Get-JCUserGroupMember', 'Import-JCCommand', 'Import-JCMSPFromCSV', - 'Import-JCUsersFromCSV', 'Invoke-JCCommand', 'Invoke-JCDeployment', - 'New-JCCommand', 'New-JCDeploymentTemplate', 'New-JCImportTemplate', - 'New-JCMSPImportTemplate', 'New-JCPolicy', 'New-JCRadiusServer', - 'New-JCSystemGroup', 'New-JCUser', 'New-JCUserGroup', - 'Remove-JCAssociation', 'Remove-JCCommand', 'Remove-JCCommandResult', - 'Remove-JCCommandTarget', 'Remove-JCGsuiteMember', - 'Remove-JCOffice365Member', 'Remove-JCPolicy', - 'Remove-JCRadiusReplyAttribute', 'Remove-JCRadiusServer', - 'Remove-JCSystem', 'Remove-JCSystemGroup', - 'Remove-JCSystemGroupMember', 'Remove-JCSystemUser', 'Remove-JCUser', - 'Remove-JCUserGroup', 'Remove-JCUserGroupMember', - 'Send-JCPasswordReset', 'Set-JCCloudDirectory', 'Set-JCCommand', - 'Set-JCOrganization', 'Set-JCPolicy', 'Set-JCRadiusReplyAttribute', - 'Set-JCRadiusServer', 'Set-JCSettingsFile', 'Set-JCSystem', - 'Set-JCSystemUser', 'Set-JCUser', 'Set-JCUserGroupLDAP', - 'Update-JCModule', 'Update-JCMSPFromCSV', 'Update-JCUsersFromCSV' + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + FunctionsToExport = 'Add-JCAssociation', 'Add-JCCommandTarget', 'Add-JCGsuiteMember', + 'Add-JCOffice365Member', 'Add-JCRadiusReplyAttribute', + 'Add-JCSystemGroupMember', 'Add-JCSystemUser', + 'Add-JCUserGroupMember', 'Backup-JCOrganization', 'Connect-JCOnline', + 'Copy-JCAssociation', 'Get-JCAdmin', 'Get-JCAssociation', + 'Get-JCBackup', 'Get-JCCloudDirectory', 'Get-JCCommand', + 'Get-JCCommandResult', 'Get-JCCommandTarget', 'Get-JCEvent', + 'Get-JCEventCount', 'Get-JCGroup', 'Get-JCOrganization', 'Get-JCPolicy', + 'Get-JCPolicyResult', 'Get-JCPolicyTargetGroup', + 'Get-JCPolicyTargetSystem', 'Get-JCRadiusReplyAttribute', + 'Get-JCRadiusServer', 'Get-JCScheduledUserstate', 'Get-JCSystem', + 'Get-JCSystemApp', 'Get-JCSystemGroupMember', 'Get-JCSystemInsights', + 'Get-JCSystemKB', 'Get-JCSystemUser', 'Get-JCUser', + 'Get-JCUserGroupMember', 'Import-JCCommand', 'Import-JCMSPFromCSV', + 'Import-JCUsersFromCSV', 'Invoke-JCCommand', 'Invoke-JCDeployment', + 'New-JCCommand', 'New-JCDeploymentTemplate', 'New-JCImportTemplate', + 'New-JCMSPImportTemplate', 'New-JCPolicy', 'New-JCRadiusServer', + 'New-JCSystemGroup', 'New-JCUser', 'New-JCUserGroup', + 'Remove-JCAssociation', 'Remove-JCCommand', 'Remove-JCCommandResult', + 'Remove-JCCommandTarget', 'Remove-JCGsuiteMember', + 'Remove-JCOffice365Member', 'Remove-JCPolicy', + 'Remove-JCRadiusReplyAttribute', 'Remove-JCRadiusServer', + 'Remove-JCSystem', 'Remove-JCSystemGroup', + 'Remove-JCSystemGroupMember', 'Remove-JCSystemUser', 'Remove-JCUser', + 'Remove-JCUserGroup', 'Remove-JCUserGroupMember', + 'Send-JCPasswordReset', 'Set-JCCloudDirectory', 'Set-JCCommand', + 'Set-JCOrganization', 'Set-JCPolicy', 'Set-JCRadiusReplyAttribute', + 'Set-JCRadiusServer', 'Set-JCSettingsFile', 'Set-JCSystem', + 'Set-JCSystemUser', 'Set-JCUser', 'Set-JCUserGroupLDAP', + 'Update-JCModule', 'Update-JCMSPFromCSV', 'Update-JCUsersFromCSV' -# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. -CmdletsToExport = @() + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + CmdletsToExport = @() -# Variables to export from this module -VariablesToExport = '*' + # Variables to export from this module + VariablesToExport = '*' -# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. -AliasesToExport = 'New-JCAssociation' + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + AliasesToExport = 'New-JCAssociation' -# DSC resources to export from this module -# DscResourcesToExport = @() + # DSC resources to export from this module + # DscResourcesToExport = @() -# List of all modules packaged with this module -# ModuleList = @() + # List of all modules packaged with this module + # ModuleList = @() -# List of all files packaged with this module -# FileList = @() + # List of all files packaged with this module + # FileList = @() -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ - PSData = @{ + PSData = @{ - # Tags applied to this module. These help with module discovery in online galleries. - Tags = 'JumpCloud', 'DaaS', 'Jump', 'Cloud', 'Directory' + # Tags applied to this module. These help with module discovery in online galleries. + Tags = 'JumpCloud', 'DaaS', 'Jump', 'Cloud', 'Directory' - # A URL to the license for this module. - LicenseUri = 'https://github.com/TheJumpCloud/support/blob/master/PowerShell/LICENSE' + # A URL to the license for this module. + LicenseUri = 'https://github.com/TheJumpCloud/support/blob/master/PowerShell/LICENSE' - # A URL to the main website for this project. - ProjectUri = 'https://github.com/TheJumpCloud/support/wiki' + # A URL to the main website for this project. + ProjectUri = 'https://github.com/TheJumpCloud/support/wiki' - # A URL to an icon representing this module. - IconUri = 'https://avatars1.githubusercontent.com/u/4927461?s=200&v=4' + # A URL to an icon representing this module. + IconUri = 'https://avatars1.githubusercontent.com/u/4927461?s=200&v=4' - # ReleaseNotes of this module - ReleaseNotes = 'https://git.io/jc-pwsh-releasenotes' + # ReleaseNotes of this module + ReleaseNotes = 'https://git.io/jc-pwsh-releasenotes' - # Prerelease string of this module - # Prerelease = '' + # Prerelease string of this module + # Prerelease = '' - # Flag to indicate whether the module requires explicit user acceptance for install/update/save - # RequireLicenseAcceptance = $false + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false - # External dependent modules of this module - # ExternalModuleDependencies = @() + # External dependent modules of this module + # ExternalModuleDependencies = @() - } # End of PSData hashtable - -} # End of PrivateData hashtable + } # End of PSData hashtable + + } # End of PrivateData hashtable -# HelpInfo URI of this module -HelpInfoURI = 'https://github.com/TheJumpCloud/support/wiki' + # HelpInfo URI of this module + HelpInfoURI = 'https://github.com/TheJumpCloud/support/wiki' -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' } diff --git a/PowerShell/JumpCloud Module/Public/Utilities/CSV_Import/Import-JCUsersFromCSV.ps1 b/PowerShell/JumpCloud Module/Public/Utilities/CSV_Import/Import-JCUsersFromCSV.ps1 index 485544192..3c86f50e8 100755 --- a/PowerShell/JumpCloud Module/Public/Utilities/CSV_Import/Import-JCUsersFromCSV.ps1 +++ b/PowerShell/JumpCloud Module/Public/Utilities/CSV_Import/Import-JCUsersFromCSV.ps1 @@ -368,15 +368,51 @@ Function Import-JCUsersFromCSV () { $FormatGroupOutput = $Null $CustomGroupArrayList = $Null - $CustomAttributes = $UserAdd | Get-Member | Where-Object Name -Like "*Attribute*" | Where-Object { $_.Definition -NotLike "*=" -and $_.Definition -NotLike "*null" } | Select-Object Name + $CustomAttributes = $UserAdd | Get-Member | Where-Object Name -Like "*Attribute*" | Where-Object { $_.Definition -NotLike "*=" -and $_.Definition -NotLike "*null" } | Select-Object Write-Verbose $CustomAttributes.name.count if ($CustomAttributes.name.count -gt 1) { try { - $NumberOfCustomAttributes = ($CustomAttributes.name.count) / 2 + # Counter is used to create a clean list of attributes + $counter = 1 + # Create a clean list of attributes + $CustomAttributes | ForEach-Object { + # Current value of the attribute from Definition property + $value = $_.Definition -split '=' | Select-Object -Last 1 + + # If attribute has a name + if ($_.Name -like "*_name") { + # If current attribute is the same as counter, skip since it is already in the UpdateParams + if ($_.Name -eq "Attribute$($counter)_name") { + } else { + # Add the new AttributeName and current value to the UpdateParams + $UpdateParams.Add("Attribute$($counter)_name", $value) + # Remove the Current AttributeName from the UpdateParams since we overwrote it with the new name + $UpdateParams.Remove($_.Name) + } + } + + # If attribute has a value + if ($_.Name -like "*_value") { + # If current attribute is the same as counter, skip since it is already in the UpdateParams + if ($_.Name -eq "Attribute$($counter)_value") { + $counter++ + } else { + # Add the new AttributeValue and current value to the UpdateParams + $UpdateParams.Add("Attribute$($counter)_value", $value) + # Remove the Current AttributeValue from the UpdateParams since we overwrote it with the new value + $UpdateParams.Remove($_.Name) + $counter++ + } + } + } + + Write-Verbose "Attributes are $($UpdateParams)" + + $NumberOfCustomAttributes = $UpdateParams.Keys | Where-Object { $_ -like "*Attribute*" } | Measure-Object | Select-Object -ExpandProperty Count - $UpdateParams.Add("NumberOfCustomAttributes", $NumberOfCustomAttributes) + $UpdateParams.Add("NumberOfCustomAttributes", $NumberOfCustomAttributes / 2) $JSONParams = $UpdateParams | ConvertTo-Json diff --git a/PowerShell/JumpCloud Module/Public/Utilities/CSV_Import/Update-JCUsersFromCSV.ps1 b/PowerShell/JumpCloud Module/Public/Utilities/CSV_Import/Update-JCUsersFromCSV.ps1 index 7cc8f9bd2..8b9ddf255 100755 --- a/PowerShell/JumpCloud Module/Public/Utilities/CSV_Import/Update-JCUsersFromCSV.ps1 +++ b/PowerShell/JumpCloud Module/Public/Utilities/CSV_Import/Update-JCUsersFromCSV.ps1 @@ -118,14 +118,11 @@ Function Update-JCUsersFromCSV () { Write-Host "" $UpdateUsers = Import-Csv -Path $CSVFilePath - $CustomAttributes = $UpdateUsers | Get-Member | Where-Object Name -Like "*Attribute*" | Select-Object Name - foreach ($attr in $CustomAttributes ) { $UserUpdateParams.Add($attr.name, $attr.name) } - $employeeIdentifierCheck = $UpdateUsers | Where-Object { ($_.employeeIdentifier -ne $Null) -and ($_.employeeIdentifier -ne "") } if ($employeeIdentifierCheck.Count -gt 0) { @@ -322,7 +319,6 @@ Function Update-JCUsersFromCSV () { } elseif ($UserUpdateParams.$($Param.name)) { $UpdateParams.Add($Param.name, $Param.value) } - } $ProgressCounter++ @@ -344,19 +340,50 @@ Function Update-JCUsersFromCSV () { $FormatGroupOutput = $Null $CustomGroupArrayList = $Null - $CustomAttributes = $UserUpdate | Get-Member | Where-Object Name -Like "*Attribute*" | Where-Object { $_.Definition -NotLike "*=" -and $_.Definition -NotLike "*null" } | Select-Object Name - - Write-Verbose $CustomAttributes.name.count + $CustomAttributes = $UserUpdate | Get-Member | Where-Object Name -Like "*Attribute*" | Where-Object { $_.Definition -NotLike "*=" -and $_.Definition -NotLike "*null" } | Select-Object if ($CustomAttributes.name.count -gt 1) { try { - $NumberOfCustomAttributes = ($CustomAttributes.name.count) / 2 + # Counter is used to create a clean list of attributes + $counter = 1 + # Create a clean list of attributes + $CustomAttributes | ForEach-Object { + # Current value of the attribute from Definition property + $value = $_.Definition -split '=' | Select-Object -Last 1 + + # If attribute has a name + if ($_.Name -like "*_name") { + # If current attribute is the same as counter, skip since it is already in the UpdateParams + if ($_.Name -eq "Attribute$($counter)_name") { + } else { + # Add the new AttributeName and current value to the UpdateParams + $UpdateParams.Add("Attribute$($counter)_name", $value) + # Remove the Current AttributeName from the UpdateParams since we overwrote it with the new name + $UpdateParams.Remove($_.Name) + } + } + # If attribute has a value + if ($_.Name -like "*_value") { + # If current attribute is the same as counter, skip since it is already in the UpdateParams + if ($_.Name -eq "Attribute$($counter)_value") { + $counter++ + } else { + # Add the new AttributeValue and current value to the UpdateParams + $UpdateParams.Add("Attribute$($counter)_value", $value) + # Remove the Current AttributeValue from the UpdateParams since we overwrote it with the new value + $UpdateParams.Remove($_.Name) + $counter++ + } + } + } - $UpdateParams.Add("NumberOfCustomAttributes", $NumberOfCustomAttributes) + Write-Verbose "Attributes are $($UpdateParams)" - $JSONParams = $UpdateParams | ConvertTo-Json + $NumberOfCustomAttributes = $UpdateParams.Keys | Where-Object { $_ -like "*Attribute*" } | Measure-Object | Select-Object -ExpandProperty Count - Write-Verbose "$($JSONParams)" + $UpdateParams.Add("NumberOfCustomAttributes", $NumberOfCustomAttributes / 2) + + $JSONParams = $UpdateParams | ConvertTo-Json $NewUser = Set-JCUser @UpdateParams diff --git a/PowerShell/ModuleChangelog.md b/PowerShell/ModuleChangelog.md index 811d5491e..2fb7b8c4b 100644 --- a/PowerShell/ModuleChangelog.md +++ b/PowerShell/ModuleChangelog.md @@ -1,3 +1,15 @@ +## 2.14.2 + +Release Date: October 15, 2024 + +#### RELEASE NOTES + +``` +Fixed a bug with `Import-JCUsersFromCSV` and `Update-JCUsersFromCSV` where the a user does not get created/updated when there is an empty value on one of the Attribute columns on the CSV +``` + +#### BUG FIXES: +- Import-JCUsersFromCSV and Update-JCUsersFromCSV are now able to update/create users even when some Attributes column are not filled in ## 2.14.1 Release Date: October 1, 2024