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.
Related
For some reason, inserting formula into Excel table using Powershell script. If formula is entered manually, it works fine. Suppose I have 3 elements in $names array:
$excel = New-Object -ComObject excel.application
$excel.visible = $true
$workbook = $excel.Workbooks.Add()
$names = #()
foreach ($i in 1,2,3) {
$ws = $workbook.Worksheets.Add()
$names += $ws.Name
}
$i = 1
foreach ($sheet in $names) {
$formula = "=COUNT('$sheet'!D:D)"
write-host $formula
$excel.cells.item($i, 1).Formula = $formula
$i++
}
What happens: The first cell gets the correct value, but the formula name is missing (e.g. instead of =COUNT('Sheet2'!D:D) I get =('Sheet2'!D:D), and the other 2 throw 0x800A03EC exception. The sheet I reference definitely exists, because if I copy and paste values printed by write-host $formula manually, it works as expected.
It also seems to work correctly If I don't reference another sheet. The code below doesn't throw any exceptions:
$formula = "=COUNT(D:D)"
$excel.cells.item($i, 1).Formula = $formula
UPDATE:
Seems like an issue specific to Excel 2010. In Excel 2016, the same code works fine.
I have tried to mock your approach and came up with the following working code:
foreach($ws in $b.Sheets) {
$ws.Cells.Item(1,1).Formula = "=COUNT($($ws.Name)!D:D)"
}
I've managed to make it work using FormulaLocal instead of Formula. I still believe it's a bug in Excel 2010, since Formula works perfectly fine in Excel 2016. I also still can't insert array formulas containing sheet references in Excel 2010 from script: even assigning FormulaLocal first, and then assigning it's value to FormulaArray doesn't work.
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++
}
For example, I'm looking to determine the following logic:
if (B2 in A1:B20) # if cell B2 is within the range A1:B20
{
return $true
}
Is there a function within excel that can be used for something like this? I read about =COUNTIF() function but was not able to get it working. Again this is using an Excel COM object within Powershell.
Thanks
Since cell names are basically coordinates, this is purely a question of arithmetic comparison, no need to involve Excel itself:
function Test-CellInRange
{
param(
[ValidatePattern('^[A-Z]+\d+$')]
[string]$Cell,
[ValidatePattern('^[A-Z]+\d+\:[A-Z]+\d+$')]
[string]$Range
)
# Grab X and Y coordinates from Range input, sort in ascending order (low to high)
$P1,$P2 = $Range -split ':'
$Xpoints = ($P1 -replace '\d'),($P2 -replace '\d') |Sort-Object
$Ypoints = ($P1 -replace '\D'),($P2 -replace '\D') |Sort-Object
# Grab X and Y coordinate from cell
$CellX = $Cell -replace '\d'
$CellY = $Cell -replace '\D'
# Test whether cell coordinates are within range
return ($CellX -ge $Xpoints[0] -and $CellX -le $Xpoints[1] -and $CellY -ge $Ypoints[0] -and $CellY -le $Ypoints[1])
}
Use it like:
if(Test-CellInRange -Cell B2 -Range A1:B20){
"B2 is in A1:B20"
}
I'm not sure about the COM interface (never used it), but if you have access to the INTERSECT Method then you could write something like this:
If Not Application.Intersect(Range("B2"), Range("A1:B20")) Is Nothing Then
CODE_IF_TRUE
End If
It just does a set intersection of the two ranges. If they don't intersect, then the one is definitely not a subset of the other. If you need to check for a proper subset, you'd have to get more creative and check whether the intersection was the same as the entire desired subset. Remember your set logic and check out the UNION Method - between these things you should be able to handle any sort of operations you want.
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
Following up from this question, I'm trying to replace $B$1 to TEXT($B$1,"0000") on all formulas I can find on a lot of workbooks. Now that i'm past that .save() problem, I've got another (which should've been the first, actually): I can't seem to change .Formula value, no matter what I try.
PS C:\> $Search.Formula = $Search.Formula -replace '\$B\$1','TEXTO($B$1,"0000")'
Exceção ao definir "Formula": "Exceção de HRESULT: 0x800A03EC"
No linha:1 caractere:1
+ $Search.Formula = $Search.Formula -replace '\$B\$1','TEXTO($B$1,"0000")'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], SetValueInvocationException
+ FullyQualifiedErrorId : CatchFromBaseAdapterSetValueTI
Formula is:
=PROCV(("A"&ANO($A6)&"M"&MÊS($A6)&"P"&$B$1);BASE!$A:$P;9;FALSO)
In English, if I remember the correlation correctly:
=VLOOKUP(("A"&YEAR($A6)&"M"&MONTH($A6)&"P"&$B$1);BASE!$A:$P;9;FALSE)
The expected output would be
=VLOOKUP(("A"&YEAR($A6)&"M"&MONTH($A6)&"P"&TEXT($B$1,"0000"));BASE!$A:$P;9;FALSE)
There were a couple of things going on with what you supplied. At first glance, you seem to be using the backslash for an escape character to make the dollar signs literal. The escape character for this in PowerShell is the back-tick or grave (e.g. `).
If I was performing this action within Excel, I would probably just Find & Replace every $B$1 on the worksheet with text($B$1, "0000"). Seems to me that it is powerful enough to take care of the operation without PowerShell's -replace method. The worksheet method does depend somewhat on $B$1 begin available but since it is also in the replacement, you pretty much need to know what you are replacing beforehand. Some error control in that area may be necessary if this script is left for casual users.
$excel = New-Object -comobject Excel.Application
$FilePath = "c:\temp\example.xlsx"
$workbook = $excel.Workbooks.Open($FilePath)
$excel.Visible = $true
$worksheet = $workbook.worksheets.item("Sheet1")
#set some Find & Replace vars
$what = "`$B`$1"
$with = "text(`$B`$1, `"0000`")"
#use worksheet-wide Find & Replace to change formula
$worksheet.usedrange.replace($what, $with, 2)
#formula(s) should be changed. now Find it and display it
$fnd = $worksheet.usedrange.find($what, $worksheet.range("A1"), -4123, 2)
Write-Output $fnd.formula
$workbook.save()
$workbook.close()
$excel.quit()
I've proofed the Range.Replace method by finding and displaying the formula after the operations and made more extensive use of the grave escape character rather than swap back and forth between single quotes and double quotes within quoted strings.
The above code uses the EN-US version I tested with. The actual replacement text for your regional settings would seem to be,
$with = "texto(`$B`$1; `"0000`")"