Why am I getting 'The remote procedure call failed' error after stopping excel process? - excel

I have the following code that converts an excel sheets to csv files. If the csv files do not exist/or exist already but not in use (e.g. opened in excel), the script generates the csv files successfully (overwriting them if they exist already)!
However, if the csv file is opened in excel, then i get an error "Can't access csv file" which i have determined is because its in use by excel (when opened). I know this is 100% the reason because if i have the existing csv file opened in notepad, the script still overwrites the csv file, running successfully.
so i tried implementing an automatic resolution, which is Get-Process 'exce[l]' | Stop-Process -Force , and although it does stop the process (closes excel), I get yet another error:
Convert-ExcelSheetsToCsv : Failed to save csv! Path: 'C:\Users\Documents\Folder1\CSV_Files\COS.csv'. The remote
procedure call failed. (Exception from HRESULT: 0x800706BE)
Convert-ExcelSheetsToCsv : Failed to save csv! Path: 'C:\Users\Documents\Folder1\CSV_Files\.csv'. The RPC server is
unavailable. (Exception from HRESULT: 0x800706BA)
After some research, I disabled my COM-Excel Addins, ran the script again, and the exceptions still occurred again...
why is that?
$currentDir = $PSScriptRoot
$csvPATH = Join-Path -Path $currentDir -ChildPath CSV_Files
New-Item -ItemType Directory -Force -Path $csvPATH | out-null
function Convert-ExcelSheetsToCsv {
param(
[Parameter(Mandatory, ValueFromPipelineByPropertyName, Position=1)]
[ValidateNotNullOrEmpty()]
[Alias('FullName')]
[string]$Path,
[Parameter(Mandatory = $false, Position=0)]
[bool]$AppendFileName,
[Parameter(Mandatory = $false, Position=2)]
[bool]$ExcludeHiddenSheets,
[Parameter(Mandatory = $false, Position=3)]
[bool]$ExcludeHiddenColumns
)
Begin {
$excel = New-Object -ComObject Excel.Application -Property #{
Visible = $false
DisplayAlerts = $false
}
}
Process {
#$root = Split-Path -Path $Path
$filename = [System.IO.Path]::GetFileNameWithoutExtension($Path)
$workbook = $excel.Workbooks.Open($Path)
foreach ($worksheet in ($workbook.Worksheets | Where { <# $_.Visible -eq -1 #> $_.Name -ne 'Security' -and $_.Name -ne 'Notes' })) {
if($ExcludeHiddenColumns) {
$ColumnsCount = $worksheet.UsedRange.Columns.Count
for ($i=1; $i -le $ColumnsCount; $i++)
{
$column = $worksheet.Columns.Item($i).EntireColumn #$worksheet.sheets.columns.entirecolumn.hidden=$true
if ($column.hidden -eq $true)
{
$columnname = $column.cells.item(1,$i).value2
if ($worksheet.Visible -eq 0) #worksheet hidden
{
"`r`nHidden column [{0}] found in hidden [{1}] worksheet. Deleting..." -f $columnname, $($worksheet.name)
}
else {
"`r`nHidden column [{0}] found in [{1}] worksheet. Deleting..." -f $columnname, $($worksheet.name)
}
try {
$column.Delete() | out-null
"`r`nHidden column [{0}] was Deleted! Proceeding with Export to CSV operation...`r`n" -f $columnname
}
catch {
Write-Error -Message "`r`nFailed to Delete hidden column [$columnname] from [$($worksheet.name)] worksheet! $PSItem"
#$_ | Select *
}
#$i = $i - 1
}
}
}
if ($ExcludeHiddenSheets) {
if ($worksheet.Visible -eq -1) #worksheet visible
{
$ws = $worksheet
}
}
else {
$ws = $worksheet
}
if ($AppendFileName) {
$name = Join-Path -Path $csvPATH <# $root #> -ChildPath "${filename}_$($ws.Name).csv"
}
else {
$name = Join-Path -Path $csvPATH <# $root #> -ChildPath "$($ws.Name).csv"
}
try {
$ws.SaveAs($name, 6) #6 to ignore formatting and convert to pure text, otherwise, file could end up containing rubbish
}
catch {
if ($error[0].ToString().Contains("Cannot access"))
{
"`r`n'{0}' is currently in use.`r`n Attempting to override usage by trying to stop Excel process..." -f $name
try {
#Only 'excel' will be matched, but because a wildcard [] is used, not finding a match will not generate an error.
#https://stackoverflow.com/a/32475836/8397835
Get-Process 'exce[l]' | Stop-Process -Force
"`r`nExcel process stopped! Saving '{0}' ..." -f $name
$ws.SaveAs($name, 6)
}
catch {
Write-Error -Message "Failed to save csv! Path: '$name'. $PSItem"
}
}
else {
Write-Error -Message "Failed to save csv! Path: '$name'. $PSItem"
}
}
}
}
End {
$excel.Quit()
$null = [System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel)
}
}
Get-ChildItem -Path $currentDir -Filter *.xlsx | Convert-ExcelSheetsToCsv -AppendFileName 0 -ExcludeHiddenSheets 1 -ExcludeHiddenColumns 1 #0 for false, so that filename of excel file isnt appended, and only sheet names are the names of the csv files

That is because the excel object ends up getting destroyed as well. the correct way to do this is to end the process PRIOR to instantiating the excel object:
Begin {
Get-Process 'exce[l]' | Stop-Process -Force

Related

How to create an elseif condition in a script with Runspaces

In the original script, I was attempting to search for a string in a text file in a running log. It worked fine however, since there is a -wait parameter in the loop, it wasn't easy to find a solution that would allow for the same script to search over multiple text files. Since then the following script was introduced to me that incorporates Runspaces:
using namespace System.Management.Automation.Runspaces
using namespace System.Threading
# get the log files here
$LogGroup = ('C:\log 0.txt', 'C:\Log 1.txt', 'C:\Log 2.txt')
# this help us write to the main log file in a thread safe manner
$lock = [SemaphoreSlim]::new(1, 1)
# define the logic used for each thread, this is very similar to the
# initial script except for the use of the SemaphoreSlim
$action = {
param($path)
$PSDefaultParameterValues = #{ "Get-Date:format" = "yyyy-MM-dd HH:mm:ss" }
Get-Content $path -Tail 1 -Wait | ForEach-Object {
if($_ -match 'down') {
# can I write to this file?
$lock.Wait()
try {
Write-Host "Down: $_ - $path" -ForegroundColor Green
Add-Content "path\to\mainLog.txt" -Value "$(Get-Date) Down: $_ - $path"
}
finally {
# release the lock so other threads can write to the file
$null = $lock.Release()
}
}
}
}
try {
$iss = [initialsessionstate]::CreateDefault2()
$iss.Variables.Add([SessionStateVariableEntry]::new('lock', $lock, $null))
$rspool = [runspacefactory]::CreateRunspacePool(1, $LogGroup.Count, $iss, $Host)
$rspool.ApartmentState = [ApartmentState]::STA
$rspool.ThreadOptions = [PSThreadOptions]::UseNewThread
$rspool.Open()
$res = foreach($path in $LogGroup) {
$ps = [powershell]::Create($iss).AddScript($action).AddArgument($path)
$ps.RunspacePool = $rspool
#{
Instance = $ps
AsyncResult = $ps.BeginInvoke()
}
}
# block the main thread
do {
$id = [WaitHandle]::WaitAny($res.AsyncResult.AsyncWaitHandle, 200)
}
while($id -eq [WaitHandle]::WaitTimeout)
}
finally {
# clean all the runspaces
$res.Instance.ForEach('Dispose')
$rspool.ForEach('Dispose')
}
The Runspaces allow for additional threads allowing for multitasking but I am not very skilled and I need help adding an elseif clause after an if statement. But my attempts were rewarded with the following error:
cmdlet ForEach-Object at command pipeline position 2
Supply values for the following parameters:
Process[0]:
Here’s the best I could come up with so far:
$action = {
param($path)
$PSDefaultParameterValues = #{ "Get-Date:format" = "yyyy-MM-dd HH:mm:ss" }
$lock.Wait()
# can I write to this file?
try {
Get-Content $path -Tail 1 -Wait | ForEach-Object
if($_ -match 'down) {
Write-Host "Down: $_ - $path" -ForegroundColor Red
Add-Content "C:\ log_down.txt" -Value "$(Get-Date) Down: $_ - $path"
}
elseif($_ -match 'up') {
Write-Host "Down: $_ - $path" -ForegroundColor Green
Add-Content "C:\ log_up.txt" -Value "$(Get-Date) up: $_ - $path"
}
$lock.Wait()
}
finally {
# release the lock so other threads can write to the file
$null = $lock.Release()
}
}
Thanks in advance for any help!
Here is the only change you need to do, below code only addresses the $action Script Block. Rest of the code should remain the same.
Make sure you're using the Full Paths of the logs.
$action = {
param($path)
$PSDefaultParameterValues = #{ "Get-Date:format" = "yyyy-MM-dd HH:mm:ss" }
Get-Content $path -Tail 1 -Wait | ForEach-Object {
# wait to enter the SemaphoreSlim
$lock.Wait()
try {
if($_ -match 'down') {
Write-Host "Down: $_ - $path" -ForegroundColor Red
Add-Content "C:\log_down.txt" -Value "$(Get-Date) Down: $_ - $path"
}
elseif($_ -match 'up') {
Write-Host "Up: $_ - $path" -ForegroundColor Green
Add-Content "C:\log_up.txt" -Value "$(Get-Date) up: $_ - $path"
}
# more conditions can go here
}
finally {
# release the lock so other threads can write to the file
$null = $lock.Release()
}
}
}
After closer look at your attempt, it seems almost right:
Missing an opening { after ForEach-Object.
Missing a closing } before the finally block.
.Wait() should be inside the loop instead of outside.

Trying to write a code to remove properties in Powerpoint file

Hello,
I am trying to create a PowerShell script where it takes the properties of a PowerPoint file and removes them so they no issues are being caused by it but with the code I am making I am trying to copy some code that does the same thing excel and word and just change something over to PowerPoint and it doesn't seem to want to work
Code that I've tried so far (forgive me I'm not the most experienced with PowerShell)
$path = “c:\fso”
Add-Type -AssemblyName Microsoft.Office.Interop.PowerPoint
$PpRemoveDocType = “Microsoft.Office.Interop.Powerpoint.PpRemoveDocInfoType” -as [type]
$pointFiles = Get-ChildItem -Path $path -include *.pot, *.ppt, *.pps -recurse
$objPoint.visible = $false
$objPoint = New-Object -ComObject powerpoint.application
foreach($wb in $pointFiles)
{
$workbook = $objPoint.workbooks.open($wb.fullname)
“Removing document information from $wb”
$workbook.RemoveDocumentInformation($PpRemoveDocType::xlRDIAll)
$workbook.Save()
$objPoint.Workbooks.close()
}
$objPoint.Quit()
This is the Excel code for reference and it works just fine
$path = “c:\fso”
Add-Type -AssemblyName Microsoft.Office.Interop.Excel
$xlRemoveDocType = “Microsoft.Office.Interop.Excel.XlRemoveDocInfoType” -as [type]
$excelFiles = Get-ChildItem -Path $path -include *.xls, *.xlsx -recurse
$objExcel = New-Object -ComObject excel.application
$objExcel.visible = $false
foreach($wb in $excelFiles)
{
$workbook = $objExcel.workbooks.open($wb.fullname)
“Removing document information from $wb”
$workbook.RemoveDocumentInformation($xlRemoveDocType::xlRDIAll)
$workbook.Save()
$objExcel.Workbooks.close()
}
$objExcel.Quit()
Thank you for the help.
This has worked for me in the past:
function handlePowerpointFiles ($file) {
Write-Host "Processing file: " $file.Fullname
Add-Type -AssemblyName Microsoft.Office.Interop.Powerpoint
$PpRemoveDocType = "Microsoft.Office.Interop.PowerPoint.PpRemoveDocInfoType" -as [type]
$objpp = New-Object -ComObject Powerpoint.Application
$doc = $objpp.Presentations.Open($file.FullName, $false, $null, $false)
$doc.RemoveDocumentInformation($PpRemoveDocType::ppRDIAll)
$doc.Save()
$doc.Close()
$objpp.Quit()
}
The reason I wrapped it into a function, is because I wrote a small tool to handle all major Microsoft Office files, also perform some pre-checks if the file is locked or password protected. Essentially something along the lines of (simplified):
$path = "C:\Documents"
$files = Get-ChildItem -Path $path -include *.doc, *.docx, *.xls, *.xlsx, *.ppt, *.pptx -Recurse
foreach ($fileEntry in $files) {
if (CheckForFileLock $fileEntry.FullName) {
Write-Error "File '$($fileEntry.FullName)' is locked" -ErrorAction Stop
}
if (CheckForPasswordProtection($fileEntry) -eq $true){
Write-Error "File '$($fileEntry.FullName)' is password protected" -ErrorAction Stop
}
Switch ($fileEntry.Extension){
{$_ -in ".xls",".xlsx"} {
handleExcelFiles $fileEntry
}
{$_ -in ".doc",".docx"} {
handleWordFiles $fileEntry
}
{$_ -in ".ppt",".pptx"} {
handlePowerpointFiles $fileEntry
}
}
}
You get the idea.

PS Azure Web App Exception calling “UploadFile” with “2” argument(s): “The Content-Type header cannot be set to a multipart type for this request

Hi I am trying to upload published files from Azure Git artifacts to FTP. But randomly I am getting the below error.
This is only happening for files not for any folder or subfolders.
“UploadFile” with “2” argument(s): “The Content-Type header cannot be set to a multipart type for this request.
All the files are available in the artifacts.
Observation & tried:
All files are present in artifacts
Getting the error only for files. (Folder & subfolders are creating successfully)
Stopped the Web App and then tried to upload
Tried Sleep also between uploading two files
After all of these, the result is the same.
Can anyone please help me out?
Get-ChildItem -Path $target_directory
# Upload files recursively
write-host "target_directory - $target_directory"
Set-Location $target_directory
$webclient = New-Object -TypeName System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($username,$password)
$files = Get-ChildItem -Path $target_directory -Recurse
write-host 'Uploading started............................'
foreach ($file in $files)
{
write-host "file -" + $file.FullName
if($file.FullName -match "web.config" -or $file.FullName -match ".pdb" -or $file.FullName -match "roslyn" -or $file.FullName -match "obj\Debug"){
write-host "ignoring " + $file.FullName
continue
}
$relativepath = (Resolve-Path -Path $file.FullName -Relative).Replace(".\", "").Replace('\', '/')
$uri = New-Object System.Uri("$url/$relativepath")
write-host "uri -" + $uri.AbsoluteUri
write-host '--------'
if($file.PSIsContainer)
{
write-host 'PSIsContainer - All dir/files within' $file
get-childitem -path $file -Recurse
try
{
$makeDirectory = [System.Net.WebRequest]::Create($uri);
$makeDirectory.Credentials = New-Object System.Net.NetworkCredential($username,$password);
$makeDirectory.Method = [System.Net.WebRequestMethods+FTP]::MakeDirectory;
$makeDirectory.GetResponse();
#folder created successfully
}
catch [Net.WebException]
{
try
{
#if there was an error returned, check if folder already existed on server
$checkDirectory = [System.Net.WebRequest]::Create($uri);
$checkDirectory.Credentials = New-Object System.Net.NetworkCredential($username,$password);
$checkDirectory.Method = [System.Net.WebRequestMethods+FTP]::PrintWorkingDirectory;
$response = $checkDirectory.GetResponse();
$response.StatusDescription
#folder already exists!
}
catch [Net.WebException]
{
#if the folder didn't exist, then it's probably a file perms issue, incorrect credentials, dodgy server name etc
}
}
continue
}
try{
write-host "Uploading to " $uri.AbsoluteUri " from " $file.FullName
$webclient.UploadFile($uri, $file.FullName)
start-sleep -Seconds 1
}
catch{
##[error]Error message
Write-Host "##[error]Error in uploading " $file -ForegroundColor red
Write-Host $_
$disputedFiles = $disputedFiles + $file.FullName
$_file = #{
FileName = $file.FullName
FTPPath = $uri.AbsoluteUri
}
$_o = New-Object psobject -Property $_file;
$disputedFilesList = $disputedFilesList + $_o
}
}
Write-Host "##[debug] Starting uploading the disputed files....."
foreach($file in $disputedFilesList){
try{
write-host "Uploading to " $file.FTPPath " from " $file.FileName
$webclient.UploadFile($file.FTPPath, $file.FileName)
start-sleep -Seconds 1
}
catch{
write-host "##[error]Error(2) in uploading to " $file.FTPPath " from " $file.FileName
}
}
Write-Host "##[debug] Ending uploading the disputed files....."
remove-item -path $target_directory\* -Recurse
get-childitem -path $target_directory
write-host "Directory Empty after cleanup"
$webclient.Dispose()

Error: Cannot find an overload for "restore" and the argument count: "1"

I am getting this error from the following code. It's coming from $Context.Load($RecycleBinItems). Any idea what's wrong with the code? I am attempting to restore all recyclebin items.
Add-Type -Path "C:\Program Files\WindowsPowerShell\Modules\SharePointPnPPowerShellOnline\3.17.2001.2\Microsoft.SharePoint.Client.dll"
Add-Type -Path "C:\Program Files\WindowsPowerShell\Modules\SharePointPnPPowerShellOnline\3.17.2001.2\Microsoft.SharePoint.Client.Runtime.dll"
Import-Module 'Microsoft.PowerShell.Security'
#Get the Site Owners Credentials to connect the SharePoint
$SiteUrl = "https://phaselinknet.sharepoint.com"
$UserName = Read-host "Enter the Email ID"
$Password = Read-host - assecurestring "Enter Password for $AdminUserName"
$Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserName, $Password)
# Once Connected, get the Site information using current Context objects
Try {
$Context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl)
$Context.Credentials = $Credentials
$Site = $Context.Site
$RecycleBinItems = $Site.RecycleBin
$Context.Load($Site)
$Context.Load($RecycleBinItems)
$Context.ExecuteQuery()
Write-Host "Total Number of Files found in Recycle Bin:" $RecycleBinItems.Count
}
catch {
write - host "Error: $($_.Exception.Message)" - foregroundcolor Red
}
# using for loop to restore the item one by one
Try {
if($RecycleBinItems)
{
foreach($Item in $RecycleBinItems)
{
$Site.RecycleBin.restore($Item.ID)
#Write-Host "Item restored:"$Item.Title
}
}
}
catch {
write-host "Error: $($_.Exception.Message)" -foregroundcolor Red
}
The error message is giving you you answer. There is not a version of the method Restore that takes 1 parameter.
You need to load up a list of items simular to this
$Item = $RecycleBin | Where{$_.Title -eq $ItemName}
Then call restore for the items.
if($Item -ne $null)
{
$Item.Restore()
}
Thanks for the tip. So I load up the first 10 items in the recyclebin, and Write-Host does write out the correct files, but the $Item.Restore() does noting as the files are still not restored:
$itemsToRestore = #()
for ($i = 0; $i -lt 10; $i++)
{
$Item = $RecycleBinItems[$i]
$itemsToRestore += $Item
}
Write-Host "Total Number of Files to Restore:" $itemsToRestore.Count
foreach($item in $itemsToRestore)
{
Write-Host "Item:" $Item.Title
$item.Restore()
}
I found the problem. I missed $Context.ExecuteQuery() after $Item.Restore(). It works now.

How do I programmatically remove passwords from excel workbooks?

A few weeks ago, I had to remove password protection from excel files which were created by an application. I had no passwords. Can this task be done with powershell, using xml transformation?
This is my solution I want to share with you. The powershell script removes passwords and sheet protections from Excel files using powershell. No Excel application and no passwords are needed. The script is working only for .xlsx file types, not for .xls. If you have ideas for improvement, let me know. Thank you.
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem
#-----------------------------------------------------------------------
function Remove-Excel-WriteProtection {
<#
// Removes all password and write protection from existing excel file
// (workbook and worksheets).
// No password needed.
//
// Input: Path to Excel file (must newer xlsx format)
// Output: true if successfull
#>
#-----------------------------------------------------------------------
param(
[Parameter(Mandatory=$true)]
[string]$filePathExcel
)
if( !(Test-Path -Path $filePathExcel) -or
!(Split-Path -Path $filePathExcel -Leaf).EndsWith('xlsx') ) {
return $false
}
$fileItem = Get-Item $filePathExcel
$filePathZip = $fileItem.DirectoryName + '\' + $fileItem.BaseName + '.zip'
$filePathTemp = $fileItem.DirectoryName + '\' + ([System.Guid]::NewGuid()).Guid
Rename-Item -Path $filePathExcel -NewName $filePathZip -Force
Expand-Archive -Path $filePathZip -DestinationPath $filePathTemp -Force
$xml = New-Object System.Xml.XmlDocument
$xml.PreserveWhitespace = $true
$workbookCollection = (Get-ChildItem -Path $filePathTemp -Filter 'workbook.xml' -Recurse -Force)
foreach( $workbook in $workbookCollection ) {
[void]$xml.RemoveAll()
[void]$xml.Load($workbook.FullName)
if( $xml.workbook.fileSharing.readOnlyRecommended -or $xml.workbook.fileSharing.reservationPassword ) {
if( $xml.workbook.fileSharing.readOnlyRecommended ) {
$xml.workbook.fileSharing.readOnlyRecommended = '0'
}
if( $xml.workbook.fileSharing.reservationPassword ) {
$xml.workbook.fileSharing.reservationPassword = ''
}
[void]$xml.Save($workbook.FullName)
}
}
$worksheetCollection = (Get-ChildItem -Path $filePathTemp -Filter 'sheet*.xml' -Recurse -Force)
foreach( $worksheet in $worksheetCollection ) {
[void]$xml.RemoveAll()
[void]$xml.Load($worksheet.FullName)
if( $xml.worksheet.sheetProtection ) {
[void]$xml.worksheet.RemoveChild($xml.worksheet.sheetProtection)
[void]$xml.Save($worksheet.FullName)
}
}
Remove-Item -Path $filePathZip -Force
Compress-Archive -Path ($filePathTemp + '\*') -DestinationPath $filePathZip -Force -CompressionLevel Optimal
Remove-Item -Path $filePathTemp -Recurse -Force
Rename-Item -Path $filePathZip -NewName $filePathExcel -Force
return $true
}
# Remove all passwords for test.xlsx
$result = Remove-Excel-WriteProtection -filePathExcel 'C:\Users\YourName\Desktop\test.xlsx'

Resources