[version]$RequiredPSversion = [version]"7.5.1" $currentPSVersion = (Get-Host).Version Write-Host "Required PowerShell version: $RequiredPSversion" -ForegroundColor Blue if ($currentPSVersion -lt $RequiredPSversion) { Write-Host "PowerShell $RequiredPSversion or higher is required. You have $currentPSVersion." -ForegroundColor Red exit 1 } else { Write-Host "PowerShell version $currentPSVersion is compatible." -ForegroundColor Green } function Set-SmooshAssetFieldsToField { param ( [PSCustomObject]$sourceAsset, [array]$smooshsource, [bool]$includeBlanks=$false ) if ($excludeHTMLinSMOOSH -and $true -eq $excludeHTMLinSMOOSH) { $lineDelmit = " " } else { $lineDelmit = "

" } foreach ($sourcefieldsmoosh in $smooshsource) { if ($null -eq $($($sourceasset.fields | where-object {$_.label -eq $sourcefieldsmoosh}).value)){ if ($false -eq $includeBlanks) {continue} } if ($includeLabelInSmooshedValues){ $header = "$sourcefieldsmoosh -" } else {$header = ""} $smooshin=@" $header $($($sourceasset.fields | where-object {$_.label -eq $sourcefieldsmoosh}).value) "@ $smoosh=@" $smoosh $lineDelmit $smooshin "@ } if ($excludeHTMLinSMOOSH -and $true -eq $excludeHTMLinSMOOSH) { Write-Host "Not using HTML for smoosh; Cleaning values to text-friendly single-line." $smoosh = $smoosh -replace "`r?`n", ' ' $smoosh = $smoosh -replace '\s{2,}', ' ' $smoosh = Remove-HtmlTags -InputString $smoosh $smoosh = $smoosh.Trim() } write-host "Smooshed: $smoosh" write-host "$($($smoosh | ConvertTo-Json -depth 66).ToString())" return $smoosh } function Get-RelinkableAssetTagLayoutFields { param ( [int]$fromLayoutId ) $linkableLayouts = @() $labelLinkMap = @{} $relinkables=$($(Get-HuduAssetLayouts -id $fromLayoutId).fields | where-object {$_.field_type -eq "AssetTag" -and $null -ne $_.linkable_id}) write-host "$($relinkables.count) are likely relinkable." $linkableIDX=0 foreach ($relinkable in $relinkables){ $linkableIDX=$linkableIDX+1 $linkablelayout = Get-HuduAssetLayouts -id $relinkable.linkable_id if (-not $linkablelayout -or $null -eq $linkablelayout) {continue} $labelLinkMap[$relinkable.label]=$linkablelayout write-host "linkable $linkableIDX of $($relinkables.count): label $($relinkable.label) is linkable to $($linkablelayout.name)" $linkableLayouts+=$linkablelayout } return $labelLinkMap } function Get-CleansedEmailAddresses { <# returns a semicolon-delimited series of email addresses (if going to Text field, it's good to do this after stripping HTML, as to remove table row / column names) #> param ( [string]$InputString, [string]$pattern = '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}' ) $cleansed = ( $inputString | Select-String -AllMatches -Pattern $pattern ).Matches.Value -join '; ' return "$cleansed".Trim() } function Get-SmooshedLinkableDescription { param ( [array]$linkableObjects ) $description="" if (-not $linkableObjects -or $linkableObjects.count -lt 1) { return "" } foreach ($linkable in $linkableObjects) { if ($linkable.linkedasset.url){ $descriptor=@"

Related $($linkable.LinkedLayout.name) - $($linkable.LinkedAsset.name) "@ } else { $descriptor=@" Related $($linkable.LinkedLayout.name) - $($linkable.LinkedAsset.name) "@ } $Description = "$( ($_.fields | Where-Object {$_.required}).Count ) required fields, $( ($_.fields | Where-Object {-not $_.required}).Count ) optional fields ..." } return $description } function Get-HuduModule { param ( [string]$HAPImodulePath = "C:\Users\$env:USERNAME\Documents\GitHub\HuduAPI\HuduAPI\HuduAPI.psm1", [bool]$use_hudu_fork = $true ) if ($true -eq $use_hudu_fork) { if (-not $(Test-Path $HAPImodulePath)) { $dst = Split-Path -Path (Split-Path -Path $HAPImodulePath -Parent) -Parent Write-Host "Using Lastest Master Branch of Hudu Fork for HuduAPI" $zip = "$env:TEMP\huduapi.zip" Invoke-WebRequest -Uri "https://github.com/Hudu-Technologies-Inc/HuduAPI/archive/refs/heads/master.zip" -OutFile $zip Expand-Archive -Path $zip -DestinationPath $env:TEMP -Force $extracted = Join-Path $env:TEMP "HuduAPI-master" if (Test-Path $dst) { Remove-Item $dst -Recurse -Force } Move-Item -Path $extracted -Destination $dst Remove-Item $zip -Force } } else { Write-Host "Assuming PSGallery Module if not already locally cloned at $HAPImodulePath" } if (Test-Path $HAPImodulePath) { Import-Module $HAPImodulePath -Force Write-Host "Module imported from $HAPImodulePath" } elseif ((Get-Module -ListAvailable -Name HuduAPI).Version -ge [version]'2.4.4') { Import-Module HuduAPI Write-Host "Module 'HuduAPI' imported from global/module path" } else { Install-Module HuduAPI -MinimumVersion 2.4.5 -Scope CurrentUser -Force Import-Module HuduAPI Write-Host "Installed and imported HuduAPI from PSGallery" } } function Set-HuduInstance { $HuduBaseURL = $HuduBaseURL ?? $((Read-Host -Prompt 'Set the base domain of your Hudu instance (e.g https://myinstance.huducloud.com)') -replace '[\\/]+$', '') -replace '^(?!https://)', 'https://' $HuduAPIKey = $HuduAPIKey ?? "$(read-host "Please Enter Hudu API Key")" while ($HuduAPIKey.Length -ne 24) { $HuduAPIKey = (Read-Host -Prompt "Get a Hudu API Key from $($settings.HuduBaseDomain)/admin/api_keys").Trim() if ($HuduAPIKey.Length -ne 24) { Write-Host "This doesn't seem to be a valid Hudu API key. It is $($HuduAPIKey.Length) characters long, but should be 24." -ForegroundColor Red } } New-HuduAPIKey $HuduAPIKey Clear-Host New-HuduBaseURL $HuduBaseURL } function Get-RelinkableRelationsForAsset { param ( [PSCustomObject]$sourceAsset, [hashtable]$labelLinkMap ) $linkableObjects = @() foreach ($linkableField in $sourceAsset.fields | Where-Object { $_.label -and $_.label -in $labelLinkMap.Keys }) { $layoutForLinking = $labelLinkMap[$linkableField.label] try { $linkedItems = $null if ($linkableField.value -is [string] -and $linkableField.value.Trim().StartsWith("[")) { $linkedItems = $linkableField.value | ConvertFrom-Json } foreach ($linkedItem in $linkedItems) { $linkedAsset = Get-HuduAssets -Id $linkedItem.id if ($false -eq $includeRelationsForArchived -and $true -eq $linkedAsset.archived){ write-host "archived link, continuing" continue } $linkableObjects+=[PSCustomObject]@{ SourceAssetId = $sourceAsset.id SourceField = $linkableField.label LinkedAsset = $linkedAsset LinkedLayout = $layoutForLinking } } } catch { Write-Warning "Could not parse linked values for field [$($linkableField.label)] in asset [$($sourceAsset.id)]" } } return $linkableObjects } $PerJobSettings = @' # if fields are blank, exclude during smoosh procress? $includeblanksduringsmoosh = $false # relate archived objects to new asset / object $includeRelationsForArchived = $true # set below to true if smooshing to plaintext field, otherwise leave for richtext field # (strip html when going to text field) $excludeHTMLinSMOOSH = $false # include description of related objects in smoosh # related objects will have a 1-line description based on related object type and name $describeRelatedInSmoosh = $true # include label - above value in smooshed? IE - # label - # value $includeLabelInSmooshedValues = $true '@ function Remove-HtmlTags { param ( [string]$InputString ) $tags = @( 'hr','br', 'tr', 'td', 'th', 'table', 'div', 'span', 'p', 'ul', 'ol', 'li', 'h[1-6]', 'strong', 'em', 'b', 'i', 'colgroup', 'col', 'input', 'column', 'section', 'article', 'header', 'footer', 'aside', 'nav', 'main', 'figure', 'figcaption', 'blockquote', 'pre', 'address', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'thead', 'tbody', 'tfoot','script','noscript','style','template','head','svg','math' ) $cleaned = $InputString foreach ($tag in $tags) { # Regex matches both opening and closing $pattern = "<\/?$tag\b[^>]*>" $cleaned = [regex]::Replace($cleaned, $pattern, " ", "IgnoreCase") } return $cleaned.Trim() } function build-templatemap { param ([array]$destfields,[string]$mapfile) # Build entries like: @{from='';to='Some Label'} $mapEntries = foreach ($f in $destfields) { if ($f.field_type -eq "AssetTag") {write-host "Skipping asset tag for $($f.label), those will be relinked."; continue} $toEsc = ([string]$f.label) -replace "'", "''" # double single-quotes inside single-quoted PS strings $desttype = ([string]$($f.field_type ?? $f.type)) -replace "'", "''" # double single-quotes inside single-quoted PS strings $req = ([string]$($f.required ?? $false)) -replace "'", "''" # double single-quotes inside single-quoted PS strings if ($desttype -eq "AddressData") { "@{to='$toEsc'; from='Meta'; dest_type='AddressData'; required='$req'; address=@{ address_line_1=@{from=''} address_line_2=@{from=''} city=@{from=''} state=@{from=''} zip=@{from=''} country_name=@{from=''} }}" } else { "@{from='';to='$toEsc'; dest_type='$desttype'; required='$req'; striphtml='False'}" } } # Wrap and write $mappingText = @' # source $CONSTANTS=@( ## @{literal="constval";to_label="constfield"} ) $SMOOSHLABELS=@() $mapping=@( '@ + ($mapEntries -join ",`n") + @' ) '@ + @" $PerJobSettings "@ Set-Content -Path $mapfile -Value $mappingText -Encoding UTF8 } function Select-ObjectFromList($objects, $message, $inspectObjects = $false, $allowNull = $false) { $validated = $false while (-not $validated) { if ($allowNull) { Write-Host "0: None/Custom" } for ($i = 0; $i -lt $objects.Count; $i++) { $object = $objects[$i] $displayLine = if ($inspectObjects) { "$($i+1): $(Write-InspectObject -object $object)" } elseif ($null -ne $object.OptionMessage) { "$($i+1): $($object.OptionMessage)" } elseif ($null -ne $object.name) { "$($i+1): $($object.name)" } else { "$($i+1): $($object)" } Write-Host $displayLine -ForegroundColor $(if ($i % 2 -eq 0) { 'Cyan' } else { 'Yellow' }) } $choice = Read-Host $message if (-not ($choice -as [int])) { Write-Host "Invalid input. Please enter a number." -ForegroundColor Red continue } $choice = [int]$choice if ($choice -eq 0 -and $allowNull) { return $null } if ($choice -ge 1 -and $choice -le $objects.Count) { return $objects[$choice - 1] } else { Write-Host "Invalid selection. Please enter a number from the list." -ForegroundColor Red } } } function Get-FieldValueByLabel { param([array]$Fields, [string]$Label) if (-not $Label) { return $null } ($Fields | Where-Object { $_.label -eq $Label } | Select-Object -First 1).value } function Normalize-Region { param([string]$State) if (-not $State) { return $null } $s = $State.Trim() # Already 2 letters? if ($s -match '^[A-Za-z]{2}$') { return $s.ToUpper() } $us = @{ 'alabama'='AL'; 'alaska'='AK'; 'arizona'='AZ'; 'arkansas'='AR'; 'california'='CA' 'colorado'='CO'; 'connecticut'='CT'; 'delaware'='DE'; 'florida'='FL'; 'georgia'='GA' 'hawaii'='HI'; 'idaho'='ID'; 'illinois'='IL'; 'indiana'='IN'; 'iowa'='IA' 'kansas'='KS'; 'kentucky'='KY'; 'louisiana'='LA'; 'maine'='ME'; 'maryland'='MD' 'massachusetts'='MA'; 'michigan'='MI'; 'minnesota'='MN'; 'mississippi'='MS'; 'missouri'='MO' 'montana'='MT'; 'nebraska'='NE'; 'nevada'='NV'; 'new hampshire'='NH'; 'new jersey'='NJ' 'new mexico'='NM'; 'new york'='NY'; 'north carolina'='NC'; 'north dakota'='ND' 'ohio'='OH'; 'oklahoma'='OK'; 'oregon'='OR'; 'pennsylvania'='PA'; 'rhode island'='RI' 'south carolina'='SC'; 'south dakota'='SD'; 'tennessee'='TN'; 'texas'='TX'; 'utah'='UT' 'vermont'='VT'; 'virginia'='VA'; 'washington'='WA'; 'west virginia'='WV'; 'wisconsin'='WI'; 'wyoming'='WY' 'district of columbia'='DC'; 'washington dc'='DC'; 'dc'='DC' } $key = $s.ToLower() if ($us.ContainsKey($key)) { return $us[$key] } return $s # fallback (leave as-is) } function Normalize-CountryName { param([string]$Country) if (-not $Country) { return $null } $c = $Country.Trim() $map = @{ 'us'='USA'; 'u.s.'='USA'; 'u.s.a'='USA'; 'usa'='USA'; 'united states'='USA'; 'united states of america'='USA' 'uk'='United Kingdom'; 'u.k.'='United Kingdom'; 'gb'='United Kingdom'; 'gbr'='United Kingdom' 'uae'='United Arab Emirates' } $key = $c.ToLower().Replace('.','') if ($map.ContainsKey($key)) { return $map[$key] } # Title-case fallback return -join ($c.ToLower().Split(' ') | ForEach-Object { if ($_){ $_.Substring(0,1).ToUpper()+$_.Substring(1) } }) } function Normalize-Zip { param([string]$Zip) if (-not $Zip) { return $null } $z = $Zip -replace '\s+', '' # collapse spaces (e.g., “802 02”) return $z.Trim() } function Write-InspectObject { param ( [object]$object, [int]$Depth = 32, [int]$MaxLines = 16 ) $stringifiedObject = $null if ($null -eq $object) { return "Unreadable Object (null input)" } # Try JSON $stringifiedObject = try { $json = $object | ConvertTo-Json -Depth $Depth -ErrorAction Stop "# Type: $($object.GetType().FullName)`n$json" } catch { $null } # Try Format-Table if (-not $stringifiedObject) { $stringifiedObject = try { $object | Format-Table -Force | Out-String } catch { $null } } # Try Format-List if (-not $stringifiedObject) { $stringifiedObject = try { $object | Format-List -Force | Out-String } catch { $null } } # Fallback to manual property dump if (-not $stringifiedObject) { $stringifiedObject = try { $props = $object | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name $lines = foreach ($p in $props) { try { "$p = $($object.$p)" } catch { "$p = " } } "# Type: $($object.GetType().FullName)`n" + ($lines -join "`n") } catch { "Unreadable Object" } } if (-not $stringifiedObject) { $stringifiedObject = try {"$($($object).ToString())"} catch {$null} } # Truncate to max lines if necessary $lines = $stringifiedObject -split "`r?`n" if ($lines.Count -gt $MaxLines) { $lines = $lines[0..($MaxLines - 1)] + "... (truncated)" } return $lines -join "`n" } function Set-LayoutsForTransfer { param ($allLayouts) $layoutMap = @{} foreach ($layout in $allLayouts) { $layoutMap[$layout.id] = $layout } $layoutSummaries = $allLayouts | ForEach-Object { [PSCustomObject]@{ ID = $_.id Description = "$($($_.fields | where-object {$_.required -and $required -eq $true}).Count) required fields, $($($_.fields | where-object {-not $_.required -or $required -eq $false}).Count) optional fields and $($_.assetsInLayoutCount) assets present, originally created at $($_.created_at)" Name = $_.name }} write-host "$(if ($layoutSummaries.count -ne $allLayouts.count) { "$([int]$allLayouts.count - [int]$layoutSummaries.count) layouts were excluded due to not having fields, not having assets, or being otherwise ineligible." } else { "created user-friendly summaries for $($layoutSummaries.count) asset layouts" })" -ForegroundColor darkcyan $sourceLayout = $null $destLayout = $null while ($true) { $sourceSummary = Select-ObjectFromList -objects $layoutSummaries -message "Which source / origin asset layout? [layouts without assets omitted for source]" -allowNull $false -inspectObjects $inspectlayouts $sourceLayout = $layoutMap[$sourceSummary.ID] $destSummaries = $layoutSummaries | Where-Object { $_.ID -ne $sourceLayout.id } $destSummary = Select-ObjectFromList -objects $destSummaries -message "Which dest / target asset layout?" -allowNull $false -inspectObjects $inspectlayouts $destLayout = $layoutMap[$destSummary.ID] if ($($null -ne $sourceLayout -and $null -ne $destLayout) -and $(Select-ObjectFromList -objects @("yes","no") -message "You've selected source layout as: $($sourceLayout.name) and dest layout as: $($destLayout.name). Proceed?") -eq "yes") { return @{ SourceLayout = $sourceLayout DestLayout = $destLayout } } else { Write-Host "Opting to re-select." } } } function Write-ErrorObjectsToFile { param ( [Parameter(Mandatory)] [object]$ErrorObject, [Parameter()] [string]$Name = "unnamed", [Parameter()] [ValidateSet("Black","DarkBlue","DarkGreen","DarkCyan","DarkRed","DarkMagenta","DarkYellow","Gray","DarkGray","Blue","Green","Cyan","Red","Magenta","Yellow","White")] [string]$Color ) $stringOutput = try { $ErrorObject | Format-List -Force | Out-String } catch { "Failed to stringify object: $_" } $propertyDump = try { $props = $ErrorObject | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name $lines = foreach ($p in $props) { try { "$p = $($ErrorObject.$p)" } catch { "$p = " } } $lines -join "`n" } catch { "Failed to enumerate properties: $_" } $logContent = @" ==== OBJECT STRING ==== $stringOutput ==== PROPERTY DUMP ==== $propertyDump "@ if ($ErroredItemsFolder -and (Test-Path $ErroredItemsFolder)) { $SafeName = ($Name -replace '[\\/:*?"<>|]', '_') -replace '\s+', '' if ($SafeName.Length -gt 60) { $SafeName = $SafeName.Substring(0, 60) } $filename = "${SafeName}_error_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" $fullPath = Join-Path $ErroredItemsFolder $filename Set-Content -Path $fullPath -Value $logContent -Encoding UTF8 if ($Color) { Write-Host "Error written to $fullPath" -ForegroundColor $Color } else { Write-Host "Error written to $fullPath" } } if ($Color) { Write-Host "$logContent" -ForegroundColor $Color } else { Write-Host "$logContent" } } # init and vars Get-HuduModule Set-HuduInstance $CONSTANTS=@() $SMOOSHLABELS=@() $mapping=@() $mapfile = "mapping.ps1" # $CreateAsIPAM=$true $inspectlayouts = $false $archivesource = $false #simpletransfer load datas write-host "$(if ($allassets -and $null -ne $allassets) {'using existing asset cache'} else {'refreshing asset cache'})" $allassets = $allassets ?? $(get-huduassets) write-host "refreshing layouts cache (every time)" $assetlayouts = get-huduassetlayouts $totallayouts = $assetlayouts.count write-host "$(if ($allrelations -and $null -ne $allrelations) {'using existing relations cache'} else {'refreshing relations cache'})" $allrelations = $allrelations ?? $(Get-HuduRelations) write-host "adding/calculating addtitional properties for layouts" foreach ($layout in $assetlayouts) {$layout | Add-Member -NotePropertyName assetsInLayoutCount -NotePropertyValue $($allAssets | Where-Object {$_.asset_layout_id -eq $layout.id}).count -Force} # foreach ($layout in $assetlayouts | where-object {$_.assetsInLayoutCount -lt 1 -and $($_.active ?? $true)}) { # write-host "archiving emtpy layout $($layout.name)" # Set-HuduAssetLayout -id $layout.id -Active $false # } $assetlayouts=$assetlayouts | where-object {$_.assetsInLayoutCount -gt 0} $assetlayouts = $assetlayouts | Sort-Object Name $usablelayouts = $assetlayouts.count write-host "$($totallayouts - $usablelayouts) omitted and marked inactive. $totallayouts available layouts." $choice=Set-LayoutsForTransfer -allLayouts $assetlayouts $sourceassetlayout = $choice.SourceLayout $destassetlayout = $choice.DestLayout foreach ($layout in @($sourceassetlayout, $destassetlayout)){ write-host "getting relinkable fields from layout $($layout.name)..." $layout | Add-Member -NotePropertyName linkables -NotePropertyValue $(Get-RelinkableAssetTagLayoutFields -fromLayoutId $layout.id) -Force } if ($(test-path "$mapfile")) { write-host "backed up $mapfile to $mapfile.old"; Move-Item $mapfile "$mapfile.old" -Force } # get fields mapped and ready $srcfields=@() foreach ($field in $sourceassetlayout.fields | Where-Object {$_.field_type -ne "AssetTag"}) { $srcfields+=@{label = $field.label; type = $field.field_type; required = $($field.required ?? $false)} } $dstfields=@() foreach ($field in $destassetlayout.fields | Where-Object {$_.field_type -ne "ListSelect"}) { $dstfields+=@{label = $field.label; field_type = $field.field_type; required = $($field.required ?? $false)} } foreach ($fields in @(@{name="source"; value=$srcfields}, @{name="dest"; value=$dstfields})) { $fields.value | convertto-json -depth 66 | out-file "$($fields.name)-fields.json" } build-templatemap -destfields $dstfields -mapfile $mapfile read-host "press enter if you filled in your mapfile, $mapfile" if (-not $(test-path "$mapfile")) { exit } . .\$mapfile $sourcedestlabels = @{} $sourcedestrequired = @{} $sourcedestStripHTML = @{} $sourceDestDataType = @{} $addressMapsByDest = @{} foreach ($entry in $mapping) { if ($entry.dest_type -eq 'AddressData') { $addressMapsByDest[$entry.to] = $entry.address $sourcedestrequired[$entry.from] = $false $sourceDestDataType[$entry.from] = 'AddressData' $sourcedestlabels[$entry.from] = 'Meta' continue } $sourcedestStripHTML[$entry.from] = [bool]$(@('t','true','y','yes') -contains "$($entry.striphtml ?? "False")".ToLower()) write-host "mapping $($entry.from) to $($entry.to) $(if ($true -eq $sourcedestStripHTML[$entry.from]) {"destination field of $($entry.to) will have HTML stripped."} else {'as-is'})" $sourcedestlabels[$entry.from] = $entry.to $sourcedestrequired[$entry.from] = $($entry.to ?? $false) $sourceDestDataType[$entry.from] = $($entry.dest_type ?? 'Text') } read-host "$($($addressMapsByDest.GetEnumerator()).count) Location Types in Target press enter to proceed" $sourceassets = $($allAssets | Where-Object {$_.asset_layout_id -eq $sourceassetlayout.id}) $destassets = $($allAssets | Where-Object {$_.asset_layout_id -eq $destassetlayout.id}) if ($sourceassets.count -lt 1) { write-host "NO SOURCE ASSETS!"; exit} $mappingtosmooshed = [bool]$($SMOOSHLABELS.count -gt 0) if ($mappingtosmooshed) { $smooshmappingto = ($mapping | Where-Object { $_.from -eq 'SMOOSH' }).to write-host "Smooshing $SMOOSHLABELS => $mappingtosmooshed; $smooshmappingto" } if ($CONSTANTS) { foreach ($c in $CONSTANTS){ write-host "Dest Labels containing $($c.to_label) will be given static value from literal $($c.literal) as literal value!" } } else {write-host "No constants mapped"} $totalcounts = @{ fromablescreated=0 toablescreated=0 assetsarchived=0 assetsmoved=0 assetsskipped=0 assetsmatched=0 errored=0 sourceassetcount=$sourceassets.count } Write-Host "Smooshing $(if ($excludeHTMLinSMOOSH -and $true -eq $excludeHTMLinSMOOSH) {'using plaintext value-joining'} else {'using traditional HTML value joining'})" read-host "$($sourceassets.count) source assets and $($destassets.count) dest assets. press enter to proceed" $sourceassetsIDX=0 foreach ($originalasset in $sourceassets) { $sourceassetsIDX=$sourceassetsIDX+1 $linkableToAssetInfo = $null write-host "matching existing assets to asset $sourceassetsIDX of $($sourceassets.count) in destination layout assets ($($destassets.count) total) to determine if overlap" $match = $destassets | where-object {$_.company_id -eq $originalasset.company_id -and $_.name -like "*$($originalasset.name)*"} | Select-Object -First 1 if ($match -and $null -ne $match) { $totalcounts.assetsmatched=$totalcounts.assetsmatched+1 write-host "match found in dest layout. (#$($totalcounts.assetsmatched)) thus far" write-host "original: $($($originalasset | ConvertTo-Json -depth 6).ToString())" -ForegroundColor Yellow write-host "match: $($($match | ConvertTo-Json -depth 6).ToString())" -ForegroundColor Blue continue $archiveChoice=$(select-objectfromlist -message "which action to take for match?" -objects @("archive match","move anyway, archive original","skip")) if ($archiveChoice -eq "archive match") { Set-HuduAssetArchive -CompanyId $originalasset.company_id -Id $originalasset.id -Archive $true $totalcounts.assetsarchived=$totalcounts.assetsarchived+1 } elseif ($archiveChoice -eq "skip") { $totalcounts.assetsskipped=$totalcounts.assetsskipped+1 continue } else {write-host "archive after moving as usual"} } $transformedFields = @() if ($CONSTANTS -and $CONSTANTS.count -gt 0) { foreach ($c in $CONSTANTS){ $transformedFields += @{$c.to_label = $c.literal} } } foreach ($field in $originalasset.fields) { $transformedlabel = $sourcedestlabels[$field.label] ?? $null $destTranslationFieldRequired = $("$($sourcedestrequired[$field.label])".ToLower() -eq 'true') ?? $false $stripHTML = $($sourcedestStripHTML["$($field.label)"] ?? $false) $destFieldType = $sourceDestDataType["$($field.label)"] ?? 'Text' if (-not $transformedlabel -or $null -eq $transformedlabel) {continue} if (-not $field.value -or $null -eq $field.value) { write-host "no translate for $($field.label)"; if ($true -eq $destTranslationFieldRequired) { write-host "no value for REQUIRED $($field.label) => $transformedlabel" $field.value = $($(read-host "target field $($field.label) => $transformedlabel is required but null, enter value") ?? "None") } else { write-host "no value for optional $($field.label) => $transformedlabel" continue } } if ($true -eq $stripHTML) { $field.value="$(Remove-HtmlTags -InputString "$($field.value)")" } if ($destFieldType -eq "Email" -or ($($destFieldType -eq "Text" -and $transformedlabel -like "*Email*"))){ $field.value="$(Get-CleansedEmailAddresses -InputString "$($field.value)")".Trim() } $transformedFields += @{$transformedlabel = $field.value} write-host "$($field.label) => $transformedlabel for value $($field.value)" } foreach ($kv in $addressMapsByDest.GetEnumerator()) { $destLabel = $kv.Key $addrMap = $kv.Value $addr1 = Get-FieldValueByLabel $originalasset.fields $addrMap.address_line_1.from $addr2 = Get-FieldValueByLabel $originalasset.fields $addrMap.address_line_2.from $city = Get-FieldValueByLabel $originalasset.fields $addrMap.city.from $state = Get-FieldValueByLabel $originalasset.fields $addrMap.state.from $zip = Get-FieldValueByLabel $originalasset.fields $addrMap.zip.from $cntry = Get-FieldValueByLabel $originalasset.fields $addrMap.country_name.from $state = Normalize-Region $state $zip = Normalize-Zip $zip $cntry = Normalize-CountryName $cntry if ($addr1 -or $addr2 -or $city -or $state -or $zip -or $cntry) { $NewAddress = [ordered]@{ address_line_1 = $addr1 city = $city state = $state zip = $zip country_name = $cntry } if ($addr2) { $NewAddress['address_line_2'] = $addr2 } $transformedFields += @{ $destLabel = $NewAddress } } } if ($sourceassetlayout.linkables -and $sourceassetlayout.linkables.keys.count -gt 0){ Write-host "Getting linkable items for asset $($originalasset.name) from $($sourceassetlayout.linkables.keys.count) potentially linkable" $linkableToAssetInfo = Get-RelinkableRelationsForAsset -sourceAsset $originalasset -labelLinkMap $sourceassetlayout.linkables } # map custom smooshed fields ( notes, richtext, whatever we smooshed to in map) if ($true -eq $mappingtosmooshed) { $valueToAdd="$(Set-SmooshAssetFieldsToField -sourceAsset $originalasset -smooshsource $SMOOSHLABELS -includeBlanks $($includeblanksduringsmoosh ?? $false))" # if linkables, smoosh in too. if ($describeRelatedInSmoosh -and $true -eq $describeRelatedInSmoosh){ $describerelated=Get-SmooshedLinkableDescription -linkableObjects $linkableToAssetInfo $valueToAdd="$describerelated
$valueToAdd" if ($true -eq $excludeHTMLinSMOOSH){$valueToAdd = Remove-HtmlTags -InputString $valueToAdd } } $transformedFields+=@{"$($sourcedestlabels["SMOOSH"])" = $valueToAdd} } $newAssetRequest = @{ Name = $originalasset.name CompanyId = $originalasset.company_id AssetLayoutId = $destassetlayout.id } if ($transformedFields -and $transformedFields.count -gt 0){ $newAssetRequest["Fields"]=$transformedFields write-host $($($transformedFields | convertto-json -depth 5).ToString()) } if ($originalAsset.primary_serial){ $newAssetRequest["PrimarySerial"]=$originalAsset.primary_serial } if ($originalAsset.primary_mail){ $newAssetRequest["PrimaryMail"]=$originalAsset.primary_mail } if ($originalAsset.primary_model){ $newAssetRequest["PrimaryModel"]=$originalAsset.primary_model } if ($originalAsset.primary_manufacturer){ $newAssetRequest["PrimaryManufacturer"]=$originalAsset.primary_manufacturer } try { write-host "$($($newAssetRequest | ConvertTo-Json -depth 66).ToString())" $newAsset = $(new-huduasset @newAssetRequest).asset write-host "Created asset $($newAsset.id)" # archive new asset if original was archived if ($originalasset.archived -eq $true) { Set-HuduAssetArchive -CompanyId $newAsset.company_id -Id $newAsset.id -Archive $true $totalcounts.assetsarchived=$totalcounts.assetsarchived+1 } # archive source asset if configured to do so if ($archivesource -eq $true) { Set-HuduAssetArchive -CompanyId $originalasset.company_id -Id $originalasset.id -Archive $true $totalcounts.assetsarchived=$totalcounts.assetsarchived+1 } } catch { Write-ErrorObjectsToFile -ErrorObject @{Err=$_; request=$newAssetRequest} -Name $newAssetRequest.name } if (-not $newAsset -or $null -eq $newAsset) { Write-ErrorObjectsToFile -ErrorObject $newAssetRequest -Name "NC-$($newAssetRequest.name)" $totalcounts.errored=$totalcounts.errored+1 continue } else { $totalcounts.assetsmoved=$totalcounts.assetsmoved+1 write-host "created asset $($newasset.id)" } # add relations $sourceToables = $($($allrelations | where-object {$_.toable_type -eq 'Asset' -and $originalasset.id -eq $_.toable_id }) ?? @()) write-host "$($sourceToables.count) toable relations" $sourceFromables = $($($allrelations | where-object {$_.fromable_type -eq 'Asset' -and $originalasset.id -eq $_.fromable_id }) ?? @()) write-host "$($sourceFromables.count) fromable relations" try { $relationsTo = $sourceToables | Where-Object { $_.toable_id -eq $sourceAsset.id } foreach ($rel in $relationsTo) { $newToable+=New-HuduRelation -FromableType $rel.fromable_type -FromableId $rel.fromable_id ` -ToableType "Asset" -ToableId $newAsset.id write-host "created toable rel $($newToable.id)" $totalcounts.toablescreated= if ($newToable) {$totalcounts.toablescreated+1} else {$totalcounts.toablescreated} } $relationsFrom = $sourceFromables | Where-Object { $_.fromable_id -eq $sourceAsset.id } foreach ($rel in $relationsFrom) { $newFromable=New-HuduRelation -FromableType "Asset" -FromableId $newAsset.id ` -ToableType $rel.toable_type -ToableId $rel.toable_id write-host "created fromable rel $($newFromable.id)" $totalcounts.fromablescreated= if ($newFromable) {$totalcounts.fromablescreated+1} else {$totalcounts.fromablescreated} } } catch { $totalcounts.errored=$totalcounts.errored+1 Write-ErrorObjectsToFile -ErrorObject @{Err= $_; From = $relationsFrom; To=$relationsTo} -Name "NCREL-$($newasset.name)" } if ($linkableToAssetInfo -and $linkableToAssetInfo.count -gt 0){ write-host "Asset has external asset links, relinking $($linkableToAssetInfo.count) for $($originalasset.name)" foreach ($linkableToAsset in $linkableToAssetInfo) { $linkedAsset=$linkableToAsset.LinkedAsset if (-not $linkableToAsset.LinkedAsset) {continue} try { $newToable=New-HuduRelation -FromableType 'Asset' -ToableType "Asset" -FromableId $LinkedAsset.id -ToableID $newAsset.id $totalcounts.toablescreated= if ($newToable) {$totalcounts.toablescreated+1} else {$totalcounts.toablescreated} write-host "created asset-toable rel $($newToable.id)" } catch { $totalcounts.errored=$totalcounts.errored+1 Write-ErrorObjectsToFile -ErrorObject @{Err = $_; From = $relationsFrom; To=$relationsTo} -Name "NCREL-AL-$($newasset.name)" } } } } Write-host "wrap-up" $newlayoutname = $null if ("yes" -eq $(Select-ObjectFromList -objects @("yes","no") -message "would you like to rename source layout ($($sourceassetlayout.name))" -allowNull $false)){ $newlayoutname = read-host "what is the new name for $($sourceassetlayout.name)" } if ([string]::IsNullOrWhiteSpace($newlayoutname)) {$newlayoutname = $sourceassetlayout.name} if ("yes" -eq $(Select-ObjectFromList -objects @("yes","no") -message "would you like to archive source layout ($($sourceassetlayout.name))" -allowNull $false)){ $setsourcelayoutarchived = $true } if ("yes" -eq $(Select-ObjectFromList -objects @("yes","no") -message "would you like to archive source layout's assets? ($($sourceassets.count) total)" -allowNull $false)){ $setsourceassetsarchived = $true } if ($newlayoutname -ne $sourceassetlayout.name -or $true -eq $setsourcelayoutarchived){ Set-HuduAssetLayout -id $sourceassetlayout.id -Name $newlayoutname -Active $false } if ($true -eq $setsourceassetsarchived) { foreach ($assetarch in $(Get-HuduAssets -AssetLayoutId $sourceassetlayout.id | where-object {$_.archived -ne $true})) { $result=Set-HuduAssetArchive -id $assetarch.id -CompanyId $assetarch.company_id -archive $true $totalcounts.assetsarchived=$(if ($result) {$totalcounts.assetsarchived+1} else {$totalcounts.assetsarchived}) } } foreach ($entry in $totalcounts.GetEnumerator()) { Write-Host "$($entry.Key): $($entry.Value)" -ForegroundColor DarkCyan }