I'm creating a function that does some things to Excel files. The list of Excel files are passed into the function as an array called $excelFiles. The code shown below is in progress (it does not yet do all the things it's intended to do). This code, as written so far, appears to be failing because there are no quotes around the string held in $excelFile that sets the $wb variable (right before the nested foreach):
Function CovertExcelFileToTextFiles ($excelFiles)
{
# create an Excel application object (fire off Excel in the background)
$excelApp = New-Object -ComObject Excel.Application
$excelApp.Visible = $false
$excelApp.DisplayAlerts = $false
# get first 3 letters of each file's name
foreach ($excelFile in $excelFiles)
{
$name = Split-Path $excelFile.FullName -Leaf #get filename only from full path
$prefix = $name.Substring(0,3) #get first 3 letters of filename
#look at contents of this variable
$excelFile
$wb = $excelApp.Workbooks.Open($excelFile)
foreach ($ws in $wb.Worksheets)
{
$n = $prefix + "_"+ $ws.Name
$n
}
}
$excelApp.Quit()
}
Here is the error that appears in the console:
The reason I suspect the problem is lack of quotes is because the code works if $wb is set to a hardcoded file path.
I'm having difficulty figuring out how to get double quotes around the variable to feed into the line that sets $wb. I have tried "$excelFile" and the editor puts a red squiggly line under it so apparently that's not allowed. I have also tried creating a new variable and populating that with "$excelFile", then plugging that into the parenthesis in the $wb line. That causes an error in the console as well. How can double quotes be put around $excelFile?
Lack of quotes is not your issue. When you hardcode a file and use quotes, the quotes tell the parser that it's a string; they aren't part of the string value.
You need to know what data type $excelFile is (that is, $excelFiles is an array of what?).
If your use of $excelFile.FullName is correct, then it seems you're dealing with an object.
In that case, the .Open() method is likely expecting a [String] and won't understand the object you're passing it, so try:
$wb = $excelApp.Workbooks.Open($excelFile.FullName)
A good way to troubleshoot this kind of thing is to use PowerShell ISE, then set a breakpoint.
In your case, set a breakpoint on the $wb = ... line, execute, and when the breakpoint is hit, execution will stop at that line (before executing it).
At that point, you can use the console to execute statements that will be run in the context of your running code. So for example you could run:
$excelFile.GetType()
or
$excelFile | Get-Member
and learn some things about the object you're dealing with. You could look at its properties, etc.:
$excelFile.FullName
You can look at the overloads of the method you're calling:
$excelApp.Workbooks.Open # <-- note no parentheses
Related
I have a folder with multiple PDFs I need to print to different printers. I've created variables for each shared printer and depending on the first 2 letters of the PDF the printing will go to the matching printer.
I'm having trouble concatenating 2 strings to form an existing variable to use it later in the printing call.
This is what I have now (all PDFs in the dir starts with 01 for now):
# SumatraPDF path
$SumatraExe = "C:\Users\Administrador.WIN-FPFTEJASDVR\AppData\Local\SumatraPDF\SumatraPDF.exe"
# PDFs to print path
$PDF = "C:\Program Files (x86)\CarrascocreditosPrueba2\CarrascocreditosPrueba2\DTE\BOL"
# Shared printers list
$01 = '\\192.168.1.70\epson'
$02 = '\\192.168.1.113\EPSON1050'
cd $PDF
While ($true) {
Get-ChildItem | Where {!$_.PsIsContainer} | Select-Object Name | %{
$Boleta = $_.Name
$CodSucursal = $Boleta.Substring(0,2)
$CodImpresora = '$' + $CodSucursal
Write-Host $CodImpresora -> This shows literal $01 on PS ISE
Write-Host $01 -> This show the shared printer path
}
Start-Sleep -Seconds 5
}
# Actual PDF printing...
#& $SumatraExe -print-to $CodImpresora $PDF
So basically I need to call an existing variable based on 2 strings. Probably this could be achieved with a Switch but that will be too extensive.
concatenating 2 strings to form an existing variable
That won't work in PowerShell, variable tokens are always treated literally.
I'd suggest you use a hashtable instead:
# Shared printers table
$Impresoras = #{
'01' = '\\192.168.1.70\epson'
'02' = '\\192.168.1.113\EPSON1050'
}
Then inside the loop:
$Boleta = $_.Name
$CodSucursal = $Boleta.Substring(0,2)
$Impresora = $Impresoras[$CodSucursal]
Although the language syntax don't support variable variable names, you can resolve variables by name using either the Get-Variable cmdlet:
# Returns a PSVariable object describing the variable $01
Get-Variable '01'
# Returns the raw value currently assigned to $01
Get-Variable '01' -ValueOnly
... or by querying the Variable: PSDrive:
# Same effect as `Get-Variable 01`
Get-Item Variable:\01
While these alternatives exist, I'd strongly suggest staying clear of using them in scripts - they're slow, makes the code more complicated to read, and I don't think I've ever encountered a situation in which using a hashtable or an array wasn't ultimately easier :)
I want to Dynamically assign information to an "Add_Click" event on a GUI button in powershell.
So an overview - I am making a GUI tool, that loads some buttons for custom "fixes" for peoples PC's. Each button is loaded from a script, and each script contains variables for information such as, Button Name, Button Icon, as well as a function that does whatever the script is meant to do.
So as per example below, I am finding the problem is where I assign the Add_Click event. I'm sorry not sure how to explain better, but it does not seem to expand the $script variable, it will save the add_click as the variable, so that when I run the program, all buttons are using whatever was last stored in the variable $script (so all the buttons look different, but run the same script).
What I want to be able to do, is have each button use an Add_Click to run whatever script is stored in $script at the time the Add_Click is assigned in the "Foreach" loop.
Example script:
#Button Container (Array to store all the Buttons)
$ComputerActionButtons = #()
#Get all the script files that we will load as buttons
$buttonsFileArray = Get-ChildItem "$dir\Addin\ " | Select -ExpandProperty Name
#Load Buttons
$ButtonTempCount = 1
Foreach ($script in $buttonsFileArray){
. $dir\Addin\$script
Write-Host "Loading Button: $ButtonName"
# Generate button
$TempButton = New-Object System.Windows.Forms.Button
$TempButton.Location = New-Object System.Drawing.Size(140,20)
$TempButton.Size = New-Object System.Drawing.Size(100,100)
$TempButton.Text = "$ButtonName"
$TempButton.Image = $ButtonIcon
$TempButton.TextImageRelation = "ImageAboveText"
$TempButton.Add_Click({& $dir\Addin\$script})
$ComputerActionButtons += $TempButton
}
#Add Buttons to panel
$CAButtonLoc = 20
Foreach ($button in $ComputerActionButtons){
$button.Location = New-Object System.Drawing.Size(50,$CAButtonLoc)
$ComputerActionsPanel.Controls.Add($button)
$CAButtonLoc = $CAButtonLoc + 120
}
I have been researching variables, found you can do a New-Variable and Get-Variable command to dynamically set and get variable names, but using this does not seem to help because of the original problem.
I hope I have explained this well enough, please let me know if I can give more information to help.
Thank you,
Adam
You need to create a closure over the $script value at the time of assignment, instead of relying on the value of $script at runtime.
Fortunately, all ScriptBlocks support this with the GetNewClosure() method.
foreach($i in 1..3){
$blocks += {echo $i}.GetNewClosure()
}
$blocks |Foreach-Object {$_.Invoke()}
which produces:
1
2
3
As opposed to
foreach($i in 1..3){
$blocks += {echo $i}
}
$blocks |Foreach-Object {$_.Invoke()}
which produces:
3
3
3
What GetNewClosure() does is that it "captures" the variables referenced inside the ScriptBlock at that time and binds it to the local scope of the scriptblock.
This way the original $i variable (or $script for that matter) is "hidden" beneath an $i variable that has the same value as $i used to have - namely at the time GetNewClosure() was called
Instead of using GetNewClosure, which create new dynamic module, capture local scope variables to it and bind ScriptBlock to that module, so in fact you lose access to any function defined not in global scope, you can just store any button specific data in the button itself. Control base class have Tag property where you can store any data you want.
$TempButton.Tag=#{Script=$script}
$TempButton.Add_Click({param($Sender) & "$dir\Addin\$($Sender.Tag.Script)"})
I'm trying to get code coverage using Sonarqube. The coverage report is generated by karma. For some reason, the coverage file generated by Karma changes
the case of 22 files inside the report. As a result, I'm unable to get coverage for those 22 files. I use a PowerShell script in my Jenkins to generate a canonical path. Below is the script. My script should perform the below steps:
Access the coverage report (unit-tests-lcov.info)
Read the report line by line
Use every file inside unit-tests-lcov.info starting with 'SF' and pass it to the canonical function
Save the file
I'm unable to write a script for the 3rd step. Can anyone make necessary changes to my script below?
$getPathNameSignature = #'
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern uint GetLongPathName(
string shortPath,
StringBuilder sb,
int bufferSize);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError=true)]
public static extern uint GetShortPathName(
string longPath,
StringBuilder shortPath,
uint bufferSize);
'#
$getPathNameType = Add-Type -MemberDefinition $getPathNameSignature -Name GetPathNameType -UsingNamespace System.Text -PassThru
function Get-PathCanonicalCase
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]
# Gets the real case of a path
$Path
)
if( -not (Test-Path $Path) )
{
Write-Error "Path '$Path' doesn't exist."
return
}
$shortBuffer = New-Object Text.StringBuilder ($Path.Length * 2)
[void] $getPathNameType::GetShortPathName( $Path, $shortBuffer, $shortBuffer.Capacity )
$longBuffer = New-Object Text.StringBuilder ($Path.Length * 2)
[void] $getPathNameType::GetLongPathName( $shortBuffer.ToString(), $longBuffer, $longBuffer.Capacity )
return $longBuffer.ToString()
}
$file3 = "$env:WORKSPACE\UIArtifacts\unit-tests-lcov.info"
$text = (Get-Content -Path $file3 -ReadCount 0) -join "`n"
$ran = $text -Includes 'SF'
Get-PathCanonicalCase($text) | Set-Content -Path $file3
A part of the input file looks like:
I need to pass the file paths to the Get-Canonical function. PS. Part of the file paths is the environment variable.
TN:
c:\sysapps\hudson\.jenkins\jobs\CropObsUi-Metrics\workspace\encirca\encConf.js
FNF:0
FNH:0
DA:10,1
DA:14,1
DA:30,1
DA:31,1
DA:32,1
DA:33,1
DA:34,1
DA:35,1
DA:36,1
DA:37,1
DA:39,1
LF:11
LH:11
BRF:0
BRH:0
end_of_record
TN:
c:\sysapps\hudson\.jenkins\jobs\CropObsUi-Metrics\workspace\encirca\common\util\data.js
FN:25,(anonymous_1)
FN:57,(anonymous_2)
FN:87,(anonymous_3)
FN:149,(anonymous_4)
FNF:4
FNH:0
FNDA:0,(anonymous_1)
FNDA:0,(anonymous_2)
FNDA:0,(anonymous_3)
FNDA:0,(anonymous_4)
Ok, short list of issues. I see no reason for the -join command. Normally the Get-Content cmdlet will read a text file in as an array of strings, with each line being one string. When you join them it is then converted to one multi-line string. That is totally opposed to your purposes.
$text = Get-Content -Path $file3
You can filter the lines using a Where statement, and the -like operator.
$ran = $text | Where{$_ -like "SF*"}
When you call a function the correct format is normally:
FunctionName -Parameter Value [-AdditionalParameters AdditionalValues]
You can leave out the parameter names and just put the values in order in most cases. So your last line should be:
Get-PathCanonicalCase $ran | Set-Content -Path $file3
That would only output the lines that started with SF though, and I'm not sure how that's going to work since I don't think a path is going to start with SF. I have a feeling that there is more to the line, and this is not going to deal with your problem like you expect it to. That function expects the string that is passed to it to be a path, and only a path. It does not expect to have to parse a path out of a longer string.
OK to pass to the function:
c:\temp\somefile.csv
Not OK to pass to the function:
SF: c:\temp\somefile.csv <8,732 KB> 11/3/2015 08:16:32.6635
I have no idea what your lines look like in your file, so I just randomly made that up, but the point is that the function is not going to work if your path is a substring of what you are passing to the function. I think you are going to need some additional logic to make this work.
But, this does answer your question as to how to pass each line of the file that starts with SF to the function.
Edit2: Ok, I think you were probably better off before you remove the SF: from the lines with a path in them. Here's why... SF: makes it easy to know what lines need to be passed to the function, while the others can be simply passed through. Trimming the "SF: " off the beginning is easy. So, we're going to use RegEx to replace the path with the updated path that the function provides. We're going to use the 'SF: ' to figure out where the paths are. Here we go...
First import the file just like you were, but don't -join it (explained above).
$text = Get-Content -Path $file3
Then we're going to skip the whole $ran = bit, because there's no need for it. Instead we pipe $text into a ForEach loop, and in that loop look at the line. If the line starts with SF: we replace it with "SF:" followed by the output of the function. For the function we send it a substring starting at the 4th character for the current line, so it skips the 'SF:' and only gets the path. If it isn't a SF: line we simply output the line unchanged.
$text |%{If($_ -like "SF:*"){"SF:$(Get-PathCanonicalCase $_.substring(3))"}else{$_}} | Out-File $file3
How can I force string variable expansion?
I need to read a string with one or more variable names in it (a template) and then expand it after I read the file. The key is that I must read the contents of the file before I declare the variables that will be used in the expansion. I've tried several ways but I can't get it to work.
It's not an option to read the file after $environment is defined.
Contents of name.txt:
$environment-RPT-INT
#example 1
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$expanded = $ExecutionContext.InvokeCommand.ExpandString($name)
$expanded
#example 2
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$expanded = $expanded = Invoke-Expression "`"$template`""
$expanded
#example 3
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$name = $name.Clone()
$expanded = $ExecutionContext.InvokeCommand.ExpandString($name)
$expanded
Any help is appreciated.
Updated: Example 1 is now working for me.
It looks like you've found some possible solutions, but I'll suggest another that is in my opinion a bit smarter and more robust.
Instead of requiring variable names in your text file, why not use format specifiers. For example, the contents of name.txt:
{0}-RPT-INT
And in your script:
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$expanded = $name -f $environment
$expanded
This way, you can rename the variable w/o changing any of your text files. As a bonus, if your text file comes from unknown sources, your script is vulnerable to code injection. For example, say you are given a text file with these contents:
$(rm -whatif -recurse -force c:\)-RPT-INT
Then ExpandString or Invoke-Expression will happily execute that code.
Your Invoke-Expression example is pretty close. Instead of using $template though, you need to use $name.
#example 2
$name = gc 'c:\temp\name.txt';
$environment = '9065DEV';
$expanded = Invoke-Expression -Command "`"$name`"";
$expanded;
If you are willing to store your Setting/Values in a CSV, I wrote a module to pull values from a CSV, put it into a HereString... any variables you put into the CSV become fully expanded inside the Here-String. That way, you can normally address the field names and values.
I might be able to change this to also work with traditional INI's if anyone is interested.
https://github.com/Inventologist?tab=repositories
Look for: HereStringFromCSV
There is a function called ExpandString predefined in powershell. It's inside $ExecutionContext, as follows.
$mystring = #'
This is a here string with some embedded variables.
Here is variable foo -- $foo
Here is variable bar -- $bar
Here is variable bletch -- $bletch
'#
#This displays the here string as is.
$mystring
#now define foo, bar and bletch
$foo = 5
$bar = Get-Date
$bletch = "George Washington"
#now run the here string through Expandstring.
$ExecutionContext.InvokeCommand.ExpandString($mystring)
Slowly learning Powershell ... I'm working on a script to query a third party AD/AM database (ldap). The specific LDAP property name that I want has a hyphen in the name.
I can do this in c# without thinking about it, but I don't want to fire up Visual Studio just to do some simple scripting stuff that changes frequently.
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
.....
$results = $objSearcher.FindAll()
foreach($result in $results) {
$item = $result.Properties
$item.some-property # this fails because of '-'
$result['some-property'] # 'Unable to index into an object of type System.DirectoryServices.SearchResult.'
}
You can also specify the property name via a variable:
$prop = 'some-property'
$result.$prop
You need to place curly braces around the hyphenated property name. This should work:
$item.{some-property}