I have a VLOOKUP being inserted into my spreadsheet's F column like this:
$vLookup = "=VLOOKUP($refCol,'$xlsLocsDIR[locs.xlsx]Device'!`$B`$2:`$C$rowsDvcs,2,FALSE)"
$sheetSave.Cells.Item(2,6).Formula = $vLookup
Which is, to be clear, saved properly in Excel like this:
=VLOOKUP(E2,'[locs.xlsx]Device'!$B$2:$C24549,2,FALSE)
(There are ~25k lines in the reference file, but there are over 200k in the file I have the VLOOKUP in.)
Because of the size of the file in which I'm doing the VLOOKUP within, and the customer could be utilizing 32-bit OS or Excel, I have to Copy/Paste no more than around 30000 rows at a time, to fill all 200k rows out, like so:
#32-bit OS/Excel app compatibility
#Excel/32-bit OS/memory errors occur if doing more than 30k cells
#instead, we do 20k, save, 20k, save, etc
for ($i=2; $i -le $rowsTrans; ($i+30000))
{
#set the stop point, not to exceed total usedrows
if (($i + 30000) -gt $totalRows)
{$j = $totalRows}
else
{$j = ($i+30000)}
#copy the data
$copyCell = (("F" + $i))
$copyRange = $sheetTrans.Range($copyCell)
$copyRange.Copy() | Out-Null
$sheetSave.Activate()
$pasteRange = $sheetTrans.Range(("F"+$i+":F"+$j)).Select()
$sheetSave.PasteSpecial(7)
$fileWorking.Save()
}
I only want to copy the VLOOKUP formula from Cell F2, into the next 20k rows of column F, then save the file, and iterate through again until I've populated the entire file.
When I do the above, and I've tried different methods aside from this example, I always receive a MethodInvocation Error, unless I explicitly make the file/sheet visible, like so, before the above loop:
$xlsObject.Visible = $true
What am I misunderstanding about the Copy() / PasteSpecial() function calls? Why does the sheet have to be visible?
NOTE: I've tried to anonymize the above code and limit what is necessary to understand the issue. The code functions, I just don't want to require the Excel instance to be brought into view at any point. I'd prefer the script run invisible to the end-user.
The MethodInvocation error I receive is typically as follows:
Exception calling "PasteSpecial" with "1" argument(s): "PasteSpecial method of
Worksheet class failed"
At line:1 char:25
+ $sheetTrans.PasteSpecial <<<< (9)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
I was able to solve this by doing a few direct references, and changing the way I assigned the range, and then calling PasteSpecial, like so:
$pasteRange = $sheetTrans.Range(("F"+$i+":F"+$j))
$pasteRange.PasteSpecial($xlPasteValues) | Out-Null
With declarations like this:
Add-Type -ASSEMBLY "Microsoft.Office.Interop.Excel" | out-null
$global:xlPasteFormulas = -4123
$global:xlPasteValues = -4163
Related
I have a very weird problem.
I have some vba scripts called merge1-6 which extracts data from different excel sheets.
These 6 resulting text files are merged using another macro "MergeALL" into 1 text file, so everything is on one string.
So far so good, it works fine.
So I wanted to make a powershell script that access the current excel document i have open, and fetch the J29 cell data, and merge this data with the value "0|" to combine a given string.
The actual powershell script I have to pull the cell data is as follows:
Macro : Call BID_MergeXCleanup_TextOut
#This script opens H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt and replace empty parts of "domain/users/0|" with "nothing"
#stripping away all entries not needed. Out-File used as output, with -NoNewline to prevent carriage return inserting empty line.
#$workbook = $excel.Workbooks.Open("H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.xlsm") #open workbook
$xl = [Runtime.InteropServices.Marshal]::GetActiveObject(Excel.Application')
$workbook = $xl.workbooks | ?{$_.FullName -eq "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.xlsm"}
$workbook.sheets.item(1).activate() #opens the sheet and activates it
$WorkbookTotal=$workbook.Worksheets.item(1) #Set sheet data to variable
$value = $WorkbookTotal.Cells.Item(29, 10) # Fetches data in Column 10, Row 29 which is the cell reference J29 where domain is.
$domain = $value.Text #Fetches "domain/users/" from the Fetch-tab of the excel file, to match the correct domain in use and stores result in the $domain variable
$leftoverdomain = '0|' #last part of domain string after the domain/user part
$joinedvariables = -join($domain,$leftoverdomain,"") #Joines the above variables together to form "domain/users/0|" as searchtag to replace as leftover bits.
$b = Get-Content -Path "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt" #Opens text file to replace in.
#Next part uses the $joinedvariables variable of merged search parametres and replaces it with "nothing" and saves the file back.
#(ForEach ($a in $b) {$a.Replace($joinedvariables, '')}) | Out-File "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt" -NoNewline
Start-Sleep -Seconds 0.3
### INFO ###
#I used a reference to the EXCEL cell here, instead of directly from the TXT-file as given what domain has been specified, the info in the TEXT file would vary
#But text in J29 would always equal the domain being used, and thus also correnspond with the one in the text file at all times.
#This way it will automatically always use the correct domain.
So, if I use a standalone reference to the excel document,
$workbook = $excel.Workbooks.Open("H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.xlsm")
...and directly open the workbook, in powershell ISE, the command is successful. BUT does not execute correctly if I call the PS1 script or run it manually from within the excel file. Which is why I started looking for how to access the current excel session leading me to the next method...
If I use
$xl = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
to reference the current running application, it successful from both ISE and inside Excel, when I run manually.
In both instances this work, as long as the TXT file that is to be modified exist and has the information in it that is searched for.
So given that I have an "unmodified file" I wanted to launch these macros by calling them from another macro.
This is the VBA code used from within the same excel sheet to launch the macros.
Call BID_1_TextOut
Application.Wait Now + TimeSerial(0, 0, 1)
Call BID_2_TextOut
Application.Wait Now + TimeSerial(0, 0, 1)
Call BID_3_TextOut
Application.Wait Now + TimeSerial(0, 0, 1)
Call BID_4_TextOut
Application.Wait Now + TimeSerial(0, 0, 1)
Call BID_5_TextOut
Application.Wait Now + TimeSerial(0, 0, 1)
Call BID_6_TextOut
Application.Wait Now + TimeSerial(0, 0, 1)
Call BID_MergeALL_TextOut 'Macro MergeALL - Merges all BID-TEXTOUT files to 1 file
Application.Wait Now + TimeSerial(0, 0, 3)
Call BID_MergeXCleanup_TextOut 'Macro MergeXCleanup - This script replace empty parts of "domain/users/0|" with "nothing"
Application.Wait Now + TimeSerial(0, 0, 1)
The two last macros are the important ones.
2nd last macro prepare the file that is to be used in "BID_MergeXCleanup" which is the PS1 script I am calling.
The macros i call are with the following code:
' Script to merge all the BID-TEXT-OUT files into 1 string with UTF32LE encoding.
Sub BID_MergeALL_TextOut()
Set objShell = CreateObject("wscript.Shell")
objShell.Run ("powershell.exe H:\Temp\PublicFolder-powershell\MergeALL_UTF32.ps1")
End Sub
and
'This script opens H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt and replace empty parts of "domain/users/0|" with "nothing"
'stripping away all entries not needed.
Sub BID_MergeXCleanup_TextOut()
Set objShell = CreateObject("wscript.Shell")
objShell.Run ("powershell.exe -noexit H:\Temp\PublicFolder-powershell\REPLACE_IN_EmptyDomainUsersPart.ps1")
End Sub
The weird thing is, if I use the "calling script" to run these commands in succession automatically, the last script ALWAYS fails
If I inspect the TXT file afterwards, I see that only the '0|' variable was removed, as it seems it was not able to fetch the J29 information from the Excel document.
If I run each of the macros 1 by 1 from within Excel after each other manually then they execute correctly and the scripts do as I want giving no errors
merge1-6-then-all-then-cleanup
So from what I can tell, my powershell script is able to call the active excel workbook, sheet, cell, get data and all that.
...but I just cant do it with calling the macro from another macro? In this example it fails if I use the "Call BID_MergeXCleanup_TextOut" reference to access the macro.
PS: I also tried to "share" the workbook, in case it was a permission issue, but it still gave error when running the Call-VBA script, but not manually running each macro in succession.
Any idea how I can fix this?
I have a feeling i might not allow the original PS1 file to have a $NULL expression exit, but i am unsure how to add it in.
The first code line that fails is
$workbook.sheets.item(1).activate()
I tried to add $NULL to it in Powershell ISE and then it fails
$null = $workbook.sheets.item(1).activate() <<<--- Fails with *"You cannot call a method on a null-valued expression."*
And i cannot see that i have any undecleared variables in the main script either.
Unsure how to figure this one out.
What i ended up with:
So #postanote up here suggested i Use COMs instead of InteropService.Marshal to access Excel.
I initially thought this would not work, since it would open a new instance of the workbook, but the COMs version have a way of hiding the instance you run with
$objExcel.Visible = $false
Now, the [Runtime.InteropServices.Marshal]::GetActiveObject version also have this value, it seems to be different somehow because, if I change my code to be COMs as follows:
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $false
$WorkBook = $objExcel.Workbooks.Open("H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.xlsm")
$WorkSheet = $WorkBook.sheets.item("Fetch")
$domain = $worksheet.Rows.Item(29).Columns.Item(10).Text
$leftoverdomain = '0|' #last part of domain string after the domain/user part
$joinedvariables = -join($domain,$leftoverdomain,"") #Joines the above variables together to form "domain/users/0|" as searchtag to replace as leftover bits.
$b = Get-Content -Path "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt" #Opens text file to replace in.
#(ForEach ($a in $b) {$a.Replace($joinedvariables, '')}) | Out-File "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt" -NoNewline
Start-Sleep -Seconds 0.3
...The script excecute without error when called from the VBA call script...
If I on the other hand try to incorporate the non-visible member in the original code as here:
$xl = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application') #opens an already open excel session
$xl.Visible = $false
$workbook = $xl.workbooks | ?{$_.FullName -eq "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.xlsm"}
$workbook.sheets.item(1).activate()
$WorkbookTotal=$workbook.Worksheets.item(1)
$value = $WorkbookTotal.Cells.Item(29, 10)
$domain = $value.Text
$leftoverdomain = '0|'
$joinedvariables = -join($domain,$leftoverdomain,"")
$b = Get-Content -Path "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt"
#(ForEach ($a in $b) {$a.Replace($joinedvariables, '')}) | Out-File "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt" -NoNewline
Start-Sleep -Seconds 0.3
...then it fails with the following changed error message:
Call was rejected by callee. (Exception from HRESULT: 0x80010001 (RPC_E_CALL_REJECTED))
So i guess the end result is that COMs makes it possible to open the same document more than once with the same name, as long as you hide the new excel instance from Windows window handler.
But...it would have been nice to know why the original code worked when ran manually, but not when called from the script, so if anyone knows, I'd love to know the answer to that one.
I am trying to set a value to an excel cell through Powershell, and I am getting the error HRESULT: 0x800A03EC, which ends the script prematurely. I realize there have been other questions relating to this error or similar, but none of the solutions have worked for me, so I am assuming this is a separate problem.
I have run my script before but it is only now giving me this error.
Relevant code:
$Output_Location = "Z:\Documents\Powershell"
$Excel_File = "Report.xlsx"
$ExcelWorkBook = $Excel.Workbooks.open("$Output_Location\$Excel_File")
$MainSheet = $ExcelWorkBook.worksheets.Item("Report")
$Sheet1 = $ExcelWorkBook.worksheets.Item("Sheet1")
$Sheet1.name = "Statistics"
$StatisticsSheet = $ExcelWorkBook.worksheets.Item("Statistics")
$row = 3
$column = 2
$StatisticsSheet.Cells.Item(2,2)= 'KeyToMatch'
$StatisticsSheet.Cells.Item($row,$column) = '=COUNTIFS(Report!E2:E200000,B$3,Report!G2:G200000,"UserMailbox")'
$row++
$StatisticsSheet.Cells.Item($row,$column) = '=COUNTIFS(Report!E2:E200000,B$3,Report!G2:G200000,"RemoteUserMailbox")'
$row++
The code loads up the excel file and hits the line which sets the cell (2,2)/(B,2) to its value just fine. But when the code hits the line setting the cell value the row below the KeyToMatch (B,3), it throws the error 0x800A03EC.
Full error:
Exception from HRESULT: 0x800A03EC
At Z:\Documents\Powershell\Reporting\Report.ps1:113 char:1
+ $StatisticsSheet.Cells.Item($row,$column).value = '=COUNTIFS(Report! ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException
I have tried spacing out the '=' between the cell and value, I have also tried the following:
$StatisticsSheet.Cells.Item($row,$column).value = ...
$StatisticsSheet.Cells.Item($row,$column).value2 = ...
$StatisticsSheet.Cells.Item($row,$column).text = ...
$StatisticsSheet.Cells.Item($row,$column).formula = ...
I can comment out any number of lines which set the cell's value to a formula and the first one to attempt to do so will throw the mentioned error.
Like I said, I have run this script before but it is only now giving me troubles. How can I fix this so the code runs smoothly?
Moved solution from question to answer:
RESOLUTION:
To resolve this issue, in the text I assigned to the cell, I replaced single quotes with double quotes, and because of this, had to escape the '$' and ' " ' characters. I also ADDED single quotes around "Report" (The table name from which data is being pulled) each time it came up within the text.
It ended up looking like this, and running fine:
$StatisticsSheet.Cells.Item($row,$column) = "=COUNTIFS('Report'!E2:E200000,B`$3,'Report'!G2:G200000,`"UserMailbox`")"
Still not sure why this error occurred-the code had worked every week prior to this! Before changing the script and finding the resolution, I tested it on multiple machines (5+) and it threw the error mentioned in the title every single time.
this is very simple issue, which in range put the values like
ExcelSheet.Cells.Range("A1:A1").Value = "'asdf"
I am working on adding links to a spreadsheet of variable length through PowerShell and I am running into an error no matter how I do it. This is the loop I am currently trying to make work, which is nested within an excel comobject, with $sheet representing $excel.activeworkbook.activesheet :
$v = 2
foreach($i in $list){
$r = "A"+$v
$link = "www.url.com"
$sheet.Hyperlinks.Add($r,$link)
$v++
}
Which keeps throwing the following error:
Exception setting "Add": Cannot convert the "A2" value of type "string" to type "Object".
At C:\hyperlink_wip.ps1:31 char:5
+ $sheet.Hyperlinks.Add($r,$link)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : RuntimeException
in this example, $list is a list which contains the information from one column of this spreadsheet and will always be the same length as the spreadsheet itself. The spreadsheet also has a header row, so the cells I want to hyperlink start at A2. I also tried the method outlined here for adding links to an excel spreadsheet but got an incorrect format error each time I tried it.
$r isn't a Range or Shape. Here's the Add Method on MSDN
Here's an example of how you could use it in your code:
$v = 2
foreach($i in $list){
$r = $sheet.range("A"+$v)
$link = "www.url.com"
$sheet.Hyperlinks.Add($r,$link)
$v++
}
I have two computers, one with windows7 and one with windows10. Both computers use Excel 15.0.4753.1003.
The following script fails on Windows10:
function write-toexcelrange(){
param(
#The range should be a cell in the upper left corner where you want to "paste" your data
[ValidateNotNullOrEmpty()]
$Range,
# data should be in the form of a jagged multiarray ("row1Column1","row2column2"),("row2column1","row2column2")
# if data is a simple array of values, it will be interpreted as 1 column with multiple rows
# Rows can differ in length
[validatenotnullorempty()]
[array]$data
)
$rows=0
$cols=0
if($data -is [array]) {
foreach($row in $data){
$rows++
$cols=[math]::max($cols,([array]$row).length)
}
#Create multiarray
$marr=new-object 'string[,]' $rows,$cols
for($r=0;$r -lt $marr.GetLength(0);$r++) {
for($c=0;$c -lt $marr.GetLength(1);$c++) {
$marr[$r,$c]=[string]::Empty
}
}
for($r=0;$r -lt $rows;$r++) {
if($data[$r] -is [array]){
for($c=0;$c -lt ([array]$data[$r]).length;$c++) {
$marr[$r,$c]=$data[$r][$c].ToString()
}
} else {
$marr[$r,0]=$data[$r].ToString()
}
}
$wrr=$range.resize($rows,$cols)
$wrr.value2=$marr
} else {
$wrr=$range
$wrr.value2=$data
}
#Return the range written to
$wrr
}
$excel = New-Object -ComObject Excel.Application
$excel.visible = $true
$defaultsheets=$excel.SheetsInNewWorkbook
$excel.SheetsInNewWorkbook=1
$wb = $Excel.Workbooks.add()
$excel.SheetsInNewWorkbook=$defaultsheets
$mysheet = $wb.worksheets.item(1)
$mysheet.name = "test"
write-toexcelrange -Range $mysheet.range("A1") -data $exceldata|out-null
With the following error:
Unable to cast object of type 'System.String[,]' to type 'System.String'.
At C:\data\rangetest.ps1:38 char:9
+ $wrr.value2=$marr
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], InvalidCastException
+ FullyQualifiedErrorId : System.InvalidCastException
It appears as if the value2 property behaves differently in Windows10 which is weird considering it´s the same version of excel.
Now to the question:
Is there a fix/workaround to getting the data into the cells, which does not involve looping through all the cells.
Update 1
It was suggested by Grade 'Eh' Bacon that I try the .Formula property. It Works! I also noted that Windows10 uses Powershell v5 while my Windows7 has Powershell v4.
Since that worked for you I'll flesh it out as an answer. To summarize, pay attention to the differences between .text, .value, .value2, and .formula [or .formulaR1C1]. See discussion of the first 3 here:
What is the difference between .text, .value, and .value2?
And discussion of .Formula here:
Can Range.Value2 & Range.Formula have different values in C#, not VBA?
Without getting into why any of these can have different values (in short, formatting and other metadata can have an impact on some of those options in different ways, depending on what type of entry is made to a given cell), after reading those Q&As above, I just always use Formula when referring to what's inside a cell. In most cases, that's what you likely want VBA to look at anyway. Changing .value2 to .formula seems to work here, although I have no idea why that would be the case between Windows versions.
Using Powershell 2.0 I am trying to copy the results of a formula in one worksheet to another. In order to do this I need to use paste special (otherwise I get #value error).
However, the following code is not working. The first block is just a regular paste that works fine, but the second block has the paste special and it doesn't work. The error I get is below.
The question does anyone know what this error means or what the best way to do a paste special using Powershell and Excel is?
Works:
$worksheet.activate()
$sessionidcopyrange = $worksheet.Range("J1").EntireColumn
$sessionidcopyrange.copy()
$worksheet2.activate()
$sessionidpasterange =$worksheet2.Range("A1")
$worksheet2.paste($sessionidpasterange, $false)
Doesn't work:
$worksheet.activate()
$codecopyrange = $worksheet.Range("Q1").EntireColumn
$codecopyrange.copy()
$worksheet2.activate()
$codepasterange = $worksheet2.Range("B1")
$worksheet2.pastespecial(-4163, $false)
Exception calling "PasteSpecial" with "2" argument(s): "PasteSpecial method of
Worksheet class failed"
At line:33 char:25
+ $worksheet2.pastespecial <<<< (-4163, $false)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
Example:
$Dest = $Excel.Workbooks.Add()
$Dest.ActiveSheet.Range("B:C").copy()
$Dest.ActiveSheet.Range("D1").Select()
$Dest.ActiveSheet.Range("D1").PasteSpecial(-4163)
Paste special with PowerShell and Excel:
The second value is of type XlPasteSpecialOperation.
$worksheet2.pastespecial(-4163, $false)
should be
$worksheet2.pastespecial(-4163, xlPasteSpecialOperationNone)