Search code examples
powershellintegerposix

powershell script to populate uidnumber problem with conversion to systm.Int32 value


i'm trying to write a script to popolate uidnumber in active directory.

$SearchBase = "OU=test,DC=xxx,DC=local"
$lastuid = Get-ADUser -Filter * -searchbase $searchbase -Properties * | Where {($_.uidNumber -ne $null)} | select uidnumber | Measure-Object -Property uidnumber -Maximum | select maximum | 
Format-Wide
$i= $lastuid
$nouid = Get-ADUser -Filter * -searchbase $searchbase -Properties * | Where {($_.uidNumber -eq $null)} | select samaccountname
$nouid | %{Set-ADUser $_.samAccountName -add @{uidnumber=('{0:D4}' -f $i++)}}

This do not produce error but pupulate uidnumber starting from 0, ignoring lastuid. If i use

$i= $lastuid

produce error like

    "Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.Int32". At C:\-\Documents\uid.ps1:16 char:15...  $nouid | %{Set-ADUser $_.samAccountName -replace @{uidnumber=([int]  ...
             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 CategoryInfo          : InvalidArgument: (:) [], RuntimeException
 FullyQualifiedErrorId : ConvertToFinalInvalidCastException"

any suggestion?


Solution

  • Your command that determines the currently highest uidNumber is broken; replace it with the following:

    [int] $lastuid = (
      Get-ADUser -Filter * -searchbase $searchbase -Properties uidnumber | 
      Measure-Object -Property uidnumber -Maximum
    ).Maximum
    

    Note:

    • (...).Maximum, i.e. direct property access is used to obtain the maximum value from the Measure-Object call.

      • There is no need for select (Select-Object), and especially not Format-Wide - see next section.
    • There's no need for filtering out $null values with a Where (Where-Object) call, as Measure-Object will simply ignore them.

    • Using -Properties * is expensive and best avoided; here, you only need one non-default property, uidnumber.


    A separate problem is that your code for updating the uidnumber field doesn't increment the number for each targeted user: [int] $i + 1 assigns the same value to all; the simplest solution is to use ++:

    $noUid | Set-ADUser -replace @{ uidnumber = ++$i }
    

    Note:

    • There is no need for % (ForEach-Object), as you can pipe the user objects stored in $noUid directly to Set-ADUser.

    There's potential for additional optimization of your code, such as calling Get-ADUser -Filter * only once, assuming that all users objects fit into memory at once:

    $withoutUid, $withtUid = 
      (Get-ADUser -Filter * -searchbase $searchbase -Properties uidnumber).
      Where({ $null -eq $_.uidnumber }, 'Split')
    
    [int] $lastuid = ($withUid | Measure-Object -Property uidnumber -Maximum).Maximum
    
    $withoutUid | Set-ADUser -replace @{ uidnumber = ++$lastuid }
    

    As for what you tried:

    • Format-* cmdlets such as Format-Wide emit output objects whose sole purpose is to provide formatting instructions to PowerShell's for-display output-formatting system. In short: only ever use Format-* cmdlets to format data for display, never for subsequent programmatic processing - see this answer for more information.

      • Apart from Format-Wide not representing the data you intended, it output multiple objects, which $lastuid = ... captured in an array. It is that array that caused the error you saw.
    • Omitting Format-Wide wouldn't suffice in your case, because select maximum (Select-Object maximum) also wouldn't work as intended: it would create an object with a .Maximum property, instead of directly emitting the value of the .Maximum property of the property returned by Measure-Object. For the latter you need Select-Object -ExpandProperty maximum - see this answer for more information.