excel creation with powershell - excel

I try to create a excel file in a powershell script using this code :
$xl = New-Object -ComObject "Excel.Application"
$xl.Visible = $false
$xl.DisplayAlerts = $false
$wb = $xl.Workbooks.Open($scriptPath + "\..\..\NeededTools\ExploitationApplication.xlsm")
$global:ws = $wb.sheets.Item(3)
And I face this exception :
New-Object : Retrieving the COM class factory for component with CLSID
{00000000-0000-0000-0000-000000000000} failed due to the following
error: 80040154 Class not registered (Exception from HRESULT:
0x80040154 (REGDB_E_CLASSNOTREG)).
Do someone have informations about this exception or a sample used to generate a excel file correctly ?
Is it possible to have this error because I dont have excel on this server ?

You do not have Excel installed. Or you have, and need to run repair.

I had the same problem in my Desktop from a C# application after a Windows/Office update from my company IT (Office16). It worked for 5 years without any problems.
I create a Powershell script that launch excel to update a table that works well in my Laptop but still not in my Desktop.
After long search and follow the indication from this link (https://social.technet.microsoft.com/Forums/lync/en-US/05a1635d-142c-4866-8455-1341280967fd/windows-explorer-preview-of-office-2016-files-does-not-work?forum=Office2016setupdeploy).
I found that some information was missing in the Windows registry.
In details: comparing the Registry key "Computer\HKEY_CLASSES_ROOT\WOW6432Node\CLSID{00024500-0000-0000-C000-000000000046}" from my Laptop, in my Desktop I had only one key, "InprocServer32" instead of 5 keys.
I added the following key folders and values under "Computer\HKEY_CLASSES_ROOT\WOW6432Node\CLSID{00024500-0000-0000-C000-000000000046}":
"LocalServer32" => default: "C:\Program Files (x86)\Microsoft Office\Root\Office16\EXCEL.EXE /automation"
"ProgID" => default: "Excel.Application.16"
"VersionIndependentProgID" => default: "Excel.Application"
"InprocHandler32" => default: "ole32.dll"
Note: the "LocalServer32" was enough to run the script
Registry key structure

You do not have Excel installed. Or you have, and have to repair it.

Related

Wait to refresh external data connections in each sheet in Excel workbook before saving/closing using PowerShell

I've looked around quite a bit for solutions to this and don't see any PowerShell only solutions. I've seen a few PowerShell/VBA solutions but nothing saying it is not possible to complete solely using PowerShell. I would prefer not to use VBA if possible, PowerShell only.
I have a few workbooks with multiple sheets that are currently manually refreshed to retrieve data from a SQL Server (2008 R2) database instance. I can do everything I need using the following code if I run line by line and wait for refresh operations to complete:
$Excel = New-Object -ComObject Excel.Application
$Workbook = $Excel.Workbooks.Open('C:\test.xlsx')
$Excel.Visible = $True
$Workbook.RefreshAll()
$workbook.Save()
$Workbook.Close()
$Excel.Quit()
The only problem is when I run the whole script, as expected, the Save() method executes while the refresh operation is still running resulting in this prompt thus interrupting the Save(), Close(), and Quit() methods:
I could of course use the Start-Sleep cmdlet in a loop to wait for the database connections to complete using a static interval, however, the stored procedures that are executed range from 2 seconds - 3 minutes and seems like a waste of resources to sleep on each refresh like that.
The Stack Overflow answer I linked above lists 3 possible solutions to this but I don't see those properties that are listed available in the PowerShell objects (QueryTable.Refreshing for example doesn't exist using PowerShell). It appears that they're available in VBA, although the code examples are written using PowerShell. Are the examples wrong or am I missing something here?
My question: Is it possible to complete the code above by adding a dynamic 'wait' operation after RefreshAll() and before Save() using some sort of "Excel is refreshing/busy" property using PowerShell only?
I was able to figure this out with the following code additions:
$conn = $Workbook.Connections
and
while($conn | ForEach-Object {if($_.OLEDBConnection.Refreshing){$true}}){
Start-Sleep -Seconds 1
}
The Refreshing property lives in the OLEDBConnection object.
The final code:
$Excel = New-Object -ComObject Excel.Application
$Workbook = $Excel.Workbooks.Open('C:\test.xlsx')
$Excel.Visible = $True
$conn = $Workbook.Connections
$Workbook.RefreshAll()
while($conn | ForEach-Object {if($_.OLEDBConnection.Refreshing){$true}}){
Start-Sleep -Seconds 1
}
$workbook.Save()
$Workbook.Close()
$Excel.Quit()

Powershell excel refresh fails with "Call Was Rejected by Callee" when .visible=$false

I've had this issue for a long time now and had just ignored it out of laziness, however I now need to find a solution. I have a script which automates refreshing a large number of excel documents. This works well and dandy, however, it fails if I have the Visible property set to false on workbooks which are stored on a network share.
To reiterate, refreshing with the visible property set to false works fine on LOCAL files, but any workbook saved on a \ location fails with an error "Call was rejected by callee". All refreshes work fine with the visible property set to true.
Here is my code :
#Create Excel COM object and set it up for use.
$excel = new-object -comobject Excel.Application;
$excel.DisplayAlerts = $false;
#If this is set to false, saving the file on a network share will fail. Reason : Unknown.
$excel.Visible = $true;
#Open workbook which should be refreshed.
$excelworkbook = $excel.workbooks.Open($workbook);
#Refresh WB
$excelworkbook.RefreshAll();
#Save
$excelworkbook.Save();
#Quit Excel
$excel.Quit();
#Destroy COM object. (VERY IMPORTANT!!!!!)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel);
I have tried the following :
Adding Start-Sleep 30 between creating the excel object and setting the visible property
Setting visible before DisplayAlerts
Wishing really hard for it to just work
Any ideas?
It seems that RefreshAll() doesn't wait for the refresh to actually succeed in the background with Visible = $False set.
Introduce an artificial delay between RefreshAll() and Save(), like so:
$excelworkbook.RefreshAll();
Start-Sleep -Seconds 30
$excelworkbook.Save();
Alternatively, you might be able to force the RefreshAll() to execute synchronously by setting BackgroundQuery = $False on all query tables in your workbook, as suggested in this answer to a similar question:
foreach ($Sheet in $excelworkbook.Worksheets) {
foreach ($QTable in $Sheet.QueryTables) {
$QTable.BackgroundQuery = $false
}
}
I would add a DoEvents block:
[System.Windows.Forms.Application]::DoEvents()
This will allow the queue to cycle through anything you've told Excel to do & then back to the script execution. Another thing would be to set UserControl = false so that Excel is not simply hidden, but is clearly out of the user's ability to respond to events.
Lastly, there may be something about setting Visible after you've set other properties - it may be the case that Excel responds to the Visible event by toggling a few other things (don't remember off-hand, but something in the back of my brain says this is the case, or used to be).

Cannot open Workflows from Item after deleting workflows via Powershell

I did the following:
$oldWorkflow="MyOldWorkflow";
$oldAssoc=$list.WorkflowAssociations.GetAssociationByName($oldWorkflow,"en-US");
$list.RemoveWorkflowAssociation($oldAssoc);
After that I can see that the workflow does not exist anymore. But on each Item that had the workflow before I receive an error when trying to display the workflows:
Getting Error Message for Exception System.Web.HttpUnhandledException (0x80004005): Exception of type 'System.Web.HttpUnhandledException' was thrown. ---> System.ArgumentException: Column 'Reservat' doesn't exist
I never had a column with that name. After adding that column manually to the list, the error messages changed to:
Application error when access /_layouts/15/Workflow.aspx, Error=Object reference not set to an instance of an object.
Any chance to solve this mess? (The old workflow does NOT exist anymore as WSP)
UPDATE:
I can still find the workflows using Powershell:
foreach ($wf in $item.Workflows)
if ($wf.ParentAssociation.Name -eq $oldWorkflow)
...
But in the same time $list.WorkflowAssociations.GetAssociationByName does NOT return the workflow
I solved this. Note for all those who came here by google: This is not a good practice for removing workflows, but only a solution if you already messed up your workflows in SharePoint. (like me)
$oldWorkflow="MyOldWorkflow";
foreach ($item in $list.Items) {
foreach ($wf in $item.Workflows) {
if ($wf.ParentAssociation.Name -eq $oldWorkflow) {
Write-Host ("[{0}] Workflow '{1}' will be removed" -f $item.Title, $wf.ParentAssociation.Name);
$itemsToAdd.Add($wf);
}
}
}
ForEach($item in $itemsToAdd) {
$man.RemoveWorkflowFromListItem($item);
}

Sharepoint Online 2013 CSOM constructor error for AttachmentCollection object?

I'm writing a script to copy items from one list to another on a sharepoint online server. I'm using the 2013 sharepoint Client Side Object Model (CSOM) to script this in powershell ISE. This should be an easy task, but it's proving just the opposite. So far I can retreive all the items using camlquery and I'm just trying to duplicate those items and their attachments to another list. The error I receive is from trying to establish an attachmentCollection to retrieve all of the attachments from any item, here is a portion of the script that represents the problem:
Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"
Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
$siteURL = "https://mysite.sharepoint.com"
$password = Read-Host -Prompt "Enter Password" -AsSecureString
$ctx = New-Object Microsoft.Sharepoint.Client.ClientContext($siteURL)
$credentials = New-Object Microsoft.Sharepoint.Client.SharepointOnlineCredentials("admin#mysite.sharepoint.com", $password)
$ctx.Credentials = $credentials
#...Bunch of code that establishes/loads web/lists/items, all works fine
function CopyItem $itemToCopy
function CopyItem ($oldItem)
{
Write-Host "Copying item" $oldItem.ID
$newItemCI = New-Object Microsoft.SharePoint.Client.ListItemCreationInformation
$newItem = $archList.AddItem($newItemCI)
$ctx.load($newItem)
#Update fields
$ctx.load($sourceList.Fields)
$ctx.ExecuteQuery()
foreach($field in $sourceList.Fields)
{
$newItem[$field.InternalName] = $oldItem[$field.InternalName]
}
$attachments = New-Object Microsoft.SharePoint.Client.AttachmentCollection #ERROR HERE
$attachments = $oldItem.AttachmentFiles
$ctx.load($attachments)
$newItem.AttachmentFiles.Add($attachments)
$newItem.Update()
$ctx.load($newItem)
$ctx.ExecuteQuery()
}
The error message says: "The List Archive Failed at: with this error message: Constructor not found. Cannot find an appropriate constructor for type Microsoft.SharePoint.Client.AttachmentCollection."
I get the same error if I try to create new-object as Attachment as well, can't find constructor. This is odd, as the constructor should be in the client.dll, but no luck. I've even tried repairing my 2013 CSOM files, no errors were found there. Any help on this is appreciated, thank you.
After a hellish amount of trial and error, I discovered that you did not need to declare a new-object when dealing with the attachmentCollection objects. You can simply set a variable up like so:
$attachments = $item.AttachmentFiles
$attachments is now an array of attachment objects.
However, there is still a huge issue of copying/adding attachments to new items, since sharepoint has a horrible system for managing these attachments and does not initially have a folder to store them, nor can you create a folder directly. I'm still having trouble copying attachments between items, if anyone has knowledge of how to accomplish this, I would love help on that as well.
The main problem in adding attachments to AttachmentFiles property is that it uses the $item.AttachmentFiles.Add() method, which requires the parameter to be a AttachmentCreationInformation Object, not an attachment Object. I have no idea how to make this function as I intend so that I can add a pre-existing attachment to a new item.

PowerShell threading [duplicate]

I am trying to retrieve list of print queues from PowerShell as shown below.
But I am getting
The calling thread cannot access this object because a different thread owns it.
Is it happeneing because PowerShell not being able to support multiple threads?
Is there a way to get around this problem?
As far as I understand, you have to start PowerShell with -STA (single thread appartment) parameter to have your code working :
PowerShell -STA
Add-Type -AssemblyName "system.Printing"
$f = #([system.Printing.EnumeratedPrintQueueTypes]::local, [system.Printing.EnumeratedPrintQueueTypes]::shared)
$ps = New-Object system.Printing.localprintserver
$pq = $ps.GetPrintQueues($f)

Resources