Compare commits

..

52 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
d823761970 Implement gradient fill functions to resolve issue #1720
Co-authored-by: dfinke <67258+dfinke@users.noreply.github.com>
2025-09-22 11:31:26 +00:00
copilot-swe-agent[bot]
5b4ae76394 Initial plan 2025-09-22 11:21:38 +00:00
Doug Finke
5387c06146 Merge pull request #1695 from scriptingstudio/master
[Get-HtmlTable] XPath optimization
2025-04-23 08:16:05 -04:00
Matthew Gray
45ed6a06dc [Get-HtmlTable] XPath optimization
```powershell
$rows =    $h.SelectNodes("//table[$TableIndex]//tr")
```
XPath selector in line 53 uses complex expression that can lead to unexpected result. The problem is that HtmlAgilityPack may have specific issues. In particular, on websites containing multiple tables this selector can find not one table. This is aggravated by the fact that tables can have different structures.
To avoid ambiguity this PR suggests to separate queries. Oneliner simplifies error checking
```powershell
$rows = try {
  $h.SelectSingleNode("//table[$TableIndex]").SelectNodes(".//tr")
} catch {}
if (-not $rows) {Write-Warning "Could not find rows for `"//table[$TableIndex]`" in $Url ."}
```
This expression doesn't even need testing, it just works.
2025-04-23 13:36:32 +03:00
dfinke
fa447a745c move llms text to root 2025-04-11 07:10:31 -04:00
dfinke
49affcfba7 add llms text of Examples directort for use in LLMs 2025-04-11 06:51:18 -04:00
dfinke
dc4a5e9db9 chore: Update changelog for version 7.8.10 and acknowledge PR contributions 2024-10-21 19:44:36 -04:00
Doug Finke
85e48acf36 Merge pull request #1648 from evenmartinsen/master
Remove flagged URL
2024-10-21 19:42:23 -04:00
dfinke
fb41d3de83 chore: Update module version to 7.8.10 2024-10-21 19:39:44 -04:00
dfinke
db84a59dd0 chore: Update module version to 7.8.10 2024-10-21 19:37:58 -04:00
Even Martinsen
5a61c5dda4 Re-add icon variable (Commented)
re-added variable for easier modification in the future.
2024-10-21 14:37:55 +02:00
Even Martinsen
a2bc50aeb0 Remove flagged URI
Removed due to various AV's flagging the link as malicious thus flagging and quarentining the module.
2024-10-21 14:35:50 +02:00
dfinke
fa907da4a4 chore: Update module version to 7.8.9 2024-05-18 09:41:57 -04:00
dfinke
24c205e65d feat: Improve ConvertTo-ExcelXlsx robustness
This commit improves the `ConvertTo-ExcelXlsx` function by making it more robust. Thanks to Edward Miller for the contribution.

Note: This message follows the established convention of starting with a type (feat for feature) and providing a concise and clear description of the changes made.
2024-05-18 09:41:50 -04:00
Doug Finke
a1418a336e Merge pull request #1603 from edwardmiller-mesirow/read-only
[ConvertTo-ExcelXlsx] open XLS as read-only
2024-05-18 09:37:38 -04:00
dfinke
63683db543 chore: Update module version to 7.8.8 2024-05-18 09:33:54 -04:00
Edward Miller
36b5495bd5 check for null first 2024-05-17 23:28:26 -05:00
Edward Miller
722516de7c use try-finally 2024-05-17 23:24:11 -05:00
Edward Miller
57bb049111 open XLS as read-only 2024-05-17 22:41:53 -05:00
Doug Finke
74cbca8b2f Merge pull request #1591 from pbossman/master
Resolve worksheet ArgumentCompleter Fixes:#1590
2024-05-03 13:50:52 -04:00
Doug Finke
53712d4f7f bump version 2024-05-03 13:46:19 -04:00
Doug Finke
98e2ac96ea update changelog and add image 2024-05-03 13:46:02 -04:00
Phil Bossman
efa73b37a0 Resolve worksheet argumentcompleter Fixes:#1590 2024-04-30 11:59:08 -04:00
Doug Finke
c1b395fac6 Merge pull request #1578 from RipFence/ChartDataSeparateBranch
ChartDataSeparatePage.ps1
2024-03-15 08:24:32 -04:00
dfinke
0d8d125222 Add infrastructure change and new features 2024-03-15 08:21:17 -04:00
dfinke
f77ee7b060 Ignore Examples folder in CI workflow 2024-03-15 08:21:07 -04:00
Tim Kremer
8ff1757c06 ChartDataSeparatePage.ps1 2024-03-14 13:33:49 -05:00
dfinke
ab526558a3 more badges 2023-12-27 16:00:40 -05:00
Doug Finke
61744012cd Merge pull request #1522 from kyllath/master
Add missing parameter aliases to align with caller/callee
2023-10-12 17:16:46 -04:00
dfinke
2cb4712bc5 bump version 2023-10-12 17:10:00 -04:00
dfinke
6847a49baa update log 2023-10-12 17:09:56 -04:00
John Boyne
b8a8f4c565 Add missing parameter aliases to align with caller/callee 2023-10-11 20:36:26 +01:00
Doug Finke
edf25cf946 Merge pull request #1465 from dfinke/Extend-Get-Excel-File-Summary
Add Get-ExcelFileSchema
2023-06-14 05:54:11 -04:00
dfinke
77ec946b84 update changelog 2023-06-14 05:45:57 -04:00
dfinke
637c11c2e6 needs to work xplat 2023-06-10 16:40:25 -04:00
dfinke
cd2a70197c use -BeExactly 2023-06-10 16:00:11 -04:00
dfinke
d99bd8fc9e add tests 2023-06-10 15:45:35 -04:00
dfinke
23f1d92c1b first pass 2023-06-10 15:27:53 -04:00
Doug Finke
ab4d03c984 Merge pull request #1392 from stahler/patch-7
minor spelling edit
2023-02-05 06:41:07 -05:00
Doug Finke
ca4dfb7040 Merge pull request #1391 from stahler/patch-6
minor syntax changes
2023-02-05 06:40:54 -05:00
Wes Stahler
a9eca05303 minor spelling edit 2023-02-04 22:16:22 -05:00
Wes Stahler
195efdea54 minor syntax changes 2023-02-04 22:04:08 -05:00
Doug Finke
24e5fccafe Merge pull request #1361 from dfinke/thkn-hofa-Issue1344
Fix Issue 1344 with Freeze Top Row
2022-12-28 15:58:09 -05:00
dfinke
840870df11 Resolve conflict 2022-12-28 14:26:43 -05:00
dfinke
0fe6654233 Add additional test 2022-12-28 14:16:14 -05:00
dfinke
7598b75538 update 2022-12-27 16:20:50 -05:00
Doug Finke
ed6c87e8de Merge pull request #1357 from thkn-hofa/Issue1316
Add -NoHyperLinkConversion
2022-12-27 16:11:17 -05:00
dfinke
2642211e12 Added some hyperlink tests 2022-12-27 16:06:07 -05:00
Thomas Hofkens
9373765e10 Introduced tests for Freezing panes 2022-12-26 13:10:24 +01:00
Thomas Hofkens
16820a30c4 Add -NoHyperLinkConversion 2022-12-26 01:22:22 +01:00
Thomas Hofkens
438d7e2b03 Fix Issue 1344 2022-12-25 23:44:21 +01:00
dfinke
c617d96322 Tweak both 2022-12-11 10:26:47 -05:00
23 changed files with 6734 additions and 51 deletions

View File

@@ -3,8 +3,11 @@ on:
branches:
- master
- Set-up-GHA-CI/CD
paths-ignore:
- 'Examples/**'
pull_request:
jobs:
validate:

View File

@@ -0,0 +1,35 @@
$data = ConvertFrom-Csv @"
Region,State,Units,Price
West,Texas,927,923.71
North,Tennessee,466,770.67
East,Florida,520,458.68
East,Maine,828,661.24
West,Virginia,465,053.58
North,Missouri,436,235.67
South,Kansas,214,992.47
North,North Dakota,789,640.72
South,Delaware,712,508.55
"@
$xlfile = "$PSScriptRoot\spike.xlsx"
Remove-Item $xlfile -ErrorAction SilentlyContinue
$xlpkg = $data | Export-Excel $xlfile -WorksheetName Data -AutoNameRange -PassThru
$null = Add-Worksheet -ExcelPackage $xlpkg -WorksheetName Summary -Activate
$params = @{
Worksheet = $xlpkg.Summary
Title = "Sales by Region"
ChartType = 'ColumnClustered'
# XRange = "Data!A2:A10"
# YRange = "Data!C2:C10"
XRange = 'Data!Region'
YRange = 'Data!Units'
}
Add-ExcelChart @params
Close-ExcelPackage $xlpkg -Show

152
GRADIENT_GUIDE.md Normal file
View File

@@ -0,0 +1,152 @@
# Working with Gradient Fills in ImportExcel
## Overview
ImportExcel now provides enhanced support for working with Excel gradient fills through three new functions that address EPPlus 4.x limitations with gradient color reading.
## The Issue
In EPPlus 4.x (used by ImportExcel), while gradient colors can be set using `SetColor()`, they cannot be read back via properties like `Rgb`, `Top`, `Bottom`, `Left`, `Right`. This prevents copying gradients between cells.
## The Solution
Three new functions provide a complete gradient workflow:
### Set-ExcelGradientFill
Sets gradient fill properties for Excel ranges.
```powershell
# Basic linear gradient
Set-ExcelGradientFill -Range $ws.Cells["A1"] -Type Linear -Degree 90 -Color1 Blue -Color2 Green
# Path gradient
Set-ExcelGradientFill -Range $ws.Cells["B1"] -Type Path -Color1 Red -Color2 Yellow
# Advanced linear gradient with positioning
Set-ExcelGradientFill -Range $ws.Cells["C1"] -Type Linear -Degree 45 -Color1 Purple -Color2 Orange -Top 0.1 -Bottom 0.9
```
### Get-ExcelGradientFill
Reads gradient properties that can be reliably extracted from EPPlus.
```powershell
$gradient = Get-ExcelGradientFill -Range $ws.Cells["A1"]
# Returns: Type, Degree, Top, Bottom, Left, Right
# Note: Color1 and Color2 are null due to EPPlus limitations
```
### Copy-ExcelGradientFill
Copies gradient fills between ranges using two approaches:
#### Approach 1: Copy Geometric Properties Only
```powershell
# Copies Type, Degree, and positioning but warns about colors
Copy-ExcelGradientFill -SourceRange $ws.Cells["A1"] -TargetRange $ws.Cells["B1"]
```
#### Approach 2: Complete Gradient Definition
```powershell
# Create a complete gradient definition
$gradientDef = @{
Type = "Linear"
Degree = 45
Color1 = [System.Drawing.Color]::Blue
Color2 = [System.Drawing.Color]::Green
Top = 0.2
Bottom = 0.8
}
Copy-ExcelGradientFill -TargetRange $ws.Cells["C1:E3"] -GradientDefinition $gradientDef
```
## Complete Example
```powershell
Import-Module ImportExcel
$xl = Open-ExcelPackage -Path "gradients.xlsx" -Create
$ws = Add-WorkSheet -ExcelPackage $xl -WorksheetName "Gradients"
# Set original gradient
Set-ExcelGradientFill -Range $ws.Cells["A1"] -Type Linear -Degree 90 -Color1 Blue -Color2 Green
# Get gradient properties (geometric only)
$gradientInfo = Get-ExcelGradientFill -Range $ws.Cells["A1"]
# Create complete definition for copying
$completeDef = @{
Type = $gradientInfo.Type
Degree = $gradientInfo.Degree
Top = $gradientInfo.Top
Bottom = $gradientInfo.Bottom
Left = $gradientInfo.Left
Right = $gradientInfo.Right
Color1 = [System.Drawing.Color]::Blue # Must specify colors
Color2 = [System.Drawing.Color]::Green
}
# Copy to other cells
Copy-ExcelGradientFill -TargetRange $ws.Cells["B1:D3"] -GradientDefinition $completeDef
Close-ExcelPackage -ExcelPackage $xl -SaveAs "gradients.xlsx"
```
## Migration from Direct EPPlus Access
### Before (Limited by EPPlus 4.x):
```powershell
# Setting worked
$Sheet.Cells["A1"].Style.Fill.Gradient.Color1.SetColor("BLUE")
$Sheet.Cells["A1"].Style.Fill.Gradient.Color2.SetColor("GREEN")
# Reading failed - returned empty values
$rgb = $Sheet.Cells["A1"].Style.Fill.Gradient.Color1.Rgb # Empty!
```
### After (Using ImportExcel functions):
```powershell
# Setting is easier and more robust
Set-ExcelGradientFill -Range $Sheet.Cells["A1"] -Type Linear -Degree 90 -Color1 Blue -Color2 Green
# Reading works for geometric properties
$gradient = Get-ExcelGradientFill -Range $Sheet.Cells["A1"]
# $gradient.Type, $gradient.Degree work; colors need to be specified separately
# Copying now possible with complete definitions
$gradientDef = @{
Type = $gradient.Type
Degree = $gradient.Degree
Color1 = [System.Drawing.Color]::Blue
Color2 = [System.Drawing.Color]::Green
}
Copy-ExcelGradientFill -TargetRange $Sheet.Cells["B1"] -GradientDefinition $gradientDef
```
## Parameters Reference
### Set-ExcelGradientFill Parameters
- **Range**: Target Excel range (required)
- **Type**: "Linear" or "Path" (required)
- **Degree**: Angle for linear gradients (0-360, default 90)
- **Color1**: First color - Color object or string name (required)
- **Color2**: Second color - Color object or string name (required)
- **Top/Bottom/Left/Right**: Position values (0-1, optional)
### Copy-ExcelGradientFill Parameters
- **SourceRange**: Source range (for copying geometric properties)
- **TargetRange**: Target range (required)
- **GradientDefinition**: Hashtable with gradient properties (alternative to SourceRange)
## Limitations
- Color properties cannot be read from existing gradients due to EPPlus 4.x limitations
- When copying gradients, colors must be specified manually in the gradient definition
- Functions provide warnings when color limitations affect operations
## Benefits
- Clean, PowerShell-friendly interface for gradient operations
- Comprehensive parameter validation
- Clear error messages and warnings
- Supports both string color names and Color objects
- Enables gradient copying workflows that weren't possible before

View File

@@ -6,7 +6,7 @@
RootModule = 'ImportExcel.psm1'
# Version number of this module.
ModuleVersion = '7.8.4'
ModuleVersion = '7.8.10'
# ID used to uniquely identify this module
GUID = '60dd4136-feff-401a-ba27-a84458c57ede'
@@ -46,6 +46,7 @@ Check out the How To Videos https://www.youtube.com/watch?v=U3Ne_yX4tYo&list=PL5
'ConvertFrom-ExcelSheet',
'ConvertFrom-ExcelToSQLInsert',
'ConvertTo-ExcelXlsx',
'Copy-ExcelGradientFill',
'Copy-ExcelWorksheet',
'DoChart',
'Enable-ExcelAutoFilter',
@@ -54,7 +55,9 @@ Check out the How To Videos https://www.youtube.com/watch?v=U3Ne_yX4tYo&list=PL5
'Export-Excel',
'Export-ExcelSheet',
'Get-ExcelColumnName',
'Get-ExcelFileSchema',
'Get-ExcelFileSummary',
'Get-ExcelGradientFill',
'Get-ExcelSheetDimensionAddress',
'Get-ExcelSheetInfo',
'Get-ExcelWorkbookInfo',
@@ -91,6 +94,7 @@ Check out the How To Videos https://www.youtube.com/watch?v=U3Ne_yX4tYo&list=PL5
'Set-CellComment',
'Set-CellStyle',
'Set-ExcelColumn',
'Set-ExcelGradientFill',
'Set-ExcelRange',
'Set-ExcelRow',
'Set-WorksheetProtection',
@@ -122,8 +126,7 @@ Check out the How To Videos https://www.youtube.com/watch?v=U3Ne_yX4tYo&list=PL5
'.\GetExcelTable.ps1',
'.\ImportExcel.psd1',
'.\ImportExcel.psm1',
'.\LICENSE.txt',
'.\README.md',
'.\LICENSE.txt',
'.\Plot.ps1',
'.\Private',
'.\Public',
@@ -150,7 +153,7 @@ Check out the How To Videos https://www.youtube.com/watch?v=U3Ne_yX4tYo&list=PL5
Tags = @("Excel", "EPPlus", "Export", "Import")
# The web address of an icon which can be used in galleries to represent this module
#IconUri = "http://pesterbdd.com/images/Pester.png"
#IconUri =
# The web address of this module's project or support homepage.
ProjectUri = "https://github.com/dfinke/ImportExcel"
@@ -215,4 +218,4 @@ Check out the How To Videos https://www.youtube.com/watch?v=U3Ne_yX4tYo&list=PL5
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
}

View File

@@ -67,9 +67,8 @@ function WorksheetArgumentCompleter {
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
$xlPath = $fakeBoundParameter['Path']
if (Test-Path -Path $xlPath) {
$xlpkg = Open-ExcelPackage -ReadOnly -Path $xlPath
$WorksheetNames = $xlPkg.Workbook.Worksheets.Name
Close-ExcelPackage -nosave -ExcelPackage $xlpkg
$xlSheet = Get-ExcelSheetInfo -Path $xlPath
$WorksheetNames = $xlSheet.Name
$WorksheetNames.where( { $_ -like "*$wordToComplete*" }) | foreach-object {
New-Object -TypeName System.Management.Automation.CompletionResult -ArgumentList "'$_'",
$_ , ([System.Management.Automation.CompletionResultType]::ParameterValue) , $_

View File

@@ -8,7 +8,8 @@ function ConvertFrom-ExcelData {
[ScriptBlock]$ScriptBlock,
[Alias("Sheet")]
$WorksheetName = 1,
[int]$HeaderRow = 1,
[Alias('HeaderRow', 'TopRow')]
[int]$StartRow = 1,
[string[]]$Header,
[switch]$NoHeader,
[switch]$DataOnly

View File

@@ -45,11 +45,18 @@ function ConvertTo-ExcelXlsx {
throw "Could not create Excel.Application ComObject. Please verify that Excel is installed."
}
$Excel.Visible = $false
$null = $Excel.Workbooks.Open($xlsFile.FullName)
$Excel.ActiveWorkbook.SaveAs($xlsxPath, $xlFixedFormat)
$Excel.ActiveWorkbook.Close()
$Excel.Quit()
try {
$Excel.Visible = $false
$null = $Excel.Workbooks.Open($xlsFile.FullName, $null, $true)
$Excel.ActiveWorkbook.SaveAs($xlsxPath, $xlFixedFormat)
}
finally {
if ($null -ne $Excel.ActiveWorkbook) {
$Excel.ActiveWorkbook.Close()
}
$Excel.Quit()
}
}
}

View File

@@ -0,0 +1,119 @@
function Copy-ExcelGradientFill {
<#
.SYNOPSIS
Copies gradient fill settings from one Excel range to another.
.DESCRIPTION
Due to limitations in EPPlus 4.x, gradient color properties cannot be read directly.
This function provides a workaround by storing gradient definitions and applying them
to target ranges.
.PARAMETER SourceRange
The source range with the gradient fill to copy.
.PARAMETER TargetRange
The target range where the gradient fill should be applied.
.PARAMETER GradientDefinition
A hashtable containing gradient definition with properties:
- Type: Linear or Path
- Degree: Angle for linear gradients (0-360)
- Top, Bottom, Left, Right: Position values (0-1)
- Color1: First color (System.Drawing.Color or color name)
- Color2: Second color (System.Drawing.Color or color name)
.EXAMPLE
# Copy gradient from A1 to B1:C2
Copy-ExcelGradientFill -SourceRange $ws.Cells["A1"] -TargetRange $ws.Cells["B1:C2"]
.EXAMPLE
# Apply a custom gradient definition
$gradient = @{
Type = "Linear"
Degree = 45
Color1 = [System.Drawing.Color]::Blue
Color2 = [System.Drawing.Color]::Green
Top = 0
Bottom = 1
Left = 0
Right = 1
}
Copy-ExcelGradientFill -TargetRange $ws.Cells["A1:B2"] -GradientDefinition $gradient
.NOTES
This function works around EPPlus 4.x limitations with gradient color reading.
#>
[CmdletBinding(DefaultParameterSetName = 'CopyFromSource')]
param(
[Parameter(ParameterSetName = 'CopyFromSource', Mandatory)]
$SourceRange,
[Parameter(Mandatory)]
$TargetRange,
[Parameter(ParameterSetName = 'ApplyDefinition', Mandatory)]
[hashtable]$GradientDefinition
)
if ($PSCmdlet.ParameterSetName -eq 'CopyFromSource') {
# Extract gradient properties from source
$sourceGradient = $SourceRange.Style.Fill.Gradient
# Create gradient definition from source
$GradientDefinition = @{
Type = $sourceGradient.Type
Degree = $sourceGradient.Degree
Top = $sourceGradient.Top
Bottom = $sourceGradient.Bottom
Left = $sourceGradient.Left
Right = $sourceGradient.Right
}
# Note: Colors cannot be copied due to EPPlus limitation
Write-Warning "Gradient colors cannot be copied due to EPPlus 4.x limitations. Only geometric properties (Type, Degree, positioning) are copied. Please use Set-ExcelGradientFill to set colors."
}
# Apply gradient definition to target
$targetGradient = $TargetRange.Style.Fill.Gradient
if ($GradientDefinition.Type) {
$targetGradient.Type = [OfficeOpenXml.Style.ExcelFillGradientType]::$($GradientDefinition.Type)
}
if ($GradientDefinition.ContainsKey('Degree')) {
$targetGradient.Degree = $GradientDefinition.Degree
}
if ($GradientDefinition.ContainsKey('Top')) {
$targetGradient.Top = $GradientDefinition.Top
}
if ($GradientDefinition.ContainsKey('Bottom')) {
$targetGradient.Bottom = $GradientDefinition.Bottom
}
if ($GradientDefinition.ContainsKey('Left')) {
$targetGradient.Left = $GradientDefinition.Left
}
if ($GradientDefinition.ContainsKey('Right')) {
$targetGradient.Right = $GradientDefinition.Right
}
# Set colors if provided
if ($GradientDefinition.Color1) {
$color1 = $GradientDefinition.Color1
if ($color1 -is [string]) {
$color1 = [System.Drawing.Color]::$color1
}
$targetGradient.Color1.SetColor($color1)
}
if ($GradientDefinition.Color2) {
$color2 = $GradientDefinition.Color2
if ($color2 -is [string]) {
$color2 = [System.Drawing.Color]::$color2
}
$targetGradient.Color2.SetColor($color2)
}
}

View File

@@ -79,6 +79,7 @@
[Switch]$NoAliasOrScriptPropeties,
[Switch]$DisplayPropertySet,
[String[]]$NoNumberConversion,
[String[]]$NoHyperLinkConversion,
[Object[]]$ConditionalFormat,
[Object[]]$ConditionalText,
[Object[]]$Style,
@@ -318,7 +319,9 @@
$ws.Cells[$row, $ColumnIndex].Value = $v
if ($setNumformat) { $ws.Cells[$row, $ColumnIndex].Style.Numberformat.Format = $Numberformat }
}
elseif ($v -is [uri] ) {
elseif ($v -is [uri] -and
$NoHyperLinkConversion -ne '*' -and
$NoHyperLinkConversion -notcontains $Name ) {
$ws.Cells[$row, $ColumnIndex].HyperLink = $v
$ws.Cells[$row, $ColumnIndex].Style.Font.Color.SetColor([System.Drawing.Color]::Blue)
$ws.Cells[$row, $ColumnIndex].Style.Font.UnderLine = $true
@@ -331,7 +334,10 @@
$ws.Cells[$row, $ColumnIndex].Formula = ($v -replace '^=', '')
if ($setNumformat) { $ws.Cells[$row, $ColumnIndex].Style.Numberformat.Format = $Numberformat }
}
elseif ( [System.Uri]::IsWellFormedUriString($v , [System.UriKind]::Absolute) ) {
elseif ( $NoHyperLinkConversion -ne '*' -and # Put the check for 'NoHyperLinkConversion is null' first to skip checking for wellformedstring
$NoHyperLinkConversion -notcontains $Name -and
[System.Uri]::IsWellFormedUriString($v , [System.UriKind]::Absolute)
) {
if ($v -match "^xl://internal/") {
$referenceAddress = $v -replace "^xl://internal/" , ""
$display = $referenceAddress -replace "!A1$" , ""
@@ -344,8 +350,8 @@
}
else {
$number = $null
if ( $numberRegex.IsMatch($v) -and # if it contains digit(s) - this syntax is quicker than -match for many items and cuts out slow checks for non numbers
$NoNumberConversion -ne '*' -and # and NoNumberConversion isn't specified
if ( $NoNumberConversion -ne '*' -and # Check if NoNumberConversion isn't specified. Put this first as it's going to stop the if clause. Quicker than putting regex check first
$numberRegex.IsMatch($v) -and # and if it contains digit(s) - this syntax is quicker than -match for many items and cuts out slow checks for non numbers
$NoNumberConversion -notcontains $Name -and
[Double]::TryParse($v, [System.Globalization.NumberStyles]::Any, [System.Globalization.NumberFormatInfo]::CurrentInfo, [Ref]$number)
) {
@@ -494,7 +500,7 @@
}
elseif ($FreezeTopRow) {
if ($Title) {
$ws.View.FreezePanes(2, 1)
$ws.View.FreezePanes(3, 1)
Write-Verbose -Message "Froze title and header rows"
}
else {

View File

@@ -0,0 +1,47 @@
function Get-ExcelFileSchema {
<#
.SYNOPSIS
Gets the schema of an Excel file.
.DESCRIPTION
The Get-ExcelFileSchema function gets the schema of an Excel file by returning the property names of the first row of each worksheet in the file.
.PARAMETER Path
Specifies the path to the Excel file.
.PARAMETER Compress
Indicates whether to compress the json output.
.OUTPUTS
Json
.EXAMPLE
Get-ExcelFileSchema -Path .\example.xlsx
#>
[CmdletBinding()]
param(
[Parameter(ValueFromPipelineByPropertyName, Mandatory)]
[Alias('FullName')]
$Path,
[Switch]$Compress
)
Begin {
$result = @()
}
Process {
$excelFiles = Get-ExcelFileSummary $Path
foreach ($excelFile in $excelFiles) {
$data = Import-Excel $Path -WorksheetName $excelFile.WorksheetName | Select-Object -First 1
$names = $data[0].PSObject.Properties.name
$result += $excelFile | Add-Member -MemberType NoteProperty -Name "PropertyNames" -Value $names -PassThru
}
}
End {
$result | ConvertTo-Json -Compress:$Compress
}
}

View File

@@ -0,0 +1,52 @@
function Get-ExcelGradientFill {
<#
.SYNOPSIS
Gets gradient fill properties from an Excel range.
.DESCRIPTION
This function extracts gradient fill properties that can be reliably read from EPPlus 4.x.
Due to EPPlus limitations, gradient colors cannot be read directly and will return $null.
.PARAMETER Range
The Excel range to read gradient properties from.
.EXAMPLE
# Get gradient properties from a cell
$gradientInfo = Get-ExcelGradientFill -Range $ws.Cells["A1"]
.EXAMPLE
# Get gradient properties and use them to copy to another range
$gradient = Get-ExcelGradientFill -Range $ws.Cells["A1"]
# Add colors since they can't be read
$gradient.Color1 = [System.Drawing.Color]::Blue
$gradient.Color2 = [System.Drawing.Color]::Green
Copy-ExcelGradientFill -TargetRange $ws.Cells["B1:C2"] -GradientDefinition $gradient
.NOTES
Due to EPPlus 4.x limitations, Color1 and Color2 properties will always be $null.
This function is provided for completeness and to work with Copy-ExcelGradientFill.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
$Range
)
process {
$gradient = $Range.Style.Fill.Gradient
$result = [PSCustomObject]@{
Type = $gradient.Type
Degree = $gradient.Degree
Top = $gradient.Top
Bottom = $gradient.Bottom
Left = $gradient.Left
Right = $gradient.Right
Color1 = $null # Cannot be read due to EPPlus 4.x limitation
Color2 = $null # Cannot be read due to EPPlus 4.x limitation
ColorLimitationNote = "Color properties cannot be read due to EPPlus 4.x limitations"
}
return $result
}
}

View File

@@ -50,7 +50,9 @@ function Get-HtmlTable {
else {
$h = ConvertFrom-Html -Content $r.Content
if ($TableIndex -is [valuetype]) { $TableIndex += 1}
$rows = $h.SelectNodes("//table[$TableIndex]//tr")
$rows = try {
$h.SelectSingleNode("//table[$TableIndex]").SelectNodes(".//tr")
} catch {}
if (-not $rows) {Write-Warning "Could not find rows for `"//table[$TableIndex]`" in $Url ."}
if ( -not $propertyNames) {
if ( $tableHeaders = $rows[$FirstDataRow].SelectNodes("th")) {

View File

@@ -0,0 +1,128 @@
function Set-ExcelGradientFill {
<#
.SYNOPSIS
Sets gradient fill properties for an Excel range.
.DESCRIPTION
This function provides a complete interface for setting gradient fills in Excel ranges,
working around EPPlus 4.x limitations with gradient handling.
.PARAMETER Range
The Excel range to apply the gradient fill to.
.PARAMETER Type
The gradient type: Linear or Path.
.PARAMETER Degree
The angle for linear gradients (0-360 degrees).
.PARAMETER Color1
The first gradient color. Can be a System.Drawing.Color object or color name.
.PARAMETER Color2
The second gradient color. Can be a System.Drawing.Color object or color name.
.PARAMETER Top
The top position for gradient positioning (0-1).
.PARAMETER Bottom
The bottom position for gradient positioning (0-1).
.PARAMETER Left
The left position for gradient positioning (0-1).
.PARAMETER Right
The right position for gradient positioning (0-1).
.EXAMPLE
# Set a simple blue to green linear gradient
Set-ExcelGradientFill -Range $ws.Cells["A1"] -Type Linear -Degree 90 -Color1 Blue -Color2 Green
.EXAMPLE
# Set a more complex gradient with positioning
Set-ExcelGradientFill -Range $ws.Cells["A1:C3"] -Type Linear -Degree 45 -Color1 ([System.Drawing.Color]::Red) -Color2 ([System.Drawing.Color]::Yellow) -Top 0.2 -Bottom 0.8
.EXAMPLE
# Set a path gradient
Set-ExcelGradientFill -Range $ws.Cells["B1:D3"] -Type Path -Color1 "DarkBlue" -Color2 "LightBlue"
.NOTES
This function addresses the EPPlus 4.x limitation where gradient colors cannot be read back.
#>
[CmdletBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Does not change system state')]
param(
[Parameter(Mandatory, ValueFromPipeline)]
$Range,
[Parameter(Mandatory)]
[ValidateSet('Linear', 'Path')]
[string]$Type,
[Parameter()]
[ValidateRange(0, 360)]
[double]$Degree = 90,
[Parameter(Mandatory)]
$Color1,
[Parameter(Mandatory)]
$Color2,
[Parameter()]
[ValidateRange(0, 1)]
[double]$Top,
[Parameter()]
[ValidateRange(0, 1)]
[double]$Bottom,
[Parameter()]
[ValidateRange(0, 1)]
[double]$Left,
[Parameter()]
[ValidateRange(0, 1)]
[double]$Right
)
process {
$gradient = $Range.Style.Fill.Gradient
# Set gradient type
$gradient.Type = [OfficeOpenXml.Style.ExcelFillGradientType]::$Type
# Set degree for linear gradients
if ($Type -eq 'Linear') {
$gradient.Degree = $Degree
}
# Set positioning if provided
if ($PSBoundParameters.ContainsKey('Top')) {
$gradient.Top = $Top
}
if ($PSBoundParameters.ContainsKey('Bottom')) {
$gradient.Bottom = $Bottom
}
if ($PSBoundParameters.ContainsKey('Left')) {
$gradient.Left = $Left
}
if ($PSBoundParameters.ContainsKey('Right')) {
$gradient.Right = $Right
}
# Set colors
if ($Color1 -is [string]) {
$Color1 = [System.Drawing.Color]::$Color1
}
$gradient.Color1.SetColor($Color1)
if ($Color2 -is [string]) {
$Color2 = [System.Drawing.Color]::$Color2
}
$gradient.Color2.SetColor($Color2)
}
}

View File

@@ -4,15 +4,33 @@
<br/>
[![Build Status](https://dougfinke.visualstudio.com/ImportExcel/_apis/build/status/dfinke.ImportExcel?branchName=master)](https://dougfinke.visualstudio.com/ImportExcel/_build)
Has the ImportExcel module helped you?
- Made you look good to the boss?
- Saved you time?
- Made you more productive?
Consider donating. Thank you!
<br>
<br>
<a href="https://www.paypal.com/paypalme/DougCharlesFinke"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate" height="28"></a>
<br>
<br>
![](https://media.giphy.com/media/hpXxJ78YtpT0s/giphy.gif)
<br>
<br>
[![Follow on Twitter](https://img.shields.io/twitter/follow/dfinke.svg?style=social&label=Follow%20%40dfinke)](https://twitter.com/dfinke)
[![Subscribe on YouTube](https://img.shields.io/youtube/channel/subscribers/UCP47ZkO5EDkoI2sr-3P4ShQ
)](https://youtube.com/@dougfinke/)
<br/>
<br/>
[![](https://img.shields.io/powershellgallery/v/ImportExcel.svg)](https://www.powershellgallery.com/packages/ImportExcel)
[![](https://img.shields.io/powershellgallery/dt/ImportExcel.svg)](https://www.powershellgallery.com/packages/ImportExcel)
[![](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/dfinke/ImportExcel/tree/70ab9e46c776e96fb287682d5b9b4b51a0ec3bac/LICENSE.txt)
<a href="https://www.paypal.com/paypalme/DougCharlesFinke"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
# Overview
Automate Excel with PowerShell without having Excel installed. Works on Windows, Linux and MAC. Creating Tables, Pivot Tables, Charts and much more just got a lot easier.
Automate Excel with PowerShell without having Excel installed. Works on Windows, Linux and Mac. Creating Tables, Pivot Tables, Charts and much more just got a lot easier.
## Examples ✨
Check out the [more than 100 examples](Examples/) on ways to create amazing reports as well as make you more productive with PowerShell and Excel.
@@ -173,4 +191,14 @@ ForEach-Object { $_.Value | Export-Csv ($_.key + '.csv') }
## Contributing
Contributions are welcome! Open a pull request to fix a bug, or open an issue to discuss a new feature or change.
## New Features
### Gradient Fill Support
ImportExcel now includes enhanced gradient fill support with three new functions:
- `Set-ExcelGradientFill` - Apply gradient fills to Excel ranges
- `Get-ExcelGradientFill` - Read gradient properties from ranges
- `Copy-ExcelGradientFill` - Copy gradients between ranges
These functions work around EPPlus 4.x limitations with gradient color reading. See [GRADIENT_GUIDE.md](GRADIENT_GUIDE.md) for detailed usage examples.
Original [README.md](./README.original.md)

View File

@@ -205,7 +205,7 @@ Describe ExportExcel -Tag "ExportExcel" {
else { $OtherCurrencySymbol = "£" }
$path = "TestDrive:\test.xlsx"
$warnVar = $null
#Test correct export of different data types and number formats; test hyperlinks, test -NoNumberConversion test object is converted to a string with no warnings, test calcuation of formula
#Test correct export of different data types and number formats; test hyperlinks, test -NoNumberConversion and -NoHyperLinkConversion test object is converted to a string with no warnings, test calcuation of formula
Remove-item -Path $path -ErrorAction SilentlyContinue
[PSCustOmobject][Ordered]@{
Date = Get-Date
@@ -233,9 +233,10 @@ Describe ExportExcel -Tag "ExportExcel" {
Link3 = "xl://internal/sheet1!A1"
Link4 = "xl://internal/sheet1!C5"
Link5 = (New-Object -TypeName OfficeOpenXml.ExcelHyperLink -ArgumentList "Sheet1!E2" , "Display Text")
Link6 = "xl://internal/sheet1!C5"
Process = (Get-Process -Id $PID)
TimeSpan = [datetime]::Now.Subtract([datetime]::Today)
} | Export-Excel -NoNumberConversion IPAddress, StrLeadZero, StrAltPhone2 -Path $path -Calculate -WarningVariable $warnVar
} | Export-Excel -NoNumberConversion IPAddress, StrLeadZero, StrAltPhone2 -NoHyperLinkConversion Link6 -Path $path -Calculate -WarningVariable $warnVar
}
BeforeEach {
@@ -253,7 +254,7 @@ Describe ExportExcel -Tag "ExportExcel" {
}
it "Created the worksheet with the expected name, number of rows and number of columns " {
$ws.Name | Should -Be "sheet1"
$ws.Dimension.Columns | Should -Be 27
$ws.Dimension.Columns | Should -Be 28
$ws.Dimension.Rows | Should -Be 2
}
it "Set a date in Cell A2 " {
@@ -292,6 +293,9 @@ Describe ExportExcel -Tag "ExportExcel" {
$ws.Cells[2, 25].Hyperlink.ReferenceAddress | Should -Be "sheet1!E2"
$ws.Cells[2, 25].Hyperlink.Display | Should -Be "Display Text"
}
it "Create no link in cell Z2 (no hyperlink conversion) " {
$ws.Cells[2, 26].Hyperlink | Should -BeNullOrEmpty
}
it "Processed thousands according to local settings (Cells H2 and I2) " {
if ((Get-Culture).NumberFormat.NumberGroupSeparator -EQ ",") {
($ws.Cells[2, 8].Value -is [valuetype] ) | Should -Be $true
@@ -314,13 +318,13 @@ Describe ExportExcel -Tag "ExportExcel" {
($ws.Cells[2, 19].Value -is [valuetype] ) | Should -Be $true
($ws.Cells[2, 20].Value -is [valuetype] ) | Should -Be $true
}
it "Converted a nested object to a string (Y2) " {
$ws.Cells[2, 26].Value | Should -Match '^System\.Diagnostics\.Process\s+\(.*\)$'
it "Converted a nested object to a string (AA2) " {
$ws.Cells[2, 27].Value | Should -Match '^System\.Diagnostics\.Process\s+\(.*\)$'
}
it "Processed a timespan object (Z2) " {
$ws.cells[2, 27].Value.ToOADate() | Should -BeGreaterThan 0
$ws.cells[2, 27].Value.ToOADate() | Should -BeLessThan 1
$ws.cells[2, 27].Style.Numberformat.Format | Should -Be '[h]:mm:ss'
it "Processed a timespan object (AB2) " {
$ws.cells[2, 28].Value.ToOADate() | Should -BeGreaterThan 0
$ws.cells[2, 28].Value.ToOADate() | Should -BeLessThan 1
$ws.cells[2, 28].Style.Numberformat.Format | Should -Be '[h]:mm:ss'
}
}
@@ -344,8 +348,9 @@ Describe ExportExcel -Tag "ExportExcel" {
PhoneNr1 = '+32 44'
PhoneNr2 = '+32 4 4444 444'
PhoneNr3 = '+3244444444'
Link = [uri]"https://github.com/dfinke/ImportExcel"
} | Export-Excel -NoNumberConversion IPAddress, Number1 -Path $path -NoHeader
Link1 = [uri]"https://github.com/dfinke/ImportExcel"
Link2 = [uri]"https://github.com/dfinke/ImportExcel"
} | Export-Excel -NoHyperLinkConversion Link2 -NoNumberConversion IPAddress, Number1 -Path $path -NoHeader
}
BeforeEach {
@@ -363,7 +368,7 @@ Describe ExportExcel -Tag "ExportExcel" {
it "Created the worksheet with the expected name, number of rows and number of columns " {
$ws.Name | Should -Be "sheet1"
$ws.Dimension.Columns | Should -Be 14
$ws.Dimension.Columns | Should -Be 15
$ws.Dimension.Rows | Should -Be 1
}
@@ -387,6 +392,10 @@ Describe ExportExcel -Tag "ExportExcel" {
it "Set a hyperlink in Cell N1 " {
$ws.Cells[1, 14].Hyperlink | Should -Be "https://github.com/dfinke/ImportExcel"
}
it "Does not set a hyperlink in Cell O1 " {
$ws.Cells[1, 15].Hyperlink | Should -BeNullOrEmpty
}
}
Context "#Example 5 # Adding a single conditional format " {
@@ -693,11 +702,11 @@ Describe ExportExcel -Tag "ExportExcel" {
# Export as table with a totals row with a set of possibilities
$TableTotalSettings = @{
Id = "COUNT"
WS = "SUM"
Handles = "AVERAGE"
CPU = '=COUNTIF([CPU];"<1")'
NPM = @{
Id = "COUNT"
WS = "SUM"
Handles = "AVERAGE"
CPU = '=COUNTIF([CPU];"<1")'
NPM = @{
Function = '=SUMIF([Name];"=Chrome";[NPM])'
Comment = "Sum of Non-Paged Memory (NPM) for all chrome processes"
}
@@ -761,7 +770,13 @@ Describe ExportExcel -Tag "ExportExcel" {
$path = "TestDrive:\test.xlsx"
Remove-Item $path -ErrorAction SilentlyContinue
#Test freezing top row/first column, adding formats and a pivot table - from Add-Pivot table not a specification variable - after the export
$excel = Get-Process | Select-Object -Property Name, Company, Handles, CPU, PM, NPM, WS | Export-Excel -Path $path -ClearSheet -WorkSheetname "Processes" -FreezeTopRowFirstColumn -PassThru
$Ex13Data = Get-Process | Select-Object -Property Name, Company, Handles, CPU, PM, NPM, WS
$excel = $Ex13Data | Export-Excel -Path $path -ClearSheet -WorkSheetname "Processes" -FreezeTopRowFirstColumn -PassThru
# Add extra worksheets for testing 'Freeze Top Row' and 'Freeze First Column' with or without title
$excel = Export-Excel -InputObject $Ex13Data -ExcelPackage $excel -WorksheetName "FreezeTopRow" -FreezeTopRow -Passthru
$excel = Export-Excel -InputObject $Ex13Data -ExcelPackage $excel -WorksheetName "FreezeFirstColumn" -FreezeFirstColumn -Passthru
$excel = Export-Excel -InputObject $Ex13Data -Title "Freeze Top Row" -ExcelPackage $excel -WorksheetName "FreezeTopRowTitle" -FreezeTopRow -Passthru
$excel = Export-Excel -InputObject $Ex13Data -Title "Freeze Top Row First Column" -ExcelPackage $excel -WorksheetName "FreezeTRFCTitle" -FreezeTopRowFirstColumn -Passthru
$sheet = $excel.Workbook.Worksheets["Processes"]
if ($isWindows) { $sheet.Column(1) | Set-ExcelRange -Bold -AutoFit }
else { $sheet.Column(1) | Set-ExcelRange -Bold }
@@ -781,6 +796,10 @@ Describe ExportExcel -Tag "ExportExcel" {
$excel = Open-ExcelPackage $path
$sheet = $excel.Workbook.Worksheets["Processes"]
$sheetftr = $excel.Workbook.Worksheets["FreezeTopRow"]
$sheetffc = $excel.Workbook.Worksheets["FreezeFirstColumn"]
$sheetftrt = $excel.Workbook.Worksheets["FreezeTopRowTitle"]
$sheetftrfct = $excel.Workbook.Worksheets["FreezeTRFCTitle"]
}
it "Returned the rule when calling Add-ConditionalFormatting -passthru " {
$rule | Should -Not -BeNullOrEmpty
@@ -827,7 +846,27 @@ Describe ExportExcel -Tag "ExportExcel" {
$sheet.Names[6].Name | Should -Be $sheet.Cells['G1'].Value
}
it "Froze the panes " {
$sheet.view.Panes.Count | Should -Be 3
$sheetPaneInfo = $sheet.worksheetxml.worksheet.sheetViews.sheetView.pane
$sheetftrPaneInfo = $sheetftr.worksheetxml.worksheet.sheetViews.sheetView.pane
$sheetffcPaneInfo = $sheetffc.worksheetxml.worksheet.sheetViews.sheetView.pane
$sheetftrtPaneInfo = $sheetftrt.worksheetxml.worksheet.sheetViews.sheetView.pane
$sheetftrfctPaneInfo = $sheetftrfct.worksheetxml.worksheet.sheetViews.sheetView.pane
$sheet.view.Panes.Count | Should -Be 3 # Don't know if this actually checks anything
$sheetPaneInfo.xSplit | Should -Be 1
$sheetPaneInfo.ySplit | Should -Be 1
$sheetPaneInfo.topLeftCell | Should -Be "B2"
$sheetftrPaneInfo.xSplit | Should -BeNullOrEmpty
$sheetftrPaneInfo.ySplit | Should -Be 1
$sheetftrPaneInfo.topLeftCell | Should -Be "A2"
$sheetffcPaneInfo.xSplit | Should -Be 1
$sheetffcPaneInfo.ySplit | Should -BeNullOrEmpty
$sheetffcPaneInfo.topLeftCell | Should -Be "B1"
$sheetftrtPaneInfo.xSplit | Should -BeNullOrEmpty
$sheetftrtPaneInfo.ySplit | Should -Be 2
$sheetftrtPaneInfo.topLeftCell | Should -Be "A3"
$sheetftrfctPaneInfo.xSplit | Should -Be 1
$sheetftrfctPaneInfo.ySplit | Should -Be 2
$sheetftrfctPaneInfo.topLeftCell | Should -Be "B3"
}
it "Created the pivot table " {
@@ -1198,4 +1237,120 @@ Describe ExportExcel -Tag "ExportExcel" {
Close-ExcelPackage $excel -NoSave
}
}
It "Should have hyperlink created" -Tag hyperlink {
$path = "TestDrive:\testHyperLink.xlsx"
$license = "cognc:MCOMEETADV_GOV,cognc:M365_G3_GOV,cognc:ENTERPRISEPACK_GOV,cognc:RIGHTSMANAGEMENT_ADHOC"
$ms365 = [PSCustomObject]@{
DisplayName = "Test Subject"
UserPrincipalName = "test@contoso.com"
licenses = $license
}
$ms365 | Export-Excel $path
$excel = Open-ExcelPackage $Path
$ws = $excel.Sheet1
$ws.Dimension.Rows | Should -Be 2
$ws.Dimension.Columns | Should -Be 3
$ws.Cells["C2"].Hyperlink | Should -BeExactly $license
Close-ExcelPackage $excel
Remove-Item $path
}
It "Should have no hyperlink created" -Tag hyperlink {
$path = "TestDrive:\testHyperLink.xlsx"
$license = "cognc:MCOMEETADV_GOV,cognc:M365_G3_GOV,cognc:ENTERPRISEPACK_GOV,cognc:RIGHTSMANAGEMENT_ADHOC"
$ms365 = [PSCustomObject]@{
DisplayName = "Test Subject"
UserPrincipalName = "test@contoso.com"
licenses = $license
}
$ms365 | Export-Excel $path -NoHyperLinkConversion licenses
$excel = Open-ExcelPackage $Path
$ws = $excel.Sheet1
$ws.Dimension.Rows | Should -Be 2
$ws.Dimension.Columns | Should -Be 3
$ws.Cells["C2"].Hyperlink | Should -BeNullOrEmpty
Close-ExcelPackage $excel
Remove-Item $path
}
It "Should have no hyperlink created using wild card" -Tag hyperlink {
$path = "TestDrive:\testHyperLink.xlsx"
$license = "cognc:MCOMEETADV_GOV,cognc:M365_G3_GOV,cognc:ENTERPRISEPACK_GOV,cognc:RIGHTSMANAGEMENT_ADHOC"
$ms365 = [PSCustomObject]@{
DisplayName = "Test Subject"
UserPrincipalName = "test@contoso.com"
licenses = $license
}
$ms365 | Export-Excel $path -NoHyperLinkConversion *
$excel = Open-ExcelPackage $Path
$ws = $excel.Sheet1
$ws.Dimension.Rows | Should -Be 2
$ws.Dimension.Columns | Should -Be 3
$ws.Cells["A2"].Value | Should -BeExactly "Test Subject"
$ws.Cells["B2"].Value | Should -BeExactly "test@contoso.com"
$ws.Cells["C2"].Hyperlink | Should -BeNullOrEmpty
Close-ExcelPackage $excel
Remove-Item $path
}
It "Should freeze the correct rows" -tag Freeze {
<#
Export-Excel -InputObject $Data -Path $OutputFile -TableName $SheetName.Replace(' ', '_') -WorksheetName $SheetName -AutoSize -FreezeTopRow -TableStyle $TableStyle -Title $SheetName -TitleBold -TitleSize 18
#>
$path = "TestDrive:\testFreeze.xlsx"
$data = ConvertFrom-Csv @"
Region,State,Units,Price
West,Texas,927,923.71
North,Tennessee,466,770.67
East,Florida,520,458.68
East,Maine,828,661.24
West,Virginia,465,053.58
North,Missouri,436,235.67
South,Kansas,214,992.47
North,North Dakota,789,640.72
South,Delaware,712,508.55
"@
Export-Excel -InputObject $data -Path $path -TableName 'TestTable' -WorksheetName 'TestSheet' -AutoSize -TableStyle Medium2 -Title 'Test Title' -TitleBold -TitleSize 18 -FreezeTopRow
$excel = Open-ExcelPackage -Path $path
$ws = $excel.TestSheet
$r = $ws.worksheetxml.worksheet.sheetViews.sheetView.pane
$r | Should -Not -BeNullOrEmpty
$r.ySplit | Should -Be 2
$r.topLeftCell | Should -BeExactly 'A3'
$r.state | Should -BeExactly 'frozen'
$r.activePane | Should -BeExactly 'bottomLeft'
Close-ExcelPackage $excel
Remove-Item $path
}
}

View File

@@ -0,0 +1,54 @@
if (-not (Get-command Import-Excel -ErrorAction SilentlyContinue)) {
Import-Module $PSScriptRoot\..\ImportExcel.psd1
}
Describe "Test getting the schema of an Excel file" -Tag GetExcelFileSchema {
BeforeAll {
$script:excelFile = "TestDrive:\test.xlsx"
$data = ConvertFrom-Csv @"
Region,State,Units,Price
West,Texas,927,923.71
North,Tennessee,466,770.67
East,Florida,520,458.68
East,Maine,828,661.24
West,Virginia,465,053.58
North,Missouri,436,235.67
South,Kansas,214,992.47
North,North Dakota,789,640.72
South,Delaware,712,508.55
"@
$data | Export-Excel $excelFile
}
It "Test Get-ExcelFileSchema function exists" {
$function = Get-Command Get-ExcelFileSchema -ErrorAction SilentlyContinue
$function | Should -Not -Be $null
}
It "Test Get-ExcelFileSchema returns json" {
$actual = Get-ExcelFileSchema -Path $excelFile
$actual | Should -Not -Be $null
$actual | Should -BeOfType [string]
}
It "Test Get-ExcelFileSchema correct json" {
$actual = Get-ExcelFileSchema -Path $excelFile
$actual = $actual | ConvertFrom-Json
$actual.ExcelFile | Should -BeExactly "test.xlsx"
$actual.WorksheetName | Should -BeExactly "Sheet1"
$actual.Visible | Should -Be $true
$actual.Rows | Should -Be 10
$actual.Columns | Should -Be 4
$actual.Address | Should -BeExactly "A1:D10"
$actual.Path | Should -BeExactly ("TestDrive:" + [System.IO.Path]::DirectorySeparatorChar)
$actual.PropertyNames.Count | Should -Be 4
$actual.PropertyNames[0] | Should -BeExactly "Region"
$actual.PropertyNames[1] | Should -BeExactly "State"
$actual.PropertyNames[2] | Should -BeExactly "Units"
$actual.PropertyNames[3] | Should -BeExactly "Price"
}
}

View File

@@ -0,0 +1,219 @@
Describe "Gradient Fill Functions" {
BeforeAll {
$script:testPath = "TestDrive:\GradientTest.xlsx"
Remove-Item -Path $testPath -ErrorAction SilentlyContinue
# Import the module
Import-Module "$PSScriptRoot\..\ImportExcel.psd1" -Force
}
Context "Set-ExcelGradientFill Tests" {
BeforeEach {
$script:xl = Open-ExcelPackage -Path $testPath -Create
$script:ws = Add-WorkSheet -ExcelPackage $xl -WorksheetName "GradientTest"
}
AfterEach {
Close-ExcelPackage -ExcelPackage $xl
Remove-Item -Path $testPath -ErrorAction SilentlyContinue
}
It "Should set linear gradient with basic properties" {
Set-ExcelGradientFill -Range $ws.Cells["A1"] -Type Linear -Degree 90 -Color1 Blue -Color2 Green
$gradient = $ws.Cells["A1"].Style.Fill.Gradient
$gradient.Type | Should -Be "Linear"
$gradient.Degree | Should -Be 90
}
It "Should set path gradient" {
Set-ExcelGradientFill -Range $ws.Cells["A1"] -Type Path -Color1 Red -Color2 Yellow
$gradient = $ws.Cells["A1"].Style.Fill.Gradient
$gradient.Type | Should -Be "Path"
}
It "Should set linear gradient with positioning" {
Set-ExcelGradientFill -Range $ws.Cells["A1"] -Type Linear -Degree 45 -Color1 Blue -Color2 Green -Top 0.2 -Bottom 0.8 -Left 0.1 -Right 0.9
$gradient = $ws.Cells["A1"].Style.Fill.Gradient
$gradient.Type | Should -Be "Linear"
$gradient.Degree | Should -Be 45
$gradient.Top | Should -Be 0.2
$gradient.Bottom | Should -Be 0.8
$gradient.Left | Should -Be 0.1
$gradient.Right | Should -Be 0.9
}
It "Should accept System.Drawing.Color objects" {
Set-ExcelGradientFill -Range $ws.Cells["A1"] -Type Linear -Color1 ([System.Drawing.Color]::Red) -Color2 ([System.Drawing.Color]::Blue)
$gradient = $ws.Cells["A1"].Style.Fill.Gradient
$gradient.Type | Should -Be "Linear"
}
It "Should work with ranges" {
Set-ExcelGradientFill -Range $ws.Cells["A1:C3"] -Type Linear -Degree 135 -Color1 "Purple" -Color2 "Orange"
$gradient = $ws.Cells["A1"].Style.Fill.Gradient
$gradient.Type | Should -Be "Linear"
$gradient.Degree | Should -Be 135
}
It "Should validate degree range" {
{ Set-ExcelGradientFill -Range $ws.Cells["A1"] -Type Linear -Degree 400 -Color1 Blue -Color2 Green } | Should -Throw
}
It "Should validate position range" {
{ Set-ExcelGradientFill -Range $ws.Cells["A1"] -Type Linear -Color1 Blue -Color2 Green -Top 1.5 } | Should -Throw
}
}
Context "Get-ExcelGradientFill Tests" {
BeforeEach {
$script:xl = Open-ExcelPackage -Path $testPath -Create
$script:ws = Add-WorkSheet -ExcelPackage $xl -WorksheetName "GradientTest"
}
AfterEach {
Close-ExcelPackage -ExcelPackage $xl
Remove-Item -Path $testPath -ErrorAction SilentlyContinue
}
It "Should get gradient properties that can be read" {
# Set a gradient first
Set-ExcelGradientFill -Range $ws.Cells["A1"] -Type Linear -Degree 45 -Color1 Blue -Color2 Green -Top 0.3 -Bottom 0.7
$result = Get-ExcelGradientFill -Range $ws.Cells["A1"]
$result.Type | Should -Be "Linear"
$result.Degree | Should -Be 45
$result.Top | Should -Be 0.3
$result.Bottom | Should -Be 0.7
$result.Color1 | Should -BeNullOrEmpty
$result.Color2 | Should -BeNullOrEmpty
$result.ColorLimitationNote | Should -Match "EPPlus.*limitation"
}
It "Should return PSCustomObject with expected properties" {
Set-ExcelGradientFill -Range $ws.Cells["A1"] -Type Path -Color1 Red -Color2 Blue
$result = Get-ExcelGradientFill -Range $ws.Cells["A1"]
$result | Should -BeOfType [PSCustomObject]
$result.PSObject.Properties.Name | Should -Contain "Type"
$result.PSObject.Properties.Name | Should -Contain "Degree"
$result.PSObject.Properties.Name | Should -Contain "Color1"
$result.PSObject.Properties.Name | Should -Contain "Color2"
$result.PSObject.Properties.Name | Should -Contain "ColorLimitationNote"
}
}
Context "Copy-ExcelGradientFill Tests" {
BeforeEach {
$script:xl = Open-ExcelPackage -Path $testPath -Create
$script:ws = Add-WorkSheet -ExcelPackage $xl -WorksheetName "GradientTest"
}
AfterEach {
Close-ExcelPackage -ExcelPackage $xl
Remove-Item -Path $testPath -ErrorAction SilentlyContinue
}
It "Should copy gradient geometric properties from source to target" {
# Set up source
Set-ExcelGradientFill -Range $ws.Cells["A1"] -Type Linear -Degree 60 -Color1 Blue -Color2 Green -Top 0.1 -Bottom 0.9
# Copy to target (should warn about colors)
$warningVar = $null
Copy-ExcelGradientFill -SourceRange $ws.Cells["A1"] -TargetRange $ws.Cells["B1"] -WarningVariable warningVar
$targetGradient = $ws.Cells["B1"].Style.Fill.Gradient
$targetGradient.Type | Should -Be "Linear"
$targetGradient.Degree | Should -Be 60
$targetGradient.Top | Should -Be 0.1
$targetGradient.Bottom | Should -Be 0.9
$warningVar | Should -Match "cannot be copied"
}
It "Should apply custom gradient definition" {
$gradientDef = @{
Type = "Linear"
Degree = 120
Color1 = [System.Drawing.Color]::Purple
Color2 = [System.Drawing.Color]::Yellow
Top = 0.25
Bottom = 0.75
Left = 0.2
Right = 0.8
}
Copy-ExcelGradientFill -TargetRange $ws.Cells["C1"] -GradientDefinition $gradientDef
$gradient = $ws.Cells["C1"].Style.Fill.Gradient
$gradient.Type | Should -Be "Linear"
$gradient.Degree | Should -Be 120
$gradient.Top | Should -Be 0.25
$gradient.Bottom | Should -Be 0.75
$gradient.Left | Should -Be 0.2
$gradient.Right | Should -Be 0.8
}
It "Should apply gradient definition with string colors" {
$gradientDef = @{
Type = "Path"
Color1 = "Red"
Color2 = "Blue"
}
Copy-ExcelGradientFill -TargetRange $ws.Cells["D1:F3"] -GradientDefinition $gradientDef
$gradient = $ws.Cells["D1"].Style.Fill.Gradient
$gradient.Type | Should -Be "Path"
}
}
Context "Integration Tests" {
BeforeEach {
$script:xl = Open-ExcelPackage -Path $testPath -Create
$script:ws = Add-WorkSheet -ExcelPackage $xl -WorksheetName "GradientTest"
}
AfterEach {
Close-ExcelPackage -ExcelPackage $xl
Remove-Item -Path $testPath -ErrorAction SilentlyContinue
}
It "Should work with the original issue scenario" {
# Reproduce the original issue scenario but with new functions
Set-ExcelGradientFill -Range $ws.Cells["A1"] -Type Linear -Degree 90 -Color1 Blue -Color2 Green
# Get gradient info (which will have limited color info)
$gradientInfo = Get-ExcelGradientFill -Range $ws.Cells["A1"]
# Should be able to copy geometric properties
$gradientInfo.Type | Should -Be "Linear"
$gradientInfo.Degree | Should -Be 90
# Should be able to create a complete gradient definition for copying
$completeDef = @{
Type = $gradientInfo.Type
Degree = $gradientInfo.Degree
Top = $gradientInfo.Top
Bottom = $gradientInfo.Bottom
Left = $gradientInfo.Left
Right = $gradientInfo.Right
Color1 = [System.Drawing.Color]::Blue # Need to specify colors
Color2 = [System.Drawing.Color]::Green
}
# Should be able to copy to another cell
Copy-ExcelGradientFill -TargetRange $ws.Cells["B1"] -GradientDefinition $completeDef
$targetGradient = $ws.Cells["B1"].Style.Fill.Gradient
$targetGradient.Type | Should -Be "Linear"
$targetGradient.Degree | Should -Be 90
}
}
}

View File

@@ -1,6 +1,69 @@
# 7.8.3
# 7.8.10
- Thank you https://github.com/evenmartinsen for the PR to fix the AV
# 7.8.9
- Thanks to (Edward Miller)[https://github.com/edwardmiller-mesirow] for improving `ConvertTo-ExcelXlsx`and making it more robust
# 7.8.8
- Fix the release
# 7.8.7
- Thanks to [Phil Bossman](https://github.com/pbossman) for the PR and fixing this.
Now, back again, you can type `Import-Excel .\yearlySales.xlsx`, press <ctrl+space> and get a list of the worksheets in the Excel file
![alt text](images/AutoCompleteSheetNames.png)
# Infrastructure change
- Thank you to [RipFence](https://github.com/RipFence) who asked how to place a chart on a different sheet from the data and then did a PR adding the example.
- added `ignore` so files checked into examples do not trigger a CI run
# 7.8.6
- Thank you [John Boyne](https://github.com/kyllath)
- Add missing parameter aliases to align with caller/callee
# 7.8.5
- Added `Get-ExcelFileSchema` to get the schema of an Excel file.
- This was added to support interacting with `ChatGPT`. Passing the schema to the `ChatGPT` via `PowerShellAI` let's you ask questions about the data including generating code based on the schema.
```powershell
Get-ExcelFileSchema .\salesData.xlsx
```
```json
{
"ExcelFile": "salesData.xlsx",
"WorksheetName": "Sheet1",
"Visible": true,
"Rows": 10,
"Columns": 4,
"Address": "A1:D10",
"Path": ".",
"PropertyNames": [
"Region",
"State",
"Units",
"Price"
]
}
```
# 7.8.x
Thanks to [Thomas Hofkens](https://github.com/thkn-hofa)
- Added `-NoHyperLinkConversion` to `Export-Excel` to no convert data to hyperlinks. [#1316](https://github.com/dfinke/ImportExcel/issues/1316)
# 7.8.4
- Add -ShowOnlyIcon to `New-ConditionalFormattingIconSet` does not show data in the cell, just the icon. Based on this discussion https://github.com/dfinke/ImportExcel/discussions/1340
# 7.8.3
Thanks [Thomas Hofkens](https://github.com/thkn-hofa)

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

5592
llms-examples.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -66,7 +66,7 @@ PS\> $excel = $avdata | Export-Excel -Path (Join-path $FilePath "\Machines.XLSX"
$excel.Save() ; $excel.Dispose()
```
Here Export-Excel is called with the -PassThru parameter, so the ExcelPackage object representing Machines.XLSX is stored in $Excel.The desired worksheet is selected, and then columns" B" and "I" are conditionally formatted \(excluding the top row\) to show red text if they contain "2003" or "Disabled" respectively.
Here Export-Excel is called with the -PassThru parameter, so the ExcelPackage object representing Machines.XLSX is stored in $Excel. The desired worksheet is selected, and then columns" B" and "I" are conditionally formatted \(excluding the top row\) to show red text if they contain "2003" or "Disabled" respectively.
A fixed date format is then applied to columns D to G, and the top row is formatted.
@@ -83,7 +83,7 @@ Again Export-Excel has been called with -PassThru leaving a package object in $E
This time B1:B100 has been conditionally formatted with 3 icons, using the "Flags" Icon-Set.
Add-ConditionalFormatting does not provide accessto every option in the formatting rule, so -PassThru has been used and the rule is modified to apply the flags in reverse order, and transitions between flags are set to 100 and 1000.
Add-ConditionalFormatting does not provide access to every option in the formatting rule, so -PassThru has been used and the rule is modified to apply the flags in reverse order, and transitions between flags are set to 100 and 1000.
### EXAMPLE 3

View File

@@ -81,7 +81,7 @@ PS\> $Excel = Invoke-Sum (Get-Process) Company Handles, PM, VirtualMemorySize |
The first line exports information to a table in new file; and captures the excel Package object in $Excel
The second line creates a chart on the first page of the work sheet, using the notation "TableName\[ColumnnName\]" to refer to the data, the labels come Name column in the table, and the data series from its PM and VirtualMemorySize columns. The display names for these in the header are set to 'PM' and 'VMSize'.
The second line creates a chart on the first page of the work sheet, using the notation "TableName\[ColumnName\]" to refer to the data, the labels come Name column in the table, and the data series from its PM and VirtualMemorySize columns. The display names for these in the header are set to 'PM' and 'VMSize'.
### EXAMPLE 4

View File

@@ -16,19 +16,19 @@ Exports data to an Excel worksheet.
### Default \(Default\)
```text
Export-Excel [[-Path] <String>] [-InputObject <Object>] [-Calculate] [-Show] [-WorksheetName <String>] [-Password <String>] [-ClearSheet] [-Append] [-Title <String>] [-TitleFillPattern <ExcelFillStyle>] [-TitleBold] [-TitleSize <Int32>] [-TitleBackgroundColor <Object>][-IncludePivotTable] [-PivotTableName <String>] [-PivotRows <String[]> [-PivotColumns <String[]>] [-PivotData <Object>] [-PivotFilter <String[]>] [-PivotDataToColumn] [-PivotTableDefinition <Hashtable>] [-IncludePivotChart] [-ChartType <eChartType>] [-NoLegend] [-ShowCategory] [-ShowPercent] [-AutoSize] [-MaxAutoSizeRows <Object>] [-NoClobber] [-FreezeTopRow] [-FreezeFirstColumn] [-FreezeTopRowFirstColumn] [-FreezePane <Int32[]>] [-AutoFilter] [-BoldTopRow] [-NoHeader] [-RangeName <String>] [-TableName <Object>] [-TableStyle <TableStyles>] [-TableTotalSettings <HashTable>] [-Barchart] [-PieChart] [-LineChart] [-ColumnChart] [-ExcelChartDefinition <Object[]>] [-HideSheet <String[]>] [-UnHideSheet <String[]>] [-MoveToStart] [-MoveToEnd] [-MoveBefore <Object>] [-MoveAfter <Object>] [-KillExcel] [-AutoNameRange] [-StartRow <Int32>] [-StartColumn <Int32>] [-PassThru] [-Numberformat <String>] [-ExcludeProperty <String[]>] [-NoAliasOrScriptPropeties] [-DisplayPropertySet] [-NoNumberConversion <String[]>] [-ConditionalFormat <Object[]>] [-ConditionalText <Object[]>] [-Style <Object[]>] [-CellStyleSB <ScriptBlock>] [-Activate] [-Now] [-ReturnRange] [-PivotTotals <String>] [-NoTotalsInPivot] [-ReZip] [<CommonParameters>]
Export-Excel [[-Path] <String>] [-InputObject <Object>] [-Calculate] [-Show] [-WorksheetName <String>] [-Password <String>] [-ClearSheet] [-Append] [-Title <String>] [-TitleFillPattern <ExcelFillStyle>] [-TitleBold] [-TitleSize <Int32>] [-TitleBackgroundColor <Object>][-IncludePivotTable] [-PivotTableName <String>] [-PivotRows <String[]> [-PivotColumns <String[]>] [-PivotData <Object>] [-PivotFilter <String[]>] [-PivotDataToColumn] [-PivotTableDefinition <Hashtable>] [-IncludePivotChart] [-ChartType <eChartType>] [-NoLegend] [-ShowCategory] [-ShowPercent] [-AutoSize] [-MaxAutoSizeRows <Object>] [-NoClobber] [-FreezeTopRow] [-FreezeFirstColumn] [-FreezeTopRowFirstColumn] [-FreezePane <Int32[]>] [-AutoFilter] [-BoldTopRow] [-NoHeader] [-RangeName <String>] [-TableName <Object>] [-TableStyle <TableStyles>] [-TableTotalSettings <HashTable>] [-Barchart] [-PieChart] [-LineChart] [-ColumnChart] [-ExcelChartDefinition <Object[]>] [-HideSheet <String[]>] [-UnHideSheet <String[]>] [-MoveToStart] [-MoveToEnd] [-MoveBefore <Object>] [-MoveAfter <Object>] [-KillExcel] [-AutoNameRange] [-StartRow <Int32>] [-StartColumn <Int32>] [-PassThru] [-Numberformat <String>] [-ExcludeProperty <String[]>] [-NoAliasOrScriptPropeties] [-DisplayPropertySet] [-NoNumberConversion <String[]>] [-NoHyperLinkConversion <String[]>] [-ConditionalFormat <Object[]>] [-ConditionalText <Object[]>] [-Style <Object[]>] [-CellStyleSB <ScriptBlock>] [-Activate] [-Now] [-ReturnRange] [-PivotTotals <String>] [-NoTotalsInPivot] [-ReZip] [<CommonParameters>]
```
### Package
```text
Export-Excel -ExcelPackage <ExcelPackage> [-InputObject <Object>] [-Calculate] [-Show] [-WorksheetName <String>] [-Password <String>] [-ClearSheet] [-Append] [-Title <String>] [-TitleFillPattern <ExcelFillStyle>] [-TitleBold] [-TitleSize <Int32>] [-TitleBackgroundColor <Object>] [-IncludePivotTable] [-PivotTableName <String>] [-PivotRows <String[]>] [-PivotColumns <String[]>] [-PivotData <Object>] [-PivotFilter <String[]>] [-PivotDataToColumn] [-PivotTableDefinition <Hashtable>] [-IncludePivotChart] [-ChartType <eChartType>] [-NoLegend] [-ShowCategory] [-ShowPercent] [-AutoSize] [-MaxAutoSizeRows <Object>] [-NoClobber] [-FreezeTopRow] [-FreezeFirstColumn] [-FreezeTopRowFirstColumn] [-FreezePane <Int32[]>] [-AutoFilter] [-BoldTopRow] [-NoHeader] [-RangeName <String>] [-TableName <Object>] [-TableStyle <TableStyles>] [-TableTotalSettings <HashTable>] [-Barchart] [-PieChart] [-LineChart] [-ColumnChart] [-ExcelChartDefinition <Object[]>] [-HideSheet <String[]>] [-UnHideSheet <String[]>] [-MoveToStart] [-MoveToEnd] [-MoveBefore <Object>] [-MoveAfter <Object>] [-KillExcel] [-AutoNameRange] [-StartRow <Int32>] [-StartColumn <Int32>] [-PassThru] [-Numberformat <String>] [-ExcludeProperty <String[]>]
[-NoAliasOrScriptPropeties] [-DisplayPropertySet] [-NoNumberConversion <String[]>] [-ConditionalFormat <Object[]>] [-ConditionalText <Object[]>] [-Style <Object[]>] [-CellStyleSB <ScriptBlock>] [-Activate] [-ReturnRange] [-PivotTotals <String>] [-NoTotalsInPivot] [-ReZip] [<CommonParameters>]
[-NoAliasOrScriptPropeties] [-DisplayPropertySet] [-NoNumberConversion <String[]>] [-NoHyperLinkConversion <String[]>] [-ConditionalFormat <Object[]>] [-ConditionalText <Object[]>] [-Style <Object[]>] [-CellStyleSB <ScriptBlock>] [-Activate] [-ReturnRange] [-PivotTotals <String>] [-NoTotalsInPivot] [-ReZip] [<CommonParameters>]
```
## DESCRIPTION
Exports data to an Excel file and where possible tries to convert numbers in text fields so Excel recognizes them as numbers instead of text. After all: Excel is a spreadsheet program used for number manipulation and calculations. The parameter -NoNumberConversion \* can be used if number conversion is not desired.
Exports data to an Excel file and where possible tries to convert numbers in text fields so Excel recognizes them as numbers instead of text. After all: Excel is a spreadsheet program used for number manipulation and calculations. The parameter -NoNumberConversion \* can be used if number conversion is not desired. In the same way the parameter NoHyperLinkConversion \* can be used if hyperlink conversion is not desired
## EXAMPLES
@@ -108,10 +108,10 @@ PS\> [PSCustOmobject][Ordered]@{
PhoneNr1 = '+32 44'
PhoneNr2 = '+32 4 4444 444'
PhoneNr3 = '+3244444444'
} | Export-Excel @ExcelParams -NoNumberConversion *
} | Export-Excel @ExcelParams -NoNumberConversion * -NoHyperLinkConversion *
```
Exports all data to the Excel file 'Excel.xslx' as is, no number conversion will take place. This means that Excel will show the exact same data that you handed over to the 'Export-Excel' function.
Exports all data to the Excel file 'Excel.xslx' as is, no number or hyperlink conversion will take place. This means that Excel will show the exact same data that you handed over to the 'Export-Excel' function.
### EXAMPLE 5
@@ -1357,6 +1357,24 @@ Accept pipeline input: False
Accept wildcard characters: False
```
### -NoHyperLinkConversion
By default the command will convert all URIs to hyperlinks if possible, but this isn't always desirable. -NoHyperLinkConversion allows you to add exceptions for the conversion.
The only Wildcard allowed is \* for all properties
```yaml
Type: String[]
Parameter Sets: (All)
Aliases:
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -ConditionalFormat
One or more conditional formatting rules defined with New-ConditionalFormattingIconSet.