{"id":626,"date":"2026-07-07T11:47:19","date_gmt":"2026-07-07T10:47:19","guid":{"rendered":"https:\/\/hayooh.in\/?p=626"},"modified":"2026-08-03T07:52:04","modified_gmt":"2026-08-03T06:52:04","slug":"haypoh","status":"publish","type":"post","link":"https:\/\/hayooh.in\/index.php\/2026\/07\/07\/haypoh\/","title":{"rendered":"Haypoh"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Fix<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Fix Allergan in your CSV, re-save, then run this. Save as&nbsp;<code>Desktop\\upload_final.ps1<\/code>&nbsp;in Notepad (Save as type: All Files) rather than pasting:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">powershell<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$csv      = \"C:\\Users\\HU346SK\\OneDrive - EY\\Desktop\\mapping_multi.csv\"\n$source   = \"C:\\Users\\HU346SK\\OneDrive - EY\\Desktop\\Sonnet upload\"\n$destRoot = \"C:\\Users\\HU346SK\\OneDrive - EY\\Vitality Acute Program - 17. Outside Counsel Folder Structure\"\n$dryRun   = $true\n\nfunction Get-Files($cell) {\n    ($cell -split \"`n\") | ForEach-Object {\n        $t = $_.Trim() -replace '^&#91;\\u2022\\-\\*]\\s*',''\n        if ($t -match '^(.+?\\.(pdf|docx?|msg|xlsx?))') { $Matches&#91;1] }\n    } | Where-Object { $_ }\n}\n\n$log = foreach ($row in Import-Csv $csv) {\n    $vendorPath = Join-Path $destRoot $row.Vendor.Trim()\n    $master     = $row.Master.Trim()\n    if (-not (Test-Path $vendorPath)) {\n        &#91;pscustomobject]@{Vendor=$row.Vendor; File=$master; Target=\"\"; Status=\"VENDOR FOLDER NOT FOUND\"}\n        continue\n    }\n    $base = &#91;IO.Path]::GetFileNameWithoutExtension($master)\n    $subs = Get-ChildItem $vendorPath -Directory -ErrorAction SilentlyContinue\n    $m = @($subs | Where-Object { $_.Name -eq $base -or $_.Name -eq $master })\n    if ($m.Count -eq 0) {\n        $m = @($subs | Where-Object { $_.Name -like \"$base*\" })\n        if ($m.Count -ne 1) { $m = @() }\n    }\n    if ($m.Count -eq 0) {\n        &#91;pscustomobject]@{Vendor=$row.Vendor; File=$master; Target=\"\"; Status=\"NO SUBFOLDER MATCH\"}\n        continue\n    }\n    $target = $m&#91;0].FullName\n    $files  = @($master) + (Get-Files $row.RolledUp) | Select-Object -Unique\n    foreach ($f in $files) {\n        if (Test-Path (Join-Path $target $f)) {\n            &#91;pscustomobject]@{Vendor=$row.Vendor; File=$f; Target=$m&#91;0].Name; Status=\"ALREADY THERE\"}\n            continue\n        }\n        $src = Get-ChildItem $source -Recurse -File -Filter $f -ErrorAction SilentlyContinue | Select-Object -First 1\n        if (-not $src) {\n            &#91;pscustomobject]@{Vendor=$row.Vendor; File=$f; Target=$m&#91;0].Name; Status=\"FILE NOT DOWNLOADED\"}\n        } else {\n            $st = \"OK\"\n            if (-not $dryRun) {\n                try { Copy-Item $src.FullName -Destination $target -ErrorAction Stop }\n                catch { $st = \"COPY FAILED: $($_.Exception.Message)\" }\n            }\n            &#91;pscustomobject]@{Vendor=$row.Vendor; File=$f; Target=$m&#91;0].Name; Status=$st}\n        }\n    }\n}\n\n$log | Export-Csv \"$env:USERPROFILE\\Desktop\\upload_log_final.csv\" -NoTypeInformation\n\n$rowStatus = $log | Group-Object Vendor, Target | ForEach-Object {\n    $done = @($_.Group | Where-Object Status -in \"OK\",\"ALREADY THERE\").Count\n    $miss = @($_.Group | Where-Object Status -notin \"OK\",\"ALREADY THERE\")\n    &#91;pscustomobject]@{\n        Vendor       = $_.Group&#91;0].Vendor\n        Subfolder    = $_.Group&#91;0].Target\n        Expected     = $_.Count\n        Present      = $done\n        Missing      = $miss.Count\n        RowStatus    = if ($miss.Count -eq 0) { \"COMPLETE\" } elseif ($done -eq 0) { \"NONE\" } else { \"PARTIAL\" }\n        MissingFiles = ($miss.File -join \"; \")\n    }\n}\n$rowStatus | Export-Csv \"$env:USERPROFILE\\Desktop\\row_status_final.csv\" -NoTypeInformation\n\n$log | Group-Object Status | Select Name, Count\n$rowStatus | Group-Object RowStatus | Select Name, Count<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run it:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">powershell<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>powershell -ExecutionPolicy Bypass -File \"$env:USERPROFILE\\Desktop\\upload_final.ps1\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">ALREADY THERE covers last week&#8217;s uploads \u2014 those get skipped, not re-copied. Run the dry run first and send me the counts; with 717 rows the file scan will take a few minutes since it searches the source tree per file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I am sharing this code with my colleague so Give instructions on what to change<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s a note you can send along with the script.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Before running: three lines to change at the top<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">powershell<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$csv      = \"C:\\Users\\HU346SK\\OneDrive - EY\\Desktop\\mapping_multi.csv\"\n$source   = \"C:\\Users\\HU346SK\\OneDrive - EY\\Desktop\\Sonnet upload\"\n$destRoot = \"C:\\Users\\HU346SK\\OneDrive - EY\\Vitality Acute Program - 17. Outside Counsel Folder Structure\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Replace&nbsp;<code>HU346SK<\/code>&nbsp;with your own user folder in all three. Easiest way to get the exact paths: open each folder in File Explorer, click the address bar, and copy what&#8217;s there.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>$csv<\/code>\u00a0\u2014 your mapping CSV (see below)<\/li>\n\n\n\n<li><code>$source<\/code>\u00a0\u2014 folder holding the Kira exports; the script searches subfolders too, so exports can stay in their own extracted folders<\/li>\n\n\n\n<li><code>$destRoot<\/code>\u00a0\u2014 the SharePoint library. Add a shortcut to OneDrive from the &#8220;17. Outside Counsel Folder Structure&#8221; library (Add shortcut to OneDrive), wait for it to sync, then use that local path. Note the folder name gets prefixed with the site name.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Building the CSV<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Copy columns C, E, G, I from the Master Agreement Tracker into a new workbook as columns A\u2013D. Headers must be in&nbsp;<strong>row 1<\/strong>&nbsp;(delete any blank row above) and named exactly:&nbsp;<code>Vendor<\/code>,&nbsp;<code>Master<\/code>,&nbsp;<code>Docs<\/code>,&nbsp;<code>RolledUp<\/code>. Paste as values, not formulas. Save as&nbsp;<strong>CSV UTF-8 (Comma delimited)<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Running it<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Save the script as a&nbsp;<code>.ps1<\/code>&nbsp;file via Notepad (Save as type: All Files) rather than pasting into the terminal \u2014 pasting can convert quotes to curly quotes, which breaks it. Then in PowerShell:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">powershell<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>powershell -ExecutionPolicy Bypass -File \"$env:USERPROFILE\\Desktop\\upload_final.ps1\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Close the CSV in Excel first, or the read may fail.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run once with&nbsp;<code>$dryRun = $true<\/code>&nbsp;(nothing is copied, it just reports), check the counts, then change to&nbsp;<code>$dryRun = $false<\/code>&nbsp;and run again.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Reading the output<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Two files land on your Desktop:&nbsp;<code>upload_log_final.csv<\/code>&nbsp;(one line per file) and&nbsp;<code>row_status_final.csv<\/code>&nbsp;(one line per contract family, with expected vs present counts and any missing filenames).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Statuses:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>OK<\/strong>\u00a0\u2014 copied<\/li>\n\n\n\n<li><strong>ALREADY THERE<\/strong>\u00a0\u2014 was already in the subfolder, skipped<\/li>\n\n\n\n<li><strong>FILE NOT DOWNLOADED<\/strong>\u00a0\u2014 not found in the source; needs pulling from Kira<\/li>\n\n\n\n<li><strong>VENDOR FOLDER NOT FOUND \/ NO SUBFOLDER MATCH<\/strong>\u00a0\u2014 the destination folder doesn&#8217;t exist or the name doesn&#8217;t match the tracker; flag to Akshay rather than creating folders<\/li>\n\n\n\n<li><strong>COPY FAILED<\/strong>\u00a0\u2014 usually path length over 260 chars; workaround is\u00a0<code>subst X: \"&lt;destRoot>\"<\/code>\u00a0then set\u00a0<code>$destRoot = \"X:\\\"<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The script never creates or renames folders \u2014 it only matches existing ones \u2014 so a mismatch always shows as a flag rather than a new folder appearing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then verify before running anything:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">powershell<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$csv = \"C:\\Users\\HU346SK\\OneDrive - EY\\Desktop\\mapping_multi.csv\"\n$rows = Import-Csv $csv\n\"Rows: $($rows.Count)\"\n$rows | Select -First 1 | Format-List<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You want four properties named Vendor \/ Master \/ Docs \/ RolledUp, and the RolledUp value showing multiple filenames rather than just one. If you see&nbsp;<code>H1<\/code>&nbsp;as a property name again, there&#8217;s still a blank row above the headers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Before the dry run, check the parse count against column G across every row. Paste the function first (type the quotes rather than pasting them):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">powershell<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function Get-Files($cell) {\n    ($cell -split \"`n\") | ForEach-Object {\n        $t = $_.Trim() -replace '^&#91;\\u2022\\-\\*]\\s*',''\n        if ($t -match '^(.+?\\.(pdf|docx?|msg|xlsx?))') { $Matches&#91;1] }\n    } | Where-Object { $_ }\n}\n\n$rows | ForEach-Object {\n    $p = @(Get-Files $_.RolledUp).Count + 1\n    if ($p -ne &#91;int]$_.Docs) {\n        &#91;pscustomobject]@{Vendor=$_.Vendor; Master=$_.Master; ExcelDocs=$_.Docs; Parsed=$p}\n    }\n} | Format-Table -AutoSize<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Boston Scientific should come back 45. Any row that prints is one where a filename didn&#8217;t parse cleanly \u2014 those are worth looking at individually rather than assuming the count is close enough.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Worth a quick look at what the missing line is before you do, though, since both being off-by-one suggests a pattern rather than two random glitches:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">powershell<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$r = $rows | Where-Object Vendor -eq \"Allergan\"\n$r.RolledUp -split \"`n\" | ForEach-Object { $_.Trim() }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Compare that against what&nbsp;<code>Get-Files<\/code>&nbsp;returned. If it&#8217;s a filename with an unusual extension or a line wrapped oddly in the cell, you&#8217;ll know whether to just add it manually to the CSV or whether the same thing could bite elsewhere.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The Intuitive Surgical row is probably the same class of issue.<\/strong>&nbsp;Check it:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">powershell<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$r2 = $rows | Where-Object Vendor -like \"Intuitive*\"\n$r2.RolledUp -split \"`n\" | ForEach-Object { $t = $_.Trim(); if ($t -notmatch '\\.(pdf|docx?|msg|xlsx?)') { $t } }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That prints only the lines the parser rejected, so you&#8217;ll see the malformed one directly instead of scanning 24 filenames.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">able with a move \u2014 no re-download needed. The structure is vendor \u2192 subfolder \u2192 file even for single-doc rows, which my earlier script didn&#8217;t account for. The files are all sitting one level too high.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This finds each file at vendor level and moves it into the matching subfolder:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$csv      = \"C:\\Users\\HU346SK\\OneDrive - EY\\Desktop\\mapping150.csv\"\n$destRoot = \"C:\\Users\\HU346SK\\OneDrive - EY\\Vitality Acute Program - 17. Outside Counsel Folder Structure\"\n$dryRun   = $true\n\n$fix = foreach ($row in Import-Csv $csv) {\n    $vendorPath = Join-Path $destRoot $row.Vendor.Trim()\n    $file       = $row.Master.Trim()\n    $filePath   = Join-Path $vendorPath $file\n    if (-not (Test-Path $filePath)) {\n        &#91;pscustomobject]@{Vendor=$row.Vendor; File=$file; Target=\"\"; Status=\"NOT AT VENDOR LEVEL\"}\n        continue\n    }\n    $base = &#91;IO.Path]::GetFileNameWithoutExtension($file)\n    $subs = Get-ChildItem $vendorPath -Directory\n    $match = @($subs | Where-Object { $_.Name -eq $base -or $_.Name -eq $file })\n    if ($match.Count -eq 0) {\n        $match = @($subs | Where-Object { $_.Name -like \"$base*\" })\n        if ($match.Count -ne 1) { $match = @() }\n    }\n    if ($match.Count -eq 0) {\n        &#91;pscustomobject]@{Vendor=$row.Vendor; File=$file; Target=\"\"; Status=\"NO SUBFOLDER MATCH\"}\n    } else {\n        $t = $match&#91;0]\n        if (-not $dryRun) { Move-Item $filePath -Destination $t.FullName }\n        &#91;pscustomobject]@{Vendor=$row.Vendor; File=$file; Target=$t.Name; Status=\"MOVED\"}\n    }\n}\n$fix | Export-Csv \"$env:USERPROFILE\\Desktop\\move_log_150.csv\" -NoTypeInformation\n$fix | Group-Object Status | Select Name, Count\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Matching goes exact-name-minus-extension first, then a prefix match only when it resolves to exactly one subfolder. Ambiguous or missing cases get flagged as NO SUBFOLDER MATCH and the file stays put rather than being moved somewhere guessed \u2014 the Settrax-style subfolders with long timestamp suffixes are where prefix matching earns its keep.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run with&nbsp;<code>$dryRun = $true<\/code>&nbsp;first and send me the counts. The Target column in the log lets you eyeball whether the prefix matches picked sensible folders before you commit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Those are the real-run errors, and the cause is almost certainly&nbsp;<strong>path length<\/strong>. Your destRoot is already long, and some subfolders have very long names (the Settrax one with the double timestamp is ~85 characters on its own). Add vendor + filename and you blow past the 260-character Windows limit \u2014 which surfaces exactly as &#8220;Could not find a part of the path.&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Check the ones that failed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$fix | Where-Object Status -eq \"MOVED\" | ForEach-Object {\n  $p = Join-Path (Join-Path (Join-Path $destRoot $_.Vendor.Trim()) $_.Target) $_.File\n  if (-not (Test-Path $p)) { &#91;pscustomobject]@{File=$_.File; Len=$p.Length} }\n} | Sort Len -Descending\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Anything with&nbsp;<code>Len<\/code>&nbsp;near or over 260 confirms it. Note the log says MOVED for these even though the move failed \u2014 the status was written regardless of the error, so trust this check over the log.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fix<\/strong>&nbsp;\u2014 .NET&#8217;s file API handles long paths via the&nbsp;<code>\\\\?\\<\/code>&nbsp;prefix where the cmdlet can&#8217;t:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$retry = foreach ($r in $fix | Where-Object Status -eq \"MOVED\") {\n    $vp  = Join-Path $destRoot $r.Vendor.Trim()\n    $src = Join-Path $vp $r.File\n    $dst = Join-Path (Join-Path $vp $r.Target) $r.File\n    if (Test-Path $dst) { continue }\n    if (-not (Test-Path $src)) {\n        &#91;pscustomobject]@{File=$r.File; Status=\"SOURCE GONE\"}\n        continue\n    }\n    try {\n        &#91;System.IO.File]::Move(\"\\\\?\\$src\", \"\\\\?\\$dst\")\n        &#91;pscustomobject]@{File=$r.File; Status=\"FIXED\"}\n    } catch {\n        &#91;pscustomobject]@{File=$r.File; Status=\"STILL FAILING: $($_.Exception.Message)\"}\n    }\n}\n$retry | Group-Object Status | Select Name, Count\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It skips anything already sitting correctly in its subfolder, so it&#8217;s safe to run after a partial success.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If files remain stuck after that, the practical fallback is asking Akshay whether those subfolder names can be shortened \u2014 the timestamped ones are the problem, and that&#8217;s a structure decision rather than something to change unilaterally.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The retry is reporting the&nbsp;<strong>source<\/strong>&nbsp;file missing, not the destination \u2014 so path length probably isn&#8217;t the story. Let&#8217;s see the actual paths rather than guess:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$bad = $fix | Where-Object Status -eq \"MOVED\" | ForEach-Object {\n    $vp  = Join-Path $destRoot $_.Vendor.Trim()\n    $dst = Join-Path (Join-Path $vp $_.Target) $_.File\n    if (-not (Test-Path $dst)) { $_ }\n}\n$bad | Select -First 3 | ForEach-Object {\n    $vp  = Join-Path $destRoot $_.Vendor.Trim()\n    $src = Join-Path $vp $_.File\n    $dst = Join-Path (Join-Path $vp $_.Target) $_.File\n    &#91;pscustomobject]@{\n        File    = $_.File\n        SrcOK   = Test-Path $src\n        DstDir  = Test-Path (Join-Path $vp $_.Target)\n        SrcLen  = $src.Length\n        DstLen  = $dst.Length\n        Target  = $_.Target\n    }\n} | Format-List\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That tells us which of three things it is: the file already left vendor level (SrcOK false \u2014 possibly your manual moves), the target subfolder doesn&#8217;t exist (DstDir false \u2014 bad prefix match), or lengths near 260 (genuinely path length).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Send me that output and I&#8217;ll give you the right fix rather than another guess. Worth also checking one of those vendor folders in the SharePoint browser view \u2014 seeing where the file actually sits right now is often faster than inferring it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Yes \u2014 they&#8217;ll come through as MISSING, with vendor and filename. To list just those:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$verify | Where-Object Status -eq \"MISSING\" | Format-Table Vendor, File -AutoSize\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s your download-and-redo list. Once you pull those 3 from Kira into&nbsp;<code>Sonnet upload<\/code>, you can copy them straight into the right subfolder rather than re-running the whole pipeline \u2014 by then you&#8217;ll know the exact target folder name from the verify output for neighbouring rows.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Worth cross-checking that list against the original&nbsp;<code>upload_log_150.csv<\/code>&nbsp;FILE NOT DOWNLOADED rows. If they match, the story is clean end to end: 3 never downloaded, everything else placed. If MISSING shows more than 3, something slipped between the copy and the move, and that&#8217;s worth tracing before you call it done.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Good idea \u2014 final verification. This checks every CSV row and reports where the file actually sits under its vendor folder:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$verify = foreach ($row in Import-Csv $csv) {\n    $vendor = $row.Vendor.Trim()\n    $file   = $row.Master.Trim()\n    $vp     = Join-Path $destRoot $vendor\n    if (-not (Test-Path $vp)) {\n        &#91;pscustomobject]@{Vendor=$vendor; File=$file; Location=\"\"; Status=\"NO VENDOR FOLDER\"}\n        continue\n    }\n    $hits = @(Get-ChildItem $vp -Recurse -File -Filter $file -ErrorAction SilentlyContinue)\n    if ($hits.Count -eq 0) {\n        &#91;pscustomobject]@{Vendor=$vendor; File=$file; Location=\"\"; Status=\"MISSING\"}\n    } else {\n        $h = $hits&#91;0]\n        $inSub = $h.DirectoryName -ne $vp\n        &#91;pscustomobject]@{\n            Vendor   = $vendor\n            File     = $file\n            Location = Split-Path $h.DirectoryName -Leaf\n            Status   = if ($hits.Count -gt 1) { \"DUPLICATE\" }\n                       elseif ($inSub)        { \"IN SUBFOLDER\" }\n                       else                   { \"AT VENDOR LEVEL\" }\n        }\n    }\n}\n$verify | Export-Csv \"$env:USERPROFILE\\Desktop\\verify_150.csv\" -NoTypeInformation\n$verify | Group-Object Status | Select Name, Count\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">What you want to see: everything as IN SUBFOLDER except the 3 that were never downloaded (MISSING).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If any come back AT VENDOR LEVEL, those still need moving. DUPLICATE means the same filename exists in two places under that vendor \u2014 worth looking at rather than assuming, since it could be a genuine second copy from a manual move.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The full per-row detail lands in&nbsp;<code>verify_150.csv<\/code>, which is the artifact to keep alongside the upload log for Akshay.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One note: if&nbsp;<code>subst X:<\/code>&nbsp;is still active, it doesn&#8217;t affect this \u2014&nbsp;<code>$destRoot<\/code>&nbsp;points at the real path either way. Run&nbsp;<code>subst X: \/D<\/code>&nbsp;to clean up when you&#8217;re finished.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the PowerShell session is still open,&nbsp;<code>$verify<\/code>&nbsp;is in memory:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$verify | Where-Object Status -in \"MISSING\",\"AT VENDOR LEVEL\" | Format-Table Vendor, File, Status -AutoSize\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If you&#8217;ve since closed it, read from the saved file instead:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Import-Csv \"$env:USERPROFILE\\Desktop\\verify_150.csv\" |\n  Where-Object Status -in \"MISSING\",\"AT VENDOR LEVEL\" |\n  Format-Table Vendor, File, Status -AutoSize\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the list is long enough to be awkward in the terminal, drop it to its own CSV so you can work through it in Excel:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Import-Csv \"$env:USERPROFILE\\Desktop\\verify_150.csv\" |\n  Where-Object Status -in \"MISSING\",\"AT VENDOR LEVEL\" |\n  Export-Csv \"$env:USERPROFILE\\Desktop\\to_fix.csv\" -NoTypeInformation\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Worth splitting them once you see the list: MISSING means download from Kira, AT VENDOR LEVEL means the file is already there and just needs moving down a level. Different fixes, and the vendor-level ones are quick.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Fix Fix Allergan in your CSV, re-save, then run this. Save as&nbsp;Desktop\\upload_final.ps1&nbsp;in Notepad (Save as type: All Files) rather than pasting: powershell Run it: powershell ALREADY THERE covers last week&#8217;s uploads \u2014 those get skipped, not re-copied. Run the dry run first and send me the counts; with 717 rows the file scan will take&hellip; <a class=\"more-link\" href=\"https:\/\/hayooh.in\/index.php\/2026\/07\/07\/haypoh\/\">Continue reading <span class=\"screen-reader-text\">Haypoh<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-626","post","type-post","status-publish","format-standard","hentry","category-blog","entry"],"_links":{"self":[{"href":"https:\/\/hayooh.in\/index.php\/wp-json\/wp\/v2\/posts\/626","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hayooh.in\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hayooh.in\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hayooh.in\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/hayooh.in\/index.php\/wp-json\/wp\/v2\/comments?post=626"}],"version-history":[{"count":43,"href":"https:\/\/hayooh.in\/index.php\/wp-json\/wp\/v2\/posts\/626\/revisions"}],"predecessor-version":[{"id":671,"href":"https:\/\/hayooh.in\/index.php\/wp-json\/wp\/v2\/posts\/626\/revisions\/671"}],"wp:attachment":[{"href":"https:\/\/hayooh.in\/index.php\/wp-json\/wp\/v2\/media?parent=626"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hayooh.in\/index.php\/wp-json\/wp\/v2\/categories?post=626"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hayooh.in\/index.php\/wp-json\/wp\/v2\/tags?post=626"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}