From d729b88606a314721575fb390b5e7d8a02c64103 Mon Sep 17 00:00:00 2001 From: Bobby Abellana Date: Fri, 21 Feb 2025 15:34:46 -0800 Subject: [PATCH] cursor update --- .gitignore | 3 + GraphConfig.ps1 | 2 +- SharePointFunctions.ps1 | 487 +++++++++++++++++++++++++++++++++++----- config.json | 6 +- 4 files changed, 436 insertions(+), 62 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d54c2d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.aider* +.env +venv \ No newline at end of file diff --git a/GraphConfig.ps1 b/GraphConfig.ps1 index b0e39f6..9fef7fa 100644 --- a/GraphConfig.ps1 +++ b/GraphConfig.ps1 @@ -10,7 +10,7 @@ $script:graphConfig = @{ ) } -function Connect-Graph { +function Connect-Graph { try { Connect-MgGraph -ClientId $graphConfig.ClientId ` -TenantId $graphConfig.TenantId ` diff --git a/SharePointFunctions.ps1 b/SharePointFunctions.ps1 index e8aaed1..e2c6c85 100644 --- a/SharePointFunctions.ps1 +++ b/SharePointFunctions.ps1 @@ -5,17 +5,17 @@ $script:config = $null # Initialize config variable # Load configuration from config.json function Load-Config { try { - $configPath = "$PSScriptRoot\config.json" + $configPath = Join-Path $PSScriptRoot "config.json" if (Test-Path $configPath) { $script:config = Get-Content $configPath -Raw | ConvertFrom-Json return $true } else { - $script:txtStatus.Text += "Error loading configuration: Config file not found`n" + Write-Host "Config file not found at: $configPath" return $false } } catch { - $script:txtStatus.Text += "Error loading configuration: $($_.Exception.Message)`n" + Write-Host "Error loading configuration: $($_.Exception.Message)" return $false } } @@ -396,6 +396,33 @@ function Get-XlsFilesCurrentFolder { function Convert-Files { try { + # Ensure the Testing library exists before proceeding + if (-not (Ensure-TestingLibraryExists)) { + throw "Failed to ensure Testing library exists" + } + + # Get site and drive information first + $script:txtStatus.Text += "Getting SharePoint site information...`n" + $site = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/sites/sutterhill.sharepoint.com:/sites/tax" -ErrorAction Stop + + if (-not $site) { + throw "Could not find SharePoint site" + } + + $script:txtStatus.Text += "Getting document library information...`n" + $drives = Get-MgSiteDrive -SiteId $site.id + $libraryName = if ($script:txtProdLib.Text -eq "Shared Documents") { + "Documents" + } else { + $script:txtProdLib.Text + } + $drive = $drives | Where-Object { $_.Name -eq $libraryName } + + if (-not $drive) { + $script:txtStatus.Text += "Available libraries: $($drives.Name -join ', ')`n" + throw "Library not found: $libraryName" + } + # Load the file list from the JSON file $fileListPath = "$env:TEMP\FileList.json" if (Test-Path $fileListPath) { @@ -411,6 +438,10 @@ function Convert-Files { return } + # Store drive ID in script scope for use in download/upload operations + $script:driveId = $drive.Id + $script:txtStatus.Text += "Using drive ID: $($drive.Id)`n" + # Iterate through the file list and convert each file foreach ($file in $fileList) { $originalPath = $file.OriginalPath @@ -422,49 +453,79 @@ function Convert-Files { # Download the file from SharePoint $downloadPath = "$env:TEMP\$originalFileName" try { - # Get the site first - $site = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/sites/sutterhill.sharepoint.com:/sites/tax" -ErrorAction Stop - - # Get the drive - $drives = Get-MgSiteDrive -SiteId $site.id - $libraryName = if ($script:txtProdLib.Text -eq "Shared Documents") { - "Documents" - } else { - $script:txtProdLib.Text - } - $drive = $drives | Where-Object { $_.Name -eq $libraryName } + $script:txtStatus.Text += "Downloading file: $originalFileName`n" # Get the file path relative to the library root $relativePath = $originalFileName if ($script:txtFolder.Text) { $folderPath = $script:txtFolder.Text.Replace("/sites/tax/Shared Documents/", "").TrimStart("/") $relativePath = "$folderPath/$originalFileName" + $script:txtStatus.Text += "Using relative path: $relativePath`n" } - # Construct the download URI + # Construct the download URI using the stored drive ID $encodedPath = [System.Web.HttpUtility]::UrlEncode($relativePath).Replace("+", "%20") - $downloadUri = "https://graph.microsoft.com/v1.0/drives/$($drive.Id)/root:/$($encodedPath):/content" + $downloadUri = "https://graph.microsoft.com/v1.0/drives/$($script:driveId)/root:/$($encodedPath):/content" + $script:txtStatus.Text += "Download URI: $downloadUri`n" - # Download the file - Invoke-MgGraphRequest -Uri $downloadUri -Method GET -OutputFilePath $downloadPath - if (-not (Test-Path $downloadPath)) { - throw "Failed to download file from SharePoint" + # Ensure temp directory exists + $tempDir = [System.IO.Path]::GetDirectoryName($downloadPath) + if (-not (Test-Path $tempDir)) { + New-Item -ItemType Directory -Path $tempDir -Force | Out-Null } + + # Download the file with progress tracking + $script:txtStatus.Text += "Downloading to: $downloadPath`n" + try { + $response = Invoke-MgGraphRequest -Uri $downloadUri -Method GET -OutputFilePath $downloadPath + Start-Sleep -Seconds 2 # Give file system time to catch up + + if (Test-Path $downloadPath) { + $fileInfo = Get-Item $downloadPath + $script:txtStatus.Text += "Download complete. File size: $($fileInfo.Length) bytes`n" + } else { + throw "File download appeared to succeed but file not found at: $downloadPath" + } + } catch { + $script:txtStatus.Text += "Download failed: $($_.Exception.Message)`n" + if ($_.Exception.Response) { + $script:txtStatus.Text += "Response Status Code: $($_.Exception.Response.StatusCode)`n" + } + throw + } + + # Verify the downloaded file + if (-not (Test-Path -LiteralPath $downloadPath)) { + throw "Failed to download file from SharePoint. File not found at: $downloadPath" + } + + $fileSize = (Get-Item $downloadPath).Length + if ($fileSize -eq 0) { + throw "Downloaded file is empty: $downloadPath" + } + + $script:txtStatus.Text += "File downloaded successfully to: $downloadPath`n" } catch { $script:txtStatus.Text += "Error downloading file: $($_.Exception.Message)`n" + if ($_.Exception.Response) { + $script:txtStatus.Text += "Response Status Code: $($_.Exception.Response.StatusCode)`n" + } + $script:txtStatus.Text += "Stack Trace: $($_.Exception.StackTrace)`n" continue # Move to the next file } # Convert the file to XLSX $xlsxFileName = $originalFileName -replace "\.xls[m]?$", ".xlsx" - # Before the Excel conversion, get the full paths and clean them $xlsxPath = Join-Path $env:TEMP $xlsxFileName - $downloadPath = Join-Path $env:TEMP $originalFileName - # Clean the paths and ensure they're valid + # Ensure the path is resolved $xlsxPath = [System.IO.Path]::GetFullPath($xlsxPath) - $downloadPath = [System.IO.Path]::GetFullPath($downloadPath) + + # Verify the path is not null + if (-not $xlsxPath) { + throw "XLSX path is null or empty" + } # Verify the download file exists if (-not (Test-Path -LiteralPath $downloadPath)) { @@ -472,52 +533,114 @@ function Convert-Files { } try { - # Open Excel + $script:txtStatus.Text += "Opening Excel...`n" $excel = New-Object -ComObject Excel.Application $excel.Visible = $false $excel.DisplayAlerts = $false try { - Write-Host "Opening workbook: $downloadPath" - $workbook = $excel.Workbooks.Open($downloadPath) + # Create a safe temporary path without spaces + $safeTempDir = Join-Path $env:TEMP "XLSConversion" + if (-not (Test-Path $safeTempDir)) { + New-Item -ItemType Directory -Path $safeTempDir -Force | Out-Null + } - Write-Host "Saving as XLSX: $xlsxPath" + # Create safe filenames without spaces + $safeOriginalName = $originalFileName.Replace(" ", "_") + $safeOriginalPath = Join-Path $safeTempDir $safeOriginalName + + # Resolve the full path + $safeOriginalPath = [System.IO.Path]::GetFullPath($safeOriginalPath) + + # Copy the downloaded file to the safe path + $script:txtStatus.Text += "Copying file to safe path: $safeOriginalPath`n" + Copy-Item -LiteralPath $downloadPath -Destination $safeOriginalPath -Force + + # Verify the copy + if (-not (Test-Path -LiteralPath $safeOriginalPath)) { + throw "Failed to copy file to safe path" + } + + $script:txtStatus.Text += "Opening workbook from: $safeOriginalPath`n" try { - # Save directly to the final path - $workbook.SaveAs([string]$xlsxPath, 51) # 51 = xlWorkbookDefault (xlsx format) - $workbook.Close($true) + $workbook = $excel.Workbooks.Open($safeOriginalPath) + $script:txtStatus.Text += "Workbook opened successfully`n" + + # Create safe XLSX path + $safeXlsxName = [System.IO.Path]::GetFileNameWithoutExtension($safeOriginalName) + ".xlsx" + $safeXlsxPath = Join-Path $safeTempDir $safeXlsxName + $safeXlsxPath = [System.IO.Path]::GetFullPath($safeXlsxPath) + + $script:txtStatus.Text += "Attempting to save as XLSX: $safeXlsxPath`n" + + try { + # Try different SaveAs approaches + try { + # Approach 1: Use FileFormat property + $excel.DefaultSaveFormat = 51 # xlOpenXMLWorkbook + $workbook.SaveAs($safeXlsxPath) + } + catch { + $script:txtStatus.Text += "First save attempt failed, trying alternate method...`n" + # Approach 2: Use explicit FileFormat + $workbook.SaveAs([string]$safeXlsxPath, [int]51) + } + + Start-Sleep -Seconds 2 # Give the file system time to catch up + + if (-not (Test-Path -LiteralPath $safeXlsxPath)) { + throw "File was not created after SaveAs operation" + } + + $script:txtStatus.Text += "Save successful, verifying file...`n" + $fileInfo = Get-Item -LiteralPath $safeXlsxPath + $script:txtStatus.Text += "Saved file size: $($fileInfo.Length) bytes`n" + + # Copy the file back to the original destination with spaces + $script:txtStatus.Text += "Copying to final destination: $xlsxPath`n" + Copy-Item -LiteralPath $safeXlsxPath -Destination $xlsxPath -Force + + $script:txtStatus.Text += "Successfully saved XLSX file`n" + } + catch { + $script:txtStatus.Text += "Error during SaveAs: $($_.Exception.Message)`n" + if ($_.Exception.HResult) { + $script:txtStatus.Text += "Error HResult: $($_.Exception.HResult)`n" + } + throw + } } catch { - $script:txtStatus.Text += "Error during SaveAs: $($_.Exception.Message)`n" - throw "SaveAs failed: $($_.Exception.Message)" # Re-throw the exception to be caught by the outer catch + $script:txtStatus.Text += "Error opening workbook: $($_.Exception.Message)`n" + throw } - - Start-Sleep -Seconds 2 # Give Excel time to finish - - if (-not (Test-Path -LiteralPath $xlsxPath)) { - throw "Failed to create XLSX file at: $xlsxPath" - } - - $script:txtStatus.Text += "Successfully converted to: $xlsxFileName`n" } finally { - # Cleanup - $excel.Quit() if ($workbook) { - [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook) | Out-Null + try { + $workbook.Close($false) + [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook) | Out-Null + } + catch { } + $workbook = $null + } + if ($excel) { + try { + $excel.Quit() + [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null + } + catch { } + $excel = $null } - [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() - # Force cleanup of any remaining Excel processes + # Force cleanup of Excel processes Get-Process -Name "EXCEL" -ErrorAction SilentlyContinue | Where-Object { $_.SI -eq [System.Security.Principal.WindowsIdentity]::GetCurrent().SessionId } | Stop-Process -Force } - $script:txtStatus.Text += "Successfully converted to: $xlsxFileName`n" - # Upload the converted file to SharePoint try { # Verify file exists before trying to read it @@ -538,8 +661,25 @@ function Convert-Files { throw "Temp library not found: $tempLibrary" } - # Construct the upload URI for the temp library - $uploadUri = "https://graph.microsoft.com/v1.0/drives/$($tempDrive.Id)/root:/$($xlsxFileName):/content" + # Use TempPath from the JSON configuration for the upload path + $uploadFileName = $xlsxFileName # Use the converted file name + + # Construct the upload URI for the temp library, properly encoding the path + $uploadPath = $uploadFileName # Just use the filename to save in root + + # URL encode the path but preserve forward slashes and encode spaces as %20 + $encodedPath = $uploadPath.Split('/') | + ForEach-Object { + [System.Web.HttpUtility]::UrlEncode($_).Replace("+", "%20") + } | + Join-String -Separator '/' + + # Fix the URI construction to avoid the colon issue + $baseUri = "https://graph.microsoft.com/v1.0/drives" + $driveId = $tempDrive.Id + $uploadUri = "${baseUri}/${driveId}/root:/${encodedPath}:/content" + + $script:txtStatus.Text += "Uploading to: $uploadUri`n" # Read the file content $fileContent = [System.IO.File]::ReadAllBytes($xlsxPath) @@ -547,22 +687,21 @@ function Convert-Files { # Upload the file Invoke-MgGraphRequest -Uri $uploadUri -Method PUT -Body $fileContent -ContentType "application/octet-stream" - $script:txtStatus.Text += "Successfully uploaded to temp library: $tempLibrary/$xlsxFileName`n" + $script:txtStatus.Text += "Successfully uploaded to temp library: /sites/tax/$tempLibrary/$uploadFileName`n" } catch { $script:txtStatus.Text += "Error uploading file: $($_.Exception.Message)`n" if (-not (Test-Path -LiteralPath $xlsxPath)) { $script:txtStatus.Text += "File not found at: $xlsxPath`n" } - continue # Move to the next file + throw } } catch { - $script:txtStatus.Text += "Error converting file: $($_.Exception.Message)`n" - continue # Move to the next file + $script:txtStatus.Text += "Error in Excel operations: $($_.Exception.Message)`n" + throw } - # Clean up temporary files try { Remove-Item $downloadPath -ErrorAction SilentlyContinue @@ -576,6 +715,242 @@ function Convert-Files { $script:txtStatus.Text += "File conversion complete.`n" } catch { - $script:txtStatus.Text += "Error in Convert-Files function: $($_.Exception.Message)`n" + if ($script:txtStatus) { + $script:txtStatus.Text += "Error in Convert-Files function: $($_.Exception.Message)`n" + } else { + Write-Host "Error in Convert-Files function: $($_.Exception.Message)" + } + } +} + +function Move-Files { + try { + $script:txtStatus.Text += "Moving converted files back to original location...`n" + + # Load the file list from the JSON file + $fileListPath = "$env:TEMP\FileList.json" + if (Test-Path $fileListPath) { + $fileList = Get-Content $fileListPath -Raw | ConvertFrom-Json + } else { + $script:txtStatus.Text += "Error: FileList.json not found. Please list files first.`n" + return + } + + # Check if the file list is empty + if ($fileList.Count -eq 0) { + $script:txtStatus.Text += "No files to move.`n" + return + } + + # Get site and drive information first + $script:txtStatus.Text += "Getting SharePoint site information...`n" + $site = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/sites/sutterhill.sharepoint.com:/sites/tax" -ErrorAction Stop + + if (-not $site) { + throw "Could not find SharePoint site" + } + + $script:txtStatus.Text += "Getting document library information...`n" + $drives = Get-MgSiteDrive -SiteId $site.id + $libraryName = if ($script:txtProdLib.Text -eq "Shared Documents") { + "Documents" + } else { + $script:txtProdLib.Text + } + $drive = $drives | Where-Object { $_.Name -eq $libraryName } + + if (-not $drive) { + $script:txtStatus.Text += "Available libraries: $($drives.Name -join ', ')`n" + throw "Library not found: $libraryName" + } + + # Get the drive for the temp library + $tempLibrary = if ($script:txtTempLib.Text -eq "Shared Documents") { + "Documents" + } else { + $script:txtTempLib.Text + } + $tempDrive = $drives | Where-Object { $_.Name -eq $tempLibrary } + if (-not $tempDrive) { + throw "Temp library not found: $tempLibrary" + } + + # Iterate through the file list and move each file + foreach ($file in $fileList) { + $originalPath = $file.OriginalPath + $originalFileName = $file.OriginalFileName + $xlsxFileName = $originalFileName -replace "\.xls[m]?$", ".xlsx" + $tempPath = $xlsxFileName # Remove the ConvertedFiles folder prefix + + $script:txtStatus.Text += "Moving file: $xlsxFileName`n" + + try { + # Construct the source and destination URIs with proper space encoding + $sourceUri = "https://graph.microsoft.com/v1.0/drives/$($tempDrive.Id)/root:/" + + [System.Web.HttpUtility]::UrlEncode($tempPath).Replace("+", "%20") + + $destinationUri = "https://graph.microsoft.com/v1.0/drives/$($drive.Id)/root:/" + + [System.Web.HttpUtility]::UrlEncode($originalFileName).Replace("+", "%20") + + $script:txtStatus.Text += "Source URI: $sourceUri`n" + $script:txtStatus.Text += "Destination URI: $destinationUri`n" + + # Move the file + $moveResponse = Invoke-MgGraphRequest -Uri $sourceUri -Method PATCH -Body @{ + parentReference = @{ + path = "/drives/$($drive.Id)/root:/$($script:txtFolder.Text)" + } + } -ContentType "application/json" + + $script:txtStatus.Text += "Successfully moved file: $xlsxFileName`n" + } + catch { + $script:txtStatus.Text += "Error moving file: $($_.Exception.Message)`n" + continue + } + } + + $script:txtStatus.Text += "File move complete.`n" + } + catch { + $script:txtStatus.Text += "Error in Move-Files function: $($_.Exception.Message)`n" + $script:txtStatus.Text += "Stack Trace: $($_.Exception.StackTrace)`n" + } +} + +# Function to ensure the temp library exists +function Ensure-TestingLibraryExists { + try { + # Get the library name from TempLibrary field + $tempLibraryName = if ($script:txtTempLib.Text -eq "Shared Documents") { + "Documents" + } else { + $script:txtTempLib.Text + } + + if ($script:txtStatus) { + $script:txtStatus.Text += "Checking if $tempLibraryName library exists...`n" + } else { + Write-Host "Checking if $tempLibraryName library exists..." + } + + # Get site and drive information + $site = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/sites/sutterhill.sharepoint.com:/sites/tax" -ErrorAction Stop + + if (-not $site) { + throw "Could not find SharePoint site" + } + + $drives = Get-MgSiteDrive -SiteId $site.id + $tempLibrary = $drives | Where-Object { $_.Name -eq $tempLibraryName } + + if (-not $tempLibrary) { + if ($script:txtStatus) { + $script:txtStatus.Text += "$tempLibraryName library not found. Creating library...`n" + } else { + Write-Host "$tempLibraryName library not found. Creating library..." + } + + # Create the library + $createLibraryUri = "https://graph.microsoft.com/v1.0/sites/$($site.id)/lists" + $body = @{ + displayName = $tempLibraryName + list = @{ + template = "documentLibrary" + } + } | ConvertTo-Json + + Invoke-MgGraphRequest -Uri $createLibraryUri -Method POST -Body $body -ContentType "application/json" + + if ($script:txtStatus) { + $script:txtStatus.Text += "$tempLibraryName library created successfully.`n" + } else { + Write-Host "$tempLibraryName library created successfully." + } + } else { + if ($script:txtStatus) { + $script:txtStatus.Text += "$tempLibraryName library already exists.`n" + } else { + Write-Host "$tempLibraryName library already exists." + } + } + + return $true + } + catch { + if ($script:txtStatus) { + $script:txtStatus.Text += "Error ensuring library exists: $($_.Exception.Message)`n" + } else { + Write-Host "Error ensuring library exists: $($_.Exception.Message)" + } + return $false + } +} + +function Upload-ConvertedFile { + param ( + [Parameter(Mandatory=$true)] + [string]$xlsxPath, + [Parameter(Mandatory=$true)] + [string]$uploadFolder, + [Parameter(Mandatory=$true)] + [string]$uploadFileName + ) + + try { + # Verify file exists before trying to read it + if (-not (Test-Path -LiteralPath $xlsxPath)) { + throw "Converted file not found at: $xlsxPath" + } + + # Get the destination folder from the TempLibrary field + $tempLibrary = if ($script:txtTempLib.Text -eq "Shared Documents") { + "Documents" + } else { + $script:txtTempLib.Text + } + + # Get the drive for the temp library + $tempDrive = $drives | Where-Object { $_.Name -eq $tempLibrary } + if (-not $tempDrive) { + throw "Temp library not found: $tempLibrary" + } + + # Construct the upload URI for the temp library, properly encoding the path + $uploadPath = $uploadFileName # Just use the filename to save in root + + # URL encode the path but preserve forward slashes and encode spaces as %20 + $encodedPath = $uploadPath.Split('/') | + ForEach-Object { + [System.Web.HttpUtility]::UrlEncode($_).Replace("+", "%20") + } | + Join-String -Separator '/' + + # Fix the URI construction to avoid the colon issue + $baseUri = "https://graph.microsoft.com/v1.0/drives" + $driveId = $tempDrive.Id + $uploadUri = "${baseUri}/${driveId}/root:/${encodedPath}:/content" + + if ($script:txtStatus) { + $script:txtStatus.Text += "Uploading to: $uploadUri`n" + } + + # Read the file content + $fileContent = [System.IO.File]::ReadAllBytes($xlsxPath) + + # Upload the file + Invoke-MgGraphRequest -Uri $uploadUri -Method PUT -Body $fileContent -ContentType "application/octet-stream" + + if ($script:txtStatus) { + $script:txtStatus.Text += "Successfully uploaded to temp library: /sites/tax/$tempLibrary/$uploadFolder/$uploadFileName`n" + } + } + catch { + if ($script:txtStatus) { + $script:txtStatus.Text += "Error uploading file: $($_.Exception.Message)`n" + } else { + Write-Host "Error uploading file: $($_.Exception.Message)" + } + throw } } diff --git a/config.json b/config.json index 9b2ca26..a4b8dee 100644 --- a/config.json +++ b/config.json @@ -1,5 +1 @@ -{ - "SiteUrl": "https://sutterhill.sharepoint.com/sites/tax/", - "ProductionLibrary": "Shared Documents", - "TempLibrary": "Temp" -} +{"SiteUrl":"https://sutterhill.sharepoint.com/sites/tax/","TempLibrary":"Testing","ProductionLibrary":"Shared Documents"}