01 前言
这是大半年前的事了,帮一朋友研究如何批量导出Access里面存的图片(OLE对象)。Access没有提供直接导出图片的方法,很郁闷。查过一番资料,都不是很满意,决定自己鼓捣。经过N天的奋战,最后是成了。把思路记录一下,抛砖引玉。
02 正文
(1)准备
准备了一个Prod.accdb
文件,只有一张表,其中prodPic
存了JPG格式
的图片(OLE 对象
),结构如下:
注意
- 本例只存了
JPG格式
的图片,如需其他格式,请自行修改测试
(2)执行
脚本如下:
<#
.DESCRIPTION
批量导出ACCDB中的JPG图片,保存到指定路径
测试环境:powershell 5.1
2019-08-02
.EXAMPLE
cmd下执行(powershell.exe 32bit),依次传入3个参数——ACCDB文件位置、JPG输出位置、SQL语句:
C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe -file "D:\XXX\ExpJPGFromAccdb.ps1" "D:\XXX\Prod.accdb" "D:\XXX\exp" "select prodName as name,prodPic as pic from Products"
#>
#获取Access连接【accdb文件】
function Get-AccessConnection-ACCDB
{
[CmdletBinding()]
param (
[string]$filePath,
[string]$password
)
try
{
$con = New-Object System.Data.OleDb.OleDbConnection
$con.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=$filePath;"
if($password.trim()){
$con += "Jet OLEDB:Database Password=$password"
}
Write-Verbose ("连接字符串:" + $con.ConnectionString)
$con.Open()
if ($con.State -eq "Open")
{
$con
Write-Verbose "连接已打开..."
}
else
{
$null
}
}
catch
{
Write-Verbose "获取数据库连接异常..."
if([Environment]::Is64BitProcess){ Write-Verbose "当前环境或为64bit,请切换至32bit环境(32位的powershell)后重试!"}
Write-Verbose "异常信息:$($Error[0])"
}
}
#关键在确定开始的地方
function Export-File{
param([string]$path,
[byte[]]$data
)
try{
#加入集合
$list = New-Object System.Collections.ArrayList
$list.AddRange($data)
#定位JPG格式文件头
$index = Get-ExactPosition -conditionCode "FFD8FF" -content $data[0..310]
Write-Verbose "头部跳过:$index"
if($index -gt 0){
1.300 | ForEach-Object{$list.RemoveAt($list.Count-1)} #去掉后面
1..$index | ForEach-Object{$list.RemoveAt(0)} #去掉前面
[byte[]]$newData = $list.ToArray() #转为数组
$stream = New-Object System.IO.MemoryStream($newData,0,$newData.Length) #如果上一步有问题,此步报异常
$img = [System.Drawing.Image]::FromStream($stream)
$img.Save($path)
return $true
}
Write-Host "文件头格式不符,跳过:$path"
return $false
}
catch{
Write-Host ("保存异常..."+$Error[0].Exception.Message)
return $false
}
}
#定位准确的文件头位置
function Get-ExactPosition{
param(
[string]$conditionCode, #文件头格式
[byte[]]$content
)
$index = (($content | ForEach-Object{"{0:X2}" -f $_ }) -join "").IndexOf($conditionCode)
if($index -gt 1){
$index /= 2
}
return $index
}
#主要处理逻辑
function Main-Do{
param(
[string]$accfile,
[string]$savePath,
[string]$sql
)
if(-not (Test-Path $accfile)){
Write-Host "ACCDB文件不存在,请检查参数!"
return
}
if(-not (Test-Path $savePath)){
Write-Host "保存路径不存在,请检查参数!"
return
}
if([string]::IsNullOrEmpty($sql)){
Write-Host "请传入有效的SQL!"
return
}
$count = 0
$con = Get-AccessConnection-ACCDB -filePath $accfile
if($con){
try
{
[void][reflection.assembly]::Load('System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')
$Error.Clear()
if(-not $savePath.EndsWith("\")){$savePath += "\"}
$command = New-Object System.Data.OleDb.OleDbCommand($sql, $con)
$reader = $command.ExecuteReader()
while($reader.Read()){
foreach($c in 0..($reader.FieldCount-1)){
if("DBTYPE_LONGVARBINARY" -eq $reader.GetDataTypeName($c).ToString() -and $reader[$c].GetType() -ne [System.DBNull]){
#拼接文件名
if($reader["name"]){
$saveImg = $savePath+$reader["name"]+".jpg"
}else{
$saveImg = $savePath+"Row_"+("{0:d5}" -f ($count+1))+".jpg"
}
if(Export-File -path $saveImg -data $reader[$c]){
#Write-Host "$saveImg 保存成功!"
$count++
}else{
Write-Host "$saveImg 保存失败!"
}
}else{
Write-Verbose "跳过..$c"
}
}
}
$reader.Close()
}
catch [System.Exception]
{
Write-Host ("异常.."+$Error[0].Exception.Message)
}
finally
{
#Write-Host "关闭连接..."
$con.Close()
}
}
else{
Write-Host "无连接!"
}
return $count
}
$count = Main-Do -accfile $args[0] -savePath $args[1] -sql $args[2]
if($count){Write-Host "完成!成功生成${count}个文件"}
其实思路也比较自然:
- 读出
OLE
字段的二进制流 - 根据JPG文件头特征定位到JPG文件开始的地方
- 截取指定位置的二进制,构造图片流输出即可
脚本使用说明
- powershell
5.1
下测试通过- 将脚本另存为
.ps1
格式,比如此处为:ExpJPGFromAccdb.ps1
,使用32位的powershell程序执行。命令格式(参数间要以空格隔开)如下:C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe -file “D:\XXX\ExpJPGFromAccdb.ps1” “D:\XXX\Prod.accdb” “D:\XXX\exp” “select prodName as name,prodPic as pic from Products”- 如果提示“未在本地计算机上注册“Microsoft.ACE.OLEDB.12.0”提供程序。”,请自行下载安装“
Access database engine 2010
”SQL语句
强烈建议只返回两个字段
,第一个字段以name
为别名,作为导出的文件名;第二个字段为数据库中OLE
字段- 如果
SQL语句
中没有返回name
字段,默认以Row_行号
作为文件名进行保存。这时,如果记录数>99999
,为了文件名的统一和美观,可以修改{0:d5}
为{0:d10}
再执行- 如果不能执行脚本,提示“无法加载文件 D:\XXX\ExpJPGFromAccdb.ps1,因为在此系统上禁止运行脚本……”。请先修改powershell执行策略(参考此处)
执行过程:
导出结果:
03 后记
再提醒一点,该脚本目前仅能处理JPG格式
图片,其他格式暂无法识别,有条件的可以自己修改。
另外,如果有想把上面的代码改为VBA的,也是可以的,建议引用 Microsoft ActiveX data Objects 2.8
以上。
欢迎留言交流~
------END------