file IO, is this a bug in Powershell? - powershell

I have the following code in Powershell
$filePath = "C:\my\programming\Powershell\output.test.txt"
try
{
$wStream = new-object IO.FileStream $filePath, [System.IO.FileMode]::Append, [IO.FileAccess]::Write, [IO.FileShare]::Read
$sWriter = New-Object System.IO.StreamWriter $wStream
$sWriter.writeLine("test")
}
I keep getting error:
Cannot convert argument "1", with value: "[IO.FileMode]::Append", for
"FileStream" to type "System.IO.FileMode": "Cannot convert value
"[IO.FileMode]::Append" to type "System.IO.FileMode" due to invalid
enumeration values. Specify one of the following enumeration values
and try again. The possible enumeration values are "CreateNew, Create,
Open, OpenOrCreate, Truncate, Append"."
I tried the equivalent in C#,
FileStream fStream = null;
StreamWriter stWriter = null;
try
{
fStream = new FileStream(#"C:\my\programming\Powershell\output.txt", FileMode.Append, FileAccess.Write, FileShare.Read);
stWriter = new StreamWriter(fStream);
stWriter.WriteLine("hahha");
}
it works fine!
What's wrong with my powershell script? BTW I am running on powershell
Major Minor Build Revision
----- ----- ----- --------
3 2 0 2237

Another way would be to use just the name of the value and let PowerShell cast it to the target type:
New-Object IO.FileStream $filePath ,'Append','Write','Read'

When using the New-Object cmdlet and the target type constructor takes in parameters, you should either use the -ArgumentList parameter (of New-Object) or wrap the parameters in parenthesis - I prefer to wrap my constructors with parens:
# setup some convenience variables to keep each line shorter
$path = [System.IO.Path]::Combine($Env:TEMP,"Temp.txt")
$mode = [System.IO.FileMode]::Append
$access = [System.IO.FileAccess]::Write
$sharing = [IO.FileShare]::Read
# create the FileStream and StreamWriter objects
$fs = New-Object IO.FileStream($path, $mode, $access, $sharing)
$sw = New-Object System.IO.StreamWriter($fs)
# write something and remember to call to Dispose to clean up the resources
$sw.WriteLine("Hello, PowerShell!")
$sw.Dispose()
$fs.Dispose()
New-Object cmdlet online help: http://go.microsoft.com/fwlink/?LinkID=113355

Yet another way could be to enclose the enums in parens:
$wStream = new-object IO.FileStream $filePath, ([System.IO.FileMode]::Append), `
([IO.FileAccess]::Write), ([IO.FileShare]::Read)

If your goal is to write into a logfile or text file, then you could try the supported cmdlets in PowerShell to achieve this?
Get-Help Out-File -Detailed

Related

PowerShell reading and writing compressed files with byte arrays

Final Update: Turns out I didn't need Binary writer. I could just copy memory streams from one archive to another.
I'm re-writing a PowerShell script which works with archives. I'm using two functions from here
Expand-Archive without Importing and Exporting files
and can successfully read and write files to the archive. I've posted the whole program just in case it makes things clearer for someone to help me.
However, there are three issues (besides the fact that I don't really know what I'm doing).
1.) Most files have this error on when trying to run
Add-ZipEntry -ZipFilePath ($OriginalArchivePath + $PartFileDirectoryName) -EntryPath $entry.FullName -Content $fileBytes}
Cannot convert value "507" to type "System.Byte". Error: "Value was either too large or too small for an unsigned byte." (replace 507 with whatever number from the byte array is there)
2.) When it reads a file and adds it to the zip archive (*.imscc) it adds a character "a" to the beginning of the file contents.
3.) The only file it doesn't error on are text files, when I really want it to handle any file
Thank you for any assistance!
Update: I've tried using System.IO.BinaryWriter, with the same errors.
Add-Type -AssemblyName 'System.Windows.Forms'
Add-Type -AssemblyName 'System.IO.Compression'
Add-Type -AssemblyName 'System.IO.Compression.FileSystem'
function Folder-SuffixGenerator($SplitFileCounter)
{
return ' ('+$usrSuffix+' '+$SplitFileCounter+')'
}
function Get-ZipEntryContent(#returns the bytes of the first matching entry
[string] $ZipFilePath, #optional - specify a ZipStream or path
[IO.Stream] $ZipStream = (New-Object IO.FileStream($ZipFilePath, [IO.FileMode]::Open)),
[string] $EntryPath){
$ZipArchive = New-Object IO.Compression.ZipArchive($ZipStream, [IO.Compression.ZipArchiveMode]::Read)
$buf = New-Object byte[] (0) #return an empty byte array if not found
$ZipArchive.GetEntry($EntryPath) | ?{$_} | %{ #GetEntry returns first matching entry or null if there is no match
$buf = New-Object byte[] ($_.Length)
Write-Verbose " reading: $($_.Name)"
$_.Open().Read($buf,0,$buf.Length)
}
$ZipArchive.Dispose()
$ZipStream.Close()
$ZipStream.Dispose()
return ,$buf
}
function Add-ZipEntry(#Adds an entry to the $ZipStream. Sample call: Add-ZipEntry -ZipFilePath "$PSScriptRoot\temp.zip" -EntryPath Test.xml -Content ([text.encoding]::UTF8.GetBytes("Testing"))
[string] $ZipFilePath, #optional - specify a ZipStream or path
[IO.Stream] $ZipStream = (New-Object IO.FileStream($ZipFilePath, [IO.FileMode]::OpenOrCreate)),
[string] $EntryPath,
[byte[]] $Content,
[switch] $OverWrite, #if specified, will not create a second copy of an existing entry
[switch] $PassThru ){#return a copy of $ZipStream
$ZipArchive = New-Object IO.Compression.ZipArchive($ZipStream, [IO.Compression.ZipArchiveMode]::Update, $true)
$ExistingEntry = $ZipArchive.GetEntry($EntryPath) | ?{$_}
If($OverWrite -and $ExistingEntry){
Write-Verbose " deleting existing $($ExistingEntry.FullName)"
$ExistingEntry.Delete()
}
$Entry = $ZipArchive.CreateEntry($EntryPath)
$WriteStream = New-Object System.IO.StreamWriter($Entry.Open())
$WriteStream.Write($Content,0,$Content.Length)
$WriteStream.Flush()
$WriteStream.Dispose()
$ZipArchive.Dispose()
If($PassThru){
$OutStream = New-Object System.IO.MemoryStream
$ZipStream.Seek(0, 'Begin') | Out-Null
$ZipStream.CopyTo($OutStream)
}
$ZipStream.Close()
$ZipStream.Dispose()
If($PassThru){$OutStream}
}
$NoDeleteFiles = #('files_meta.xml' ,'course_settings.xml', 'assignment_groups.xml', 'canvas_export.txt', 'imsmanifest.xml')
Set-Variable usrSuffix -Option ReadOnly -Value 'part' -Force
$MaxImportFileSize = 1000
$compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal
$SplitFileCounter = 1
$FileBrowser = New-Object System.Windows.Forms.OpenFileDialog
$FileBrowser.filter = "Canvas Export Files (*.imscc)| *.imscc"
[void]$FileBrowser.ShowDialog()
$FileBrowser.FileName
$FilePath = $FileBrowser.FileName
$OriginalArchivePath = $FilePath.Substring(0,$FilePath.Length-6)
$PartFileDirectoryName = $OriginalArchive + (Folder-SuffixGenerator($SplitFileCounter)) + '.imscc'
$CourseZip = [IO.Compression.ZipFile]::OpenRead($FilePath)
$CourseZipFiles = $CourseZip.Entries | Sort Length -Descending
$CourseZip.Dispose()
<#
$SortingTable = $CourseZip.entries | Select Fullname,
#{Name="Size";Expression={$_.length}},
#{Name="CompressedSize";Expression={$_.Compressedlength}},
#{Name="PctZip";Expression={[math]::Round(($_.compressedlength/$_.length)*100,2)}}|
Sort Size -Descending | format-table –AutoSize
#>
# Add mandatory files
ForEach($entry in $CourseZipFiles)
{
if ($NoDeleteFiles.Contains($entry.Name)){
Write-Output "Adding to Zip" + $entry.FullName
# Add to Zip
$fileBytes = Get-ZipEntryContent -ZipFilePath $FilePath -EntryPath $entry.FullName
Add-ZipEntry -ZipFilePath ($OriginalArchivePath + $PartFileDirectoryName) -EntryPath $entry.FullName -Content $fileBytes
}
}```
System.IO.StreamWriter is a text writer, and therefore not suitable for writing raw bytes. Cannot convert value "507" to type "System.Byte" indicates that an inappropriate attempt was made to convert text - a .NET string composed of [char] instances which are in effect [uint16] code points (range 0x0 - 0xffff) - to [byte] instances (0x0 - 0xff). Therefore, any Unicode character whose code point is greater than 255 (0xff) will cause this error.
The solution is to use a .NET API that allows writing raw bytes, namely System.IO.BinaryWriter:
$WriteStream = [System.IO.BinaryWriter]::new($Entry.Open())
$WriteStream.Write($Content)
$WriteStream.Flush()
$WriteStream.Dispose()

PowerShell Ast for injection detection

I am generating a ScriptBlock based on DB input which I invoke later in the script. I now want to ensure that a malicious user is not injecting any PS code in the DB varchar field that then gets executed.
First, I filtered the String Script Block for forbidden chars such as $ or ;. But I want to take it one step further and use AST to check if there is any executable code in the DB field.
When I use $Ast.FindAll for a specific element such as ForEachStatementAst it works fine.
However, I also want to be able to detect cmdlets etc in the String.
Examples that should be recognised as being ok:
abc
123
'a','b'
true
Examples that should be recognised as being not ok:
Write-host or Remove-Item or any other get-command cmdlet.
`$(MySubExpression)
When using AST visualisation, I get the same tree for both examples. ('abc', 'Write-Host')
ScriptBlockAst-> NamedBlockAst -> PipelineAst -> CommandAst -> StringConstantExpressionAst
Is there any way I can use AST to determine whether the DB field (or any string) contains only allowed entries such as non PS keywords / cmdlets, numbers etc but nothing that could be used as a PS command and that could be invoked?
The following code works for the test cases but I wonder if this can be achieved in a better way. If Res.count > 0, the input was not ok, if =0, it was ok.
$DebugPreference = 'Continue'
[System.Collections.Generic.List[System.String]]$InputStringList = New-Object -TypeName "System.Collections.Generic.List[System.String]"
$InputStringList.Add("foreach (`$x in #('a','b')){;}")
$InputStringList.Add("New-item -Path 'C:\Test.txt' -ItemType File")
$InputStringList.Add("Write-host 'as'")
$InputStringList.Add("abc")
$InputStringList.Add("a,b,c")
$InputStringList.Add("123")
$InputStringList.Add("true")
[System.Collections.Generic.List[System.Type]]$TypeList = New-Object -TypeName "System.Collections.Generic.List[System.Type]"
$TypeList.Add([System.Management.Automation.Language.StringLiteralToken])
$TypeList.Add([System.Management.Automation.Language.ScriptBlockAst])
$TypeList.Add([System.Management.Automation.Language.NamedBlockAst])
$TypeList.Add([System.Management.Automation.Language.StringConstantExpressionAst])
$TypeList.Add([System.Management.Automation.Language.ConstantExpressionAst])
$TypeList.Add([System.Management.Automation.Language.CommandExpressionAst])
$TypeList.Add([System.Management.Automation.Language.CommandAst])
$TypeList.Add([System.Management.Automation.Language.PipelineAst])
$TypeList.Add([System.Management.Automation.Language.ArrayLiteralAst])
[String[]]$CommandArray = (Get-Command | Select-Object -ExpandProperty 'Name')
[System.Management.Automation.ScriptBlock]$Predicate =
{
param([System.Management.Automation.Language.Ast]$AstObject)
Write-Debug -Message $AstObject.GetType().FullName
if($AstObject -is [System.Management.Automation.Language.StringConstantExpressionAst])
{
if($AstObject.Value -in $CommandArray)
{
return $true
}
else
{
return $false
}
}
else
{
return (-not($AstObject.GetType() -in $TypeList))
}
}
$InputStringList.GetEnumerator() | ForEach-Object -Process `
{
Write-Debug -Message ("Processing string: "+$PsItem.ToString())
$ast = [System.Management.Automation.Language.Parser]::ParseInput($PsItem, [ref]$null, [ref]$null)
$res=$ast.FindAll($Predicate, $true)
Write-Debug -Message $res.count.ToString()
}
As commented, what you trying to do is creating your own restricted languagemode. Meaning that it would probably be easier to invoke the concerned scriptblock in an restricted runspace.
Derived from #mklement0 great answer for Automatically retrieve Allowed Types for Constrained Language mode:
Function Invoke-Restricted {
[CmdletBinding()]param([String]$Expression)
$Restricted = [powershell]::Create()
$Restricted.Runspace.SessionStateProxy.LanguageMode = 'Restricted'
Try { $Restricted.AddScript($expression).Invoke() }
Catch { $PSCmdlet.ThrowTerminatingError($_) }
}
Restricted expression
Invoke-Restricted #'
#{
string = 'abc'
int = 123
array = 'a','b'
hashtable = #{ a = 1; b = 2 }
boolean = $true
}
'#
Yields
Name Value
---- -----
array {a, b}
int 123
boolean True
string abc
hashtable {b, a}
Invalid expression
Invoke-Restricted #'
#{
TimeSpan = [TimeSpan]'12:34:45'
}
'#
Throws an error:
Invoke-Restricted: Exception calling "Invoke" with "0" argument(s): "At line:1 char:1
+ [TimeSpan]"12:34:45"
+ ~~~~~~~~~~
The type TimeSpan is not allowed in restricted language mode or a Data section."
Yet, it has some limitations as it does not prevent e.g. the use of cmdlets.
For an easy and secure way to retrieve a (structured) configuration file I would depend on a serialized format as JSON using the ConvertFrom-Json cmdlet
Related: #12377 Running partly trusted PowerShell code in a restricted security environment.

Is there a using namespace equivalent pre-version 5? [duplicate]

When I use another object in the .net-Framework in C# I can save a lot of typing by using the using directive.
using FooCompany.Bar.Qux.Assembly.With.Ridiculous.Long.Namespace.I.Really.Mean.It;
...
var blurb = new Thingamabob();
...
So is there a way in Powershell to do something similiar? I'm accessing a lot of .net objects and am not happy of having to type
$blurb = new-object FooCompany.Bar.Qux.Assembly.With.Ridiculous.Long.Namespace.I.Really.Mean.It.Thingamabob;
all the time.
There's really nothing at the namespace level like that. I often assign commonly used types to variables and then instantiate them:
$thingtype = [FooCompany.Bar.Qux.Assembly.With.Ridiculous.Long.Namespace.I.Really.Mean.It.Thingamabob];
$blurb = New-Object $thingtype.FullName
Probably not worth it if the type won't be used repeatedly, but I believe it's the best you can do.
PowerShell 5.0 (included in WMF5 or Windows 10 and up), adds the using namespace construct to the language. You can use it in your script like so:
#Require -Version 5.0
using namespace FooCompany.Bar.Qux.Assembly.With.Ridiculous.Long.Namespace.I.Really.Mean.It
$blurb = [Thingamabob]::new()
(The #Require statement on the first line is not necessary to use using namespace, but it will prevent the script from running in PS 4.0 and below where using namespace is a syntax error.)
Check out this blog post from a couple years ago: http://blogs.msdn.com/richardb/archive/2007/02/21/add-types-ps1-poor-man-s-using-for-powershell.aspx
Here is add-types.ps1, excerpted from that article:
param(
[string] $assemblyName = $(throw 'assemblyName is required'),
[object] $object
)
process {
if ($_) {
$object = $_
}
if (! $object) {
throw 'must pass an -object parameter or pipe one in'
}
# load the required dll
$assembly = [System.Reflection.Assembly]::LoadWithPartialName($assemblyName)
# add each type as a member property
$assembly.GetTypes() |
where {$_.ispublic -and !$_.IsSubclassOf( [Exception] ) -and $_.name -notmatch "event"} |
foreach {
# avoid error messages in case it already exists
if (! ($object | get-member $_.name)) {
add-member noteproperty $_.name $_ -inputobject $object
}
}
}
And, to use it:
RICBERG470> $tfs | add-types "Microsoft.TeamFoundation.VersionControl.Client"
RICBERG470> $itemSpec = new-object $tfs.itemspec("$/foo", $tfs.RecursionType::none)
Basically what I do is crawl the assembly for nontrivial types, then write a "constructor" that uses Add-Member add them (in a structured way) to the objects I care about.
See also this followup post: http://richardberg.net/blog/?p=38
this is just a joke, joke...
$fullnames = New-Object ( [System.Collections.Generic.List``1].MakeGenericType( [String]) );
function using ( $name ) {
foreach ( $type in [Reflection.Assembly]::LoadWithPartialName($name).GetTypes() )
{
$fullnames.Add($type.fullname);
}
}
function new ( $name ) {
$fullname = $fullnames -like "*.$name";
return , (New-Object $fullname[0]);
}
using System.Windows.Forms
using FooCompany.Bar.Qux.Assembly.With.Ridiculous.Long.Namespace.I.Really.Mean.It
$a = new button
$b = new Thingamabob
Here's some code that works in PowerShell 2.0 to add type aliases. But the problem is that it is not scoped. With some extra work you could "un-import" the namespaces, but this should get you off to a good start.
##############################################################################
#.SYNOPSIS
# Add a type accelerator to the current session.
#
#.DESCRIPTION
# The Add-TypeAccelerator function allows you to add a simple type accelerator
# (like [regex]) for a longer type (like [System.Text.RegularExpressions.Regex]).
#
#.PARAMETER Name
# The short form accelerator should be just the name you want to use (without
# square brackets).
#
#.PARAMETER Type
# The type you want the accelerator to accelerate.
#
#.PARAMETER Force
# Overwrites any existing type alias.
#
#.EXAMPLE
# Add-TypeAccelerator List "System.Collections.Generic.List``1"
# $MyList = New-Object List[String]
##############################################################################
function Add-TypeAccelerator {
[CmdletBinding()]
param(
[Parameter(Position=1,Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
[String[]]$Name,
[Parameter(Position=2,Mandatory=$true,ValueFromPipeline=$true)]
[Type]$Type,
[Parameter()]
[Switch]$Force
)
process {
$TypeAccelerators = [Type]::GetType('System.Management.Automation.TypeAccelerators')
foreach ($a in $Name) {
if ( $TypeAccelerators::Get.ContainsKey($a) ) {
if ( $Force ) {
$TypeAccelerators::Remove($a) | Out-Null
$TypeAccelerators::Add($a,$Type)
}
elseif ( $Type -ne $TypeAccelerators::Get[$a] ) {
Write-Error "$a is already mapped to $($TypeAccelerators::Get[$a])"
}
}
else {
$TypeAccelerators::Add($a, $Type)
}
}
}
}
If you just need to create an instance of your type, you can store the name of the long namespace in a string:
$st = "System.Text"
$sb = New-Object "$st.StringBuilder"
It's not as powerful as the using directive in C#, but at least it's very easy to use.
Thanks everybody for your input. I've marked Richard Berg's contribution as an answer, because it most closely resembles what I'm looking for.
All your answers brought me on the track that seems most promising: In his blog post Keith Dahlby proposes a Get-Type commandlet that allows easy consutruction of types for generic methods.
I think there is no reason against exetending this to also search through a predefined path of assemblies for a type.
Disclaimer: I haven't built that -- yet ...
Here is how one could use it:
$path = (System.Collections.Generic, FooCompany.Bar.Qux.Assembly.With.Ridiculous.Long.Namespace.I.Really.Mean.It)
$type = get-type -Path $path List Thingamabob
$obj = new-object $type
$obj.GetType()
This would result in a nice generic List of Thingamabob. Of course I'd wrap up everthing sans the path definition in just another utility function. The extended get-type would include a step to resolve any given type agains the path.
#Requires -Version 5
using namespace System.Management.Automation.Host
#using module
I realize this is an old post, but I was looking for the same thing and came across this: http://weblogs.asp.net/adweigert/powershell-adding-the-using-statement
Edit: I suppose I should specify that it allows you to use the familiar syntax of...
using ($x = $y) { ... }

Powershell: Multiple parameters for a TabExpansion++ ArgumentCompleter

I am working on a function to schedule a user's home drive transfer, I am going to use TabExpansion++ to allow the user to autocomplete the server name, which is populated from a CSV file. There will be parameters for both OldServer and NewServer.
Is it possible with TabExpansion++ to specify more than one parameter for a single autocompleter?
Here is what I have:
function HomeDriveSiteCompletion {
[ArgumentCompleter(
Parameter = 'OldServer',
Command = { 'Schedule-HomeTransfer' },
Description = 'Home drive transfer tool server name autocomplete')]
param($commandName,$parameterName,$wordToComplete,$commandAst,$fakeBoundParameter)
Import-Csv -Path $Global:ServersList | % {New-CompletionResult -ToolTip $_.Site -completiontext $_.Site}
}
Which works fine for OldServer. If I can save code by specifying both parameters in the same place, that would be ideal. I have tried both
Parameter = #('OldServer','NewServer')
and
Parameter = { 'OldServer','NewServer' }
Neither of which worked. Is there another way I could make this work?
Questions like this are why I love this site. I have not used TabExpansion++, but I have done some tab expansion stuff for parameters. I couldn't remember if I'd run into this exact question before so I went looking and discovered something that I haven't encountered in the PowerShell world before, DynamicParam. How have I not seen this before? The levels of awesome of it for situations like this are right off the charts! What it allows you to do is not declare a parameter, but then add that parameter before the actual scriptblock of the function, and do scripty kinds of things for validation of that parameter.
I asked Google for a little help, and it pointed me to this SO question (where Shay Levy gives the accepted answer recommending TabExpansion++), but the next answer goes on about DynamicParam. So I looked that up and found this blog on Microsoft's site that explains it further. Basically for your needs you would do something like:
DynamicParam {
$SrvList = Import-CSV $Global:ServerList | Select -Expand Site
$ParamNames = #('OldServer','NewServer')
#Create Param Dictionary
$ParamDictionary = new-object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
ForEach($Name in $ParamNames){
#Create a container for the new parameter's various attributes, like Manditory, HelpMessage, etc that usually goes in the [Parameter()] part
$ParamAttribCollecton = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]
#Create each attribute
$ParamAttrib = new-object System.Management.Automation.ParameterAttribute
$ParamAttrib.Mandatory = $true
$ParamAttrib.HelpMessage = "Enter a server name"
#Create ValidationSet to make tab-complete work
$ParamValSet = New-Object -type System.Management.Automation.ValidateSetAttribute($SrvList)
#Add attributes and validationset to the container
$ParamAttribCollecton.Add($ParamAttrib)
$ParamAttribCollecton.Add($ParamValSet)
#Create the actual parameter, then add it to the Param Dictionary
$MyParam = new-object -Type System.Management.Automation.RuntimeDefinedParameter($Name, [String], $ParamAttribCollecton)
$ParamDictionary.Add($Name, $MyParam)
}
#Return the param dictionary so the function can add the parameters to itself
return $ParamDictionary
}
That would add the OldServer and NewServer parameters to your function. Both would tab-complete the servers listed in the Site column of the CSV located at $global:ServerList. Sure, it's not as short and sweet as TabExpansion++'s context, but on the other hand it does not require any additional modules or anything to be loaded on the system since it is all self contained and only using basic PowerShell features.
Now, that adds the parameters, but it doesn't actually assign them to variables, so we'll have to do that in the Begin part of the function. We'll list the parameters in PSBoundParameters.Keys and check if a variable already exists in the current scope, and if not we'll make one in the current scope, so as to mess with anything outside of the function. So, with a basic parameter of -User, the two dynamic parameters, and the addition of the variables for the dynamic parameters, we're looking at something like this for your function:
Function Schedule-HomeTransfer{
[CmdletBinding()]
Param([string]$User)
DynamicParam {
$SrvList = Import-CSV $Global:ServerList | Select -Expand Site
$ParamNames = #('OldServer','NewServer')
#Create Param Dictionary
$ParamDictionary = new-object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
ForEach($Name in $ParamNames){
#Create a container for the new parameter's various attributes, like Manditory, HelpMessage, etc that usually goes in the [Parameter()] part
$ParamAttribCollecton = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]
#Create each attribute
$ParamAttrib = new-object System.Management.Automation.ParameterAttribute
$ParamAttrib.Mandatory = $true
$ParamAttrib.HelpMessage = "Enter a server name"
#Create ValidationSet to make tab-complete work
$ParamValSet = New-Object -type System.Management.Automation.ValidateSetAttribute($SrvList)
#Add attributes and validationset to the container
$ParamAttribCollecton.Add($ParamAttrib)
$ParamAttribCollecton.Add($ParamValSet)
#Create the actual parameter, then add it to the Param Dictionary
$MyParam = new-object -Type System.Management.Automation.RuntimeDefinedParameter($Name, [String], $ParamAttribCollecton)
$ParamDictionary.Add($Name, $MyParam)
}
#Return the param dictionary so the function can add the parameters to itself
return $ParamDictionary
}
Begin{$PSBoundParameters.Keys | Where{!(Get-Variable -name $_ -Scope 0 -ErrorAction SilentlyContinue)} | ForEach{New-Variable -Name $_ -Value $PSBoundParameters[$_]}}
Process{
"You chose to move $User from $OldServer to $NewServer"
}
}
That right there will allow for tab completion on -OldServer and -NewServer, and when I set $global:ServerList to "C:\Temp\new.csv' and populated that with a 'Site' column having 3 values, those popped right up for me to select (in the ISE it actually pops up a list to choose from, not just tab completion like in the console).

Converting accdb to csv with powershell

I am trying to convert some excel (.xlsx) and Access (.accdb) to CSV files.
I quickly found a way to do this with Excel but now I cannot find any helpful documentation on converting .accdb files.
So far I have:
$adOpenStatic = 3
$adLockOptimistic = 3
$objConnection = New-Object -com "ADODB.Connection"
$objRecordSet = New-Object -com "ADODB.Recordset"
$objConnection.Open("Provider = Microsoft.ACE.OLEDB.12.0; Data Source = " + $Filepath)
$objRecordset.Open("Select * From TableName",$objConnection,$adOpenStatic, $adLockOptimistic)
#Here I need some way to either saveas .csv or loop through
#each row and pass to csv.
$objRecordSet.Close()
$objConnection.Close()
Any Ideas?
I would be willing to do this with another language (VB, Java, PHP) if anyone knows a way.
If you use .NET rather than COM it's a lot easier. Here's some code to handle the Excel XLSX files
#Even /w Excel 2010 installed, needed to install ACE:
#http://www.microsoft.com/downloads/en/details.aspx?FamilyID=c06b8369-60dd-4b64-a44b-84b371ede16d&displaylang=en
#Becareful about executing in "right" version x86 vs. x64
#Change these settings as needed
$filepath = 'C:\Users\u00\Documents\backupset.xlsx'
#Comment/Uncomment connection string based on version
#Connection String for Excel 2007:
$connString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=`"$filepath`";Extended Properties=`"Excel 12.0 Xml;HDR=YES`";"
#Connection String for Excel 2003:
#$connString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=`"$filepath`";Extended Properties=`"Excel 8.0;HDR=Yes;IMEX=1`";"
$qry = 'select * from [backupset$]'
$conn = new-object System.Data.OleDb.OleDbConnection($connString)
$conn.open()
$cmd = new-object System.Data.OleDb.OleDbCommand($qry,$conn)
$da = new-object System.Data.OleDb.OleDbDataAdapter($cmd)
$dt = new-object System.Data.dataTable
[void]$da.fill($dt)
$conn.close()
$dt | export-csv ./test.csv -NoTypeInformation
If you want to stick with ADODB COM object:
# loop through all records - do work on each record to convert it to CSV
$objRecordset.Open("Select * FROM Tablename", $objConnection,$adOpenStatic,$adLockOptimistic)
$objRecordset.MoveFirst()
do {
# do your work to get each field and convert this item to CSV
# fields available thru: $objRecordset.Fields['fieldname'].Value
$objRecordset.MoveNext()
} while ($objRecordset.EOF -eq $false)
$objRecordset.Close()