nordlo.partille.cblifecycle 0.3.0-rc.15
Handles creation, updates and deletion of computer objects representing chromebooks in Active Directory
No packages depend on nordlo.partille.cblifecycle.
Fixed
Invoke-PKChromebookLifecycle -AttributeMapno longer silently skips updates when the target AD attribute is multi-valued (e.g.ou,serialNumber) and the current set is empty. The diff loop compared$existingAd.$adAttr -ne $newVal, but on a multi-valued attribute the AD module returns anADPropertyValueCollection, and-nebetween a collection-LHS and a string evaluates element-wise -- on an empty collection that result is itself empty, which coerces to$false, so the conditional treated "needs to be set" as "no change". Read is now normalized via@($existingAd.$adAttr)[0]so the comparison sees a scalar ($nullif unset). Single-valued targets are unaffected. Surfaced by mappingorgUnitPath = 'ou'.- AD-modifying cmdlets (
New-ADComputer,Set-ADComputer,Enable-ADAccount,Disable-ADAccount,Add-ADGroupMember) inInvoke-PKChromebookLifecycleandInvoke-PKChromebookPreprovisionnow pass-Confirm:$falseso they never prompt interactively. The functions are declaredConfirmImpact = 'High', which propagates to nested SupportsShouldProcess cmdlets and caused aConfirmprompt at SPN-add time (and would have surfaced on the other writes too). All writes are still gated by the outerShouldProcessso-WhatIf/-Confirmon the function as a whole still works. Matches the pre-existing pattern onRemove-ADComputerandRemove-ADGroupMember. Invoke-PKChromebookLifecycleSPN add on existing accounts no longer fails withCannot validate argument on parameter 'ServicePrincipalNames'. Values in the argument collection should be of Type: 'System.String'.Set-ADComputer -ServicePrincipalNames @{ Add = ... }validates eachAddvalue as a singleString, not a string array (the singular in the error message is literal). Now loops one SPN per call rather than passing an array. Replaces the failedList[string].ToArray()attempt in rc.12.Invoke-PKChromebookLifecycleno longer throws on "unknown root field" when a-DescriptionTemplate,-InfoTemplateor-AttributeMapkey references a Google field that happens to be unset on every fetched device. Google's Directory API omits null fields from the response, so a legitimate mapping (e.g.annotatedLocation = 'extensionAttribute3') hard-failed the run whenever no device had that field populated. The strict check is replaced by the existing always-empty soft warning (event 1003), which catches both genuine typos and sparse-field cases without blocking the run. Operators reading the rolling log will see one warning per always-empty path.
Added
Get-GoogleChromeDevice -Limit <int>parameter -- caps the number of devices fetched. Pagination terminates as soon as the cap is reached, and the last page'smaxResultsis scaled down to the remaining count so the API does not return more rows than needed. Default0= no limit (existing behaviour). Intended for test / dry-run iterations against large domains.Get-GoogleChromeDevice -OrgUnitPath <string>parameter -- restricts the fetch to a specific OU and its sub-OUs via Google's server-sideorgUnitPathfilter (URL-encoded for OU names with spaces or non-ASCII characters). Combines with-Limitfor quick samples of a specific subtree (e.g.-OrgUnitPath '/_Chromeenheter_elever' -Limit 5). Default''= no filtering.Invoke-PKChromebookLifecycle -InfoTemplateparameter. Writes a templated value to the ADinfoattribute (the "Notes" field on a computer object). Same path/transform syntax as-DescriptionTemplate; multi-line via PowerShell here-strings. When non-empty the attribute is fully managed -- manual edits in AD UC are overwritten on the next run; when empty (default) the attribute is not fetched, diffed or written.Run-ChromebookProvisioning.ps1 -InfoTemplateforwards to the lifecycle. Setting'info'as a value in-AttributeMapwhile-InfoTemplateis also set throws at startup (conflict).Invoke-PKChromebookLifecycle -AttributeMapkeys now accept the full path syntax used by-DescriptionTemplate, e.g.'recentUsers[0].email'or'autoUpdateExpiration:date'. Existing flat keys (annotatedUser,orgUnitPath, ...) remain unchanged. AttributeMap keys are validated the same way as templates: unknown root segments throw at startup, and paths that resolve to empty on every device log event 1003 (WARNING).- Template transform suffix
:date-- formats a millisecond Unix-epoch field (e.g.autoUpdateExpiration) asYYYY-MM-DD(UTC). Works in-DescriptionTemplate,-InfoTemplate, and-AttributeMapkeys. Unknown transform names throw at startup with the list of known transforms. Invoke-PKChromebookLifecycle -DescriptionTemplateparameter. Builds the ADDescriptionfrom an operator-supplied template with PowerShell-style path syntax:{name},{name.sub},{name[0]},{name[0].sub}. Derived names{school}(last segment oforgUnitPath),{class}(annotatedLocation), and{student}(annotatedUser) take precedence over same-named raw fields. Default'{school} - {class} - {student}'preserves the historical hardcoded format. Empty result falls back to'Chromebook - RADIUS/NPS'. Validation throws at startup on unknown root segments (typo guard against the loaded JSON's top-level keys) and logs event 1003 (WARNING) when a path resolves to empty on every device.Run-ChromebookProvisioning.ps1 -DescriptionTemplateforwards to the lifecycle.examples/Run-ChromebookProvisioning.ps1orchestration wrapper that runsInvoke-PKChromebookPreprovisionandInvoke-PKChromebookLifecyclein series, intended for a 30-minute Task Scheduler cadence. State-file alerting fires mail only onOK ↔ ERRORtransitions (no duplicate alerts when an error persists across many ticks), a named mutex plus the Task Scheduler "no concurrent instances" setting prevent overlap, and a retention pass prunesprocessed/failedartifacts older than-RetentionDayswhile the rolling log is rotated when it crosses-LogRotateSizeMB. Aggregates both functions' structured results into a single exit code.
Changed
examples/Run-ChromebookProvisioning.ps1default-AttributeMapmapsorgUnitPathto the standard LDAPouattribute instead ofextensionAttribute4. Theouattribute is multi-valued in the schema; the lifecycle writes exactly one value via-Replaceso each managed account has 0 or 1 values in practice. Companion change in theexamples/Get-PKChromebookADAccount.ps1andInvoke-PKChromebookLifecyclehelp examples for consistency.- Default
-MatchAttributechanged fromextensionAttribute1toserialNumberinInvoke-PKChromebookLifecycle,Invoke-PKChromebookPreprovision, and theexamples/Get-PKChromebookADAccount.ps1reader.serialNumberis the semantically correct LDAP attribute (OID 2.5.4.5) for this data. It is multi-valued in the AD schema, but the module always writes exactly one value via-Replace/OtherAttributesso each managed account has 0 or 1 values in practice. Read paths in both functions and the reader normalize the AD module'sADPropertyValueCollectionreturn via@($c.$MatchAttribute)[0]. The startup schema validation no longer rejects multi-valued attributes (the typo / exists-in-schema guard remains — that one prevents OU-wipe under-EnableDeletion -ForceDelete).DNSHostNameand SPN construction are unchanged: they are still built from the Google-provided serial. TameMyCerts / NPS impact: policies that match certificates againstDirectoryServicesAttributemust be pointed atserialNumberto follow the default (or-MatchAttribute extensionAttribute1preserved at call sites). Breaking change with no migration path, because the module is not deployed anywhere. - Google → lifecycle data exchange switched from CSV to JSON.
Get-GoogleChromeDevice -OutputCsvPathis removed in favour of-OutputJsonPath, which writes the full Chrome-device tree viaConvertTo-Json -Depth 100. Nested fields likerecentUsersandlastKnownNetworkare preserved (CSV flattens them to unusable strings).Invoke-PKChromebookLifecycle -CsvPathrenamed to-DataPath; reads JSON viaConvertFrom-Json. Wrapper default file changed fromchromebooks-limited.csvtochromebooks-limited.json. Breaking change for external callers of-OutputCsvPath-- switch to-OutputJsonPath. No alias. examples/Run-ChromebookProvisioning.ps1now performs the Google Workspace device fetch as its first step viaGet-GoogleChromeDevice, writing the result to-DataPath. New parameters:-ServiceAccountJsonPath,-ImpersonateEmail,-CustomerId,-GoogleFields. If the fetch fails or credentials are missing, the lifecycle pass is skipped (preprovision still runs because it does not depend on Google data) and the run ends with ERROR, which feeds into the existingOK↔ERRORmail transition -- no separateFETCH_FAILEDmail kind. Under-WhatIfthe fetch is skipped (it writes a file) but the rest of the wrapper runs dry as before. The summary text now starts with aGoogle fetchblock andERROR DETAILS:distinguishesGF:(Google fetch),PP:(preprovision) andLC:(lifecycle) so the operator can tell at a glance which step failed.- Wrapper defaults now anchor every path under
C:\CB-LifeCycle\(logs\,googledevices_in\,preprovision\,credentials\, plus state/lock files at the root), and the writable parent directories are auto-created at startup. README adds a "Standard-mapplayout" subsection plus a copy-pasteablemkdirrecipe. - Wrapper detects abnormal termination of the previous run (Task Scheduler force-kill, crash, BSOD, power loss) by checking for a leftover lock file at startup and fires a dedicated
ABNORMAL_ENDmail with the previous run's PID, start time, user and host. Independent of the OK/ERROR state machine so each kill event surfaces exactly once. - README section "Schemaläggning" covering the Task Scheduler recipe (including the 25-minute "stop the task if it runs longer than" setting that bounds hung runs), service-account permissions, the OK↔ERROR alerting table plus the
ABNORMAL_ENDnotification, single-instance lock semantics, and retention/rotation tuning knobs. - New exported function
Invoke-PKChromebookPreprovisionfor pre-creating AD computer accounts from a watched folder of serial-number lists (*.txt,*.lst,*.csvwithserial/serialNumbercolumn). Designed for school-start onboarding so devices can obtain a NPS/RADIUS certificate immediately after MDM enrollment, without waiting for the Google Admin Console export. Source files are moved toprocessed\(orfailed\) and accompanied by a per-file.log(one status line per serial) and-summary.txt(totals and error excerpts). Returns a structured result object withFilesProcessed,FilesFailed,SerialsRead,SerialsCreated,SerialsSkipped,GroupAddErrors,Errors,ErrorMessages,AdCountBefore/After. -DescriptionMarkerPrefixparameter (defaultPREPROVISIONED) on bothInvoke-PKChromebookPreprovisionandInvoke-PKChromebookLifecycle. Pre-provisioned accounts getDescription = "<prefix> <date> [<source-file>]". The lifecycle deletion pass excludes any account whose Description starts with the prefix, and logs a single summary line (event 1204) listing how many were protected.- "Claim" event on
Invoke-PKChromebookLifecycle: when an existing account whose Description starts with-DescriptionMarkerPrefixis matched against anACTIVECSV row, event 1142 is logged. The lifecycle's existing Description-diff path then overwrites the marker with the normalSchool - Class - Studentvalue, self-clearing the protection. - Event IDs 1150 / 1151 / 1152 / 1154 / 1160 / 1161 - preprovision created / create error / skipped (already in AD) / group-add / file OK / file failed.
- Return-object fields on
Invoke-PKChromebookLifecycle:Claimed,PreprovisionedProtected. - README section "Pre-provisionering" covering folder layout, txt/csv input formats, protection semantics and combined scheduled-task usage.
- SPN registration on
Invoke-PKChromebookLifecycle. Every account is ensured to carryhost/<serial>andhost/<serial>.<DnsDomain>onservicePrincipalName. SPNs are written atomically at creation viaNew-ADComputer -OtherAttributesand ensured on existing accounts viaSet-ADComputer -Add(add-only: other SPNs already on the object are left alone). AddsSpnsAddedto the return object andSPN additionsto the run summary. - Event IDs 1140 / 1141 - SPN added to AD account / error adding SPN.
-MatchAttributeparameter onInvoke-PKChromebookLifecycle(defaultextensionAttribute1) - AD attribute holding the Chromebook serial number, used as the identity key for matching and for TameMyCerts certificate-to-account resolution. Validated at startup against the AD schema (must exist and be single-valued) to catch typos.-SamAccountPrefixparameter (defaultCB-) - prefix for the generated SAM name.-Serverparameter - pins all AD operations to a single DC (defaults to the PDC emulator) to avoid replication-delay races between consecutive reads/writes.-OrgUnitGroupMapparameter - hashtable ofregex pattern -> single AD group. Devices whoseorgUnitPathmatches a pattern are added to the associated group; memberships are reconciled (a device is removed from a rule's group once itsorgUnitPathno longer matches). Reconciliation is bounded to groups listed in the map;-MemberOfgroups are still add-only. Conflicts between-MemberOfand-OrgUnitGroupMapthrow at startup. Invalid regex or missing groups also throw at startup.- Disable/re-enable lifecycle driven by CSV
statuscolumn. Non-ACTIVErows (e.g.DEPROVISIONED,DISABLED) disable the matching AD account if it exists; missing AD accounts for non-ACTIVEdevices are not created. Accounts whose CSV status returns toACTIVEare re-enabled. Inactive serials are excluded from the deletion pass (still present in CSV). - Return object fields:
Disabled,Reenabled,InactiveCount. - Event ID 1102 - legacy account detected (no value in
-MatchAttribute), create skipped to avoid duplicates. - Event IDs 1122 / 1123 - account removed from AD group (OU-rule reconcile) / error removing from group.
- Event IDs 1130 / 1131 - AD account disabled (CSV status not
ACTIVE) / error disabling. - Event IDs 1132 / 1133 - AD account re-enabled (CSV status returned to
ACTIVE) / error re-enabling. - README section on registering the Windows Event Log source manually (for scheduled-task service accounts without local admin).
Changed
Invoke-PKChromebookLifecyclenow matches AD accounts via-MatchAttributeinstead of byName/sAMAccountName. New accounts get a randomly generatedsAMAccountNameof the formCB-<11 hex chars>(14 characters), decoupled from the Chromebook serial number — which is now stored in the attribute named by-MatchAttribute. The 15-character NetBIOS limit applies only to the generated SAM name; serial numbers of any length are supported.DNSHostNameis built from<serialNumber>.<DnsDomain>(was<samName>.<DnsDomain>) so Google MDM WiFi profiles authenticate against the correct hostname at NPS/RADIUS. Cert-to-AD matching is unaffected — it still resolves via-MatchAttribute.- CSV gating column changed from
provisionStatus=PROVISIONEDtostatus=ACTIVEto match the actual Google Admin Console export schema. - Account creation is now atomic:
MatchAttributeand all-AttributeMapvalues are written viaNew-ADComputer -OtherAttributes, eliminating the previous create-then-set race that could leave orphan accounts. - Renamed
Get-GoogleChromeDevicestoGet-GoogleChromeDevice(singular noun per PowerShell conventions). File renamed to match.[OutputType]declared. - Upgraded module scaffold (build pipeline, VS Code tasks, PSScriptAnalyzer settings, generic tests) to the latest templates.
- Introduced
GitVersion.ymlfor automated versioning. - Event Log writes use
[System.Diagnostics.EventLog]::WriteEntry(...)instead ofWrite-EventLog(which was removed fromMicrosoft.PowerShell.Managementin PowerShell 7). Removed the now-unusedEventLogNameparameter fromWrite-CbLog.
Fixed
Invoke-PKChromebookPreprovisionno longer writes per-file.logand-summary.txtartifacts under-WhatIf. Those files imply concrete results (created / skipped serials, totals) and were misleading on dry runs. The rolling Write-CbLog audit trail is still written under-WhatIfper the entry below.- Log file writes (
Out-FileinWrite-CbLogand the run summary) no longer suppressed by-WhatIf— the audit trail is always written, even in dry runs. Import-Module ActiveDirectoryno longer emits implicit-remoting proxy-copyWhat if:noise under-WhatIf.$WhatIfPreferenceis temporarily cleared around the import (and restored immediately after) so the temporaryremoteIpMoProxy_*file copies don't inherit it. Wrapped intry/catchto surface a clear error if RSAT is missing.
Migration
- TameMyCerts policy must be repointed to
<DirectoryServicesAttribute>extensionAttribute1</DirectoryServicesAttribute>(or the attribute chosen via-MatchAttribute). Coordinated outside this module. - Existing accounts from prior versions are handled by delete-and-recreate, and require two runs because deletion happens after create/update inside a single invocation:
- Run 1 with
-EnableDeletion(add-ForceDeleteif over the safety threshold): legacy accounts trigger event 1102 and are skipped for creation, then removed by the deletion pass. - Run 2: legacy accounts are gone, new
CB-<hex>accounts are created.
- Run 1 with
- Tip: dry-run with
-WhatIf -EnableDeletionfirst to count legacy accounts before deciding on-ForceDelete. - Accounts already on the new
CB-<hex>scheme but with<samName>.<DnsDomain>DNSHostName will be migrated to<serial>.<DnsDomain>in-place on the next run (event 1110 with aDNSHostName: ... -> ...change). Expect a one-shot batch of "updated" accounts.
Known gaps
- No Pester tests covering the new naming behaviour (legacy detection, conflict guard, SAM prefix validation, DNSHostName regen). To follow up in a separate change.
This package has no dependencies.
| Version | Downloads | Last updated |
|---|---|---|
| 0.5.0-rc.1 | 2 | 07/01/2026 |
| 0.4.0-rc.3 | 2 | 07/01/2026 |
| 0.4.0-rc.2 | 1 | 07/01/2026 |
| 0.4.0-rc.1 | 2 | 07/01/2026 |
| 0.3.0 | 1 | 06/25/2026 |
| 0.3.0-rc.21 | 1 | 07/01/2026 |
| 0.3.0-rc.20 | 1 | 06/23/2026 |
| 0.3.0-rc.19 | 1 | 06/23/2026 |
| 0.3.0-rc.18 | 2 | 06/22/2026 |
| 0.3.0-rc.17 | 2 | 06/17/2026 |
| 0.3.0-rc.16 | 8 | 06/17/2026 |
| 0.3.0-rc.15 | 2 | 06/17/2026 |
| 0.3.0-rc.14 | 1 | 06/17/2026 |
| 0.3.0-rc.13 | 2 | 06/17/2026 |
| 0.3.0-rc.12 | 2 | 06/17/2026 |
| 0.3.0-rc.11 | 3 | 06/17/2026 |
| 0.3.0-rc.10 | 2 | 06/17/2026 |
| 0.3.0-rc.9 | 1 | 06/16/2026 |
| 0.3.0-rc.8 | 1 | 06/16/2026 |
| 0.3.0-rc.7 | 2 | 06/12/2026 |
| 0.3.0-rc.6 | 8 | 06/09/2026 |