[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
}