Writing Powershell Error messages to SSIS - powershell

I have an SSIS execute process task where I am calling a PowerShell script. I found that I can get the process task to fail if the PowerShell script fails by trapping the errors like this:
trap {
$err = $_.Exception
while ( $err.InnerException )
{
$err = $err.InnerException
Write-Host $err.Message
};
exit 1
}
I would like to get the error messages to return to SSIS, but I can't seem to get it to work. It just returns the following:
The process exit code was "1" while the expected was "0".
Any help is appreciated.
Full Code:
Param(
[String]$excelPath,
[String]$serverName,
[String]$databaseName,
[String]$tableName
)
$ErrorActionPreference = 'Stop'
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO.SqlDataType') | Out-Null
Trap {
$err = $_.Exception
while ( $err.InnerException )
{
$err = $err.InnerException
Write-Host $err.Message
};
exit 1
}
$excel = New-Object -ComObject excel.application
$excel.visible = $False
$excel.displayalerts=$False
$workbook = $excel.Workbooks.Open($ExcelPath)
$workSheet = $workbook.worksheets.Item(1).name
$workbook.Close()
$excel.quit()
$excel = $null
$query = "select * from [$workSheet`$]";
$connectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=`"$excelPath`";Extended Properties=`"Excel 12.0 Xml;HDR=YES`";"
# Instantiate some objects which will be needed
$serverSMO = New-Object Microsoft.SqlServer.Management.Smo.Server($serverName)
$db = $serverSMO.Databases[$databaseName];
$newTable = New-Object Microsoft.SqlServer.Management.Smo.Table ;
$newTable.Parent = $db;
$newTable.Name = $tableName ;
$conn = New-Object System.Data.OleDb.OleDbConnection($connectionString)
$conn.open()
$cmd = New-Object System.Data.OleDb.OleDbCommand($query,$conn)
$dataAdapter = New-Object System.Data.OleDb.OleDbDataAdapter($cmd)
$dataTable = New-Object System.Data.DataTable
$dataAdapter.fill($dataTable)
$conn.close()
# Drop the table if it exists
if($db.Tables.Contains($tableName).Equals($true))
{
($db.Tables[$tableName]).Drop()
}
# Iterate the columns in the DataTable object and add dynamically named columns to the SqlServer Table object.
foreach($col in $dataTable.Columns)
{
$sqlDataType = [Microsoft.SqlServer.Management.Smo.SqlDataType]::Varchar
$dataType = New-Object Microsoft.SqlServer.Management.Smo.DataType($sqlDataType);
$dataType.MaximumLength = 1000;
$newColumn = New-Object Microsoft.SqlServer.Management.Smo.Column($newTable,$col.ColumnName,$dataType);
$newColumn.DataType = $dataType;
$newTable.Columns.Add($newColumn);
}
$newTable.Create();
#bcp data into new table
$connectionString = "Data Source=$serverName;Integrated Security=true;Initial Catalog=$databaseName;"
$bc = New-Object ("Data.SqlClient.SqlBulkCopy") $connectionString
$bc.DestinationTableName = "$tableName"
$bc.WriteToServer($dataTable)

Related

Powershell Gui not returning SQL query results

I'm building a simple GUI where support staff can look up basic user information. The GUI is supposed to pull this data from an SQL DB, but nothing happens when the button is pressed.
function SQLquery {
param (
[Parameter(Mandatory = $true)]
[ValidateSet('NemOrgDB')]
[string]
$System,
[string]
$Query
)
switch ($System) {
NemOrgDB { $ConnectionString = 'Place holder for Stack Overflow' }
}
#New SqlConnection object
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = $ConnectionString
$sqlConnection.Open()
#New Sqlcommand object
$sqlCommand = New-Object System.Data.SqlClient.SqlCommand
$sqlCommand.Connection = $sqlConnection
$sqlCommand.CommandText = $Query
#Create new sqldataadapter object
$sqlDataAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$sqlDataAdapter.SelectCommand = $sqlCommand
#Create new dataset object
$DataSet = New-Object System.Data.DataSet
#Fill dataset with dataadapter input
try {
$sqlDataAdapter.Fill($DataSet) | Out-Null
$output = $DataSet.Tables
}
catch {
$output = $null
}
#Close sql connection
$sqlConnection.close()
$null = $sqlConnection
$output
}
Here is the Function in use:
#Søgnigns criteria
$EmployeeID = Read-Host -Prompt "What user to find?"
#SQL Query
SQLquery -System NemOrgDB -Query "Select [medarbejder_wnr]
,[navn]
,[stilling]
,[stilling_nr]
,[firmakode_txt]
FROM [PersonData_NemOrg].[dbo].[Personale]
WHERE medarbejder_wnr ='$EmployeeID';"
The 2 pieces of code work as they should in a console environment with no Gui attached, they return the following results:
EmployeeID: EmployeeID
Name: Name
Role: IT support
Role number : Number
fimakode_txt : Company
When the same code is run inside the GUI nothing happens, here is the GUI code:
#ButtonSearch
#
$ButtonSearch.BackColor = [System.Drawing.SystemColors]::ActiveBorder
$ButtonSearch.FlatStyle = [System.Windows.Forms.FlatStyle]::System
$ButtonSearch.Font = (New-Object -TypeName System.Drawing.Font -ArgumentList #([System.String]'Tahoma',[System.Single]9,[System.Drawing.FontStyle]::Bold,[System.Drawing.GraphicsUnit]::Point,([System.Byte][System.Byte]0)))
$ButtonSearch.Location = (New-Object -TypeName System.Drawing.Point -ArgumentList #([System.Int32]12,[System.Int32]118))
$ButtonSearch.Name = [System.String]'ButtonSearch'
$ButtonSearch.Size = (New-Object -TypeName System.Drawing.Size -ArgumentList #([System.Int32]150,[System.Int32]23))
$ButtonSearch.TabIndex = [System.Int32]24
$ButtonSearch.Text = [System.String]'Search'
$ButtonSearch.UseCompatibleTextRendering = $true
$ButtonSearch.UseVisualStyleBackColor = $false
$ButtonSearch.add_Click($ButtonSearch_Click)
#
AND
$ButtonSearch_Click = {
$Wnummer = $TextBoxWnummer1.Text
SQLquery -System NemOrgDB -Query "Select [medarbejder_wnr]
,[navn]
,[stilling]
,[stilling_nr]
,[firmakode_txt]
FROM [PersonData_NemOrg].[dbo].[Personale]
WHERE medarbejder_wnr ='$Wnummer';"
}
It simply returns nothing.

Using PowerShell For Developing A Notification For An RSS News Alert

I have this basic code that works fine for simple text message alert. Now, it would be handy to connect this script to alert the user whenever there is a new RSS feed from our ITS alert system.
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$objNotifyIcon = New-Object System.Windows.Forms.NotifyIcon
$objNotifyIcon.Icon = [System.Drawing.SystemIcons]::Information
$objNotifyIcon.BalloonTipIcon = "Info"
$text = 'This is just a text'
$objNotifyIcon.BalloonTipText = $text
$objNotifyIcon.BalloonTipTitle = "Tip Title"
$objNotifyIcon.Visible = $True
$objNotifyIcon.ShowBalloonTip(30000)
Here is an idea for auto-close:
Function Get-BalloonTip {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]$Text,
[Parameter(Mandatory=$true)]$Title,
$Icon = 'Info',
$Timeout = $10000
)
Process {
Add-Type -AssemblyName System.Windows.Forms
If ($PopUp -eq $null) {
$PopUp = New-Object System.Windows.Forms.NotifyIcon
}
$Path = Get-Process -Id $PID | Select-Object -ExpandProperty Path
$PopUp.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($Path)
$PopUp.BalloonTipIcon = $Icon
$PopUp.BalloonTipText = $Text
$PopUp.BalloonTipTitle = $Title
$PopUp.Visible = $true
$PopUp.ShowBalloonTip($Timeout)
Start-Sleep 5
$PopUp.Visible = $false
} # End of Process
} # End of Function
Get-BalloonTip -Text "Hello" "Check This out"

Don’t display Cancel in PowerShell script result

I have the following PowerShell script which displays file dialog to select a txt file. If user cancels dialog then provide a multiline text box
function GetDetails() {
Add-Type -AssemblyName System.Windows.Forms;
$browser = New-Object System.Windows.Forms.OpenFileDialog;
$browser.Filter = "txt (*.txt)|*.txt";
$browser.InitialDirectory = "E:\";
$browser.Title = "select txt file";
$browserResult = $browser.ShowDialog();
if($browserResult -eq [System.Windows.Forms.DialogResult]::OK) {
$nfoFile = $browser.FileName;
if([string]::IsNullOrWhiteSpace($txtFile)) {
return GetFromForm;
}
$txtFile = [System.IO.Path]::ChangeExtension($nfoFile, ".dac");
$txtFile = $temp + [System.IO.Path]::GetFileName($txtFile);
$exeArgs = "-f -S `"$txtFile`" -O `"$txtFile`"";
Start-Process $anExe -ArgumentList $exeArgs -Wait;
$result = Get-Content $txtFile | Out-String;
$browser.Dispose();
return $result;
} else {
return GetFromForm;
}
}
function GetFromForm(){
Add-Type -AssemblyName System.Windows.Forms;
$form = New-Object System.Windows.Forms.Form;
$form.Width = 800;
$form.Height = 600;
$txtBox = New-Object System.Windows.Forms.TextBox;
$txtBox.Multiline = $true;
$txtBox.AcceptsReturn = $true;
$txtBox.AcceptsTab = $true;
$txtBox.Visible = $true;
$txtBox.Name = "txtName";
$txtBox.Width = 760;
$txtBox.Height = 660;
$form.Controls.Add($txtBox);
$form.ShowDialog();
 
$form.Dispose();
return $txtBox.Text;
}
$desc = GetDetails;
cls;
Write-Host $desc;
Here I have two issues:
In Write-Host $desc, prints also Cancel hereiswhateverstrimg string if user chose to cancel dialog. How to avoid that?
If I run script in ISE, the generated form (in second function) will be always behind ISE even I call ShowDialog(), I expected to behave as modal dialog. It’s normal or there is a fix for this ?
Ok, there are a few changes that I made for efficiency and a few for functionality. Read the comments in the script for the explanations.
# Just add types once. There is no need to add the types in each function.
Add-Type -AssemblyName System.Windows.Forms;
Add-Type -AssemblyName System.Drawing
function GetDetails() {
$browser = New-Object System.Windows.Forms.OpenFileDialog;
$browser.Filter = "txt (*.txt)|*.txt";
$browser.InitialDirectory = "E:\";
$browser.Title = "select txt file";
$browserResult = $browser.ShowDialog();
# Combined the if statements
if($browserResult -eq [System.Windows.Forms.DialogResult]::OK -and [string]::IsNullOrWhiteSpace($txtFile) -ne $true) {
$nfoFile = $browser.FileName;
$txtFile = [System.IO.Path]::ChangeExtension($nfoFile, ".dac");
$txtFile = $temp + [System.IO.Path]::GetFileName($txtFile);
$exeArgs = "-f -S `"$txtFile`" -O `"$txtFile`"";
Start-Process $anExe -ArgumentList $exeArgs -Wait;
# The Raw flag should return a string
$result = Get-Content $txtFile -Raw;
$browser.Dispose();
return $result;
}
# No need for else since the if statement returns
return GetFromForm;
}
function GetFromForm(){
$form = New-Object System.Windows.Forms.Form;
$form.Text = 'Adding Arguments'
$form.Size = New-Object System.Drawing.Size(816,600)
$form.StartPosition = 'CenterScreen'
# Added a button
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Point(585,523)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = 'OK'
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $OKButton
$form.Controls.Add($OKButton)
$txtBox = New-Object System.Windows.Forms.TextBox;
$txtBox.Multiline = $true;
$txtBox.AcceptsReturn = $true;
$txtBox.AcceptsTab = $true;
$txtBox.Visible = $true;
$txtBox.Name = "txtName";
$txtBox.Size = New-Object System.Drawing.Size(660,500)
$form.Controls.Add($txtBox);
# Needed to force it to show on top
$form.TopMost = $true
# Select the textbox and activate the form to make it show with focus
$form.Add_Shown({$txtBox.Select(), $form.Activate()})
# Finally show the form and assign the ShowDialog method to a variable (this keeps it from printing out Cancel)
$result = $form.ShowDialog();
# If the user hit the OK button return the text in the textbox
if ($result -eq [System.Windows.Forms.DialogResult]::OK)
{
return $txtBox.Text
}
}
$desc = GetDetails;
cls;
Write-Host $desc;
You can see reference material here: https://learn.microsoft.com/en-us/powershell/scripting/samples/creating-a-custom-input-box?view=powershell-6
You need to suppress output of $form.ShowDialog() in GetFromForm:
$form.ShowDialog()|out-null
Powershell will add to a return value everything that was outputted to host within a function/commandlet.
Regarding your second issue - see this answer
And please do not use semi-colon at an end of line. This is not C# and will confuse you into thinking that the line is ended here but it's not quite true.

Array behaviour difference in PowerShell 2 to PowerShell 3

I have observed a difference in behavior between PowerShell 2.0 code and 3.0 code but can't seem to fix it. I am hoping I can get some help. Please see code below.
If you run this in V2 (please change ServerA and ServerB to a server on your network) you will see after selecting and clicking the button, write-host shows them as seperate names. In version 3 the listbox populates it as 1.
$Servers = #()
[array]$ServerstoPing = ("ServerA","ServerB")
# Generate the form
Add-Type -AssemblyName System.Windows.Forms
$Form1 = New-Object system.Windows.Forms.Form
$Form1.Text = "Ping Some Servers"
$Font = New-Object System.Drawing.Font("Calibri",18,[System.Drawing.FontStyle]::regular)
$Fontboxes = New-Object System.Drawing.Font("Lucida Sans",8,[System.Drawing.FontStyle]::regular)
$Fontdate = New-Object System.Drawing.Font("Lucida Sans",11,[System.Drawing.FontStyle]::bold)
$FontProgress = New-Object System.Drawing.Font("Lucida Sans",11,[System.Drawing.FontStyle]::bold)
$Form1.Font = $Font
$Serverlistbox = New-Object System.Windows.Forms.Listbox
$Serverlistbox.Location = New-Object System.Drawing.Size(11,137)
$Serverlistbox.Size = New-Object System.Drawing.Size(200,170)
$Serverlistbox.SelectionMode = "MultiExtended"
$Serverlistbox.font = $Fontboxes
foreach($item in $ServerstoPing){
[void] $Serverlistbox.Items.Add("$item")
}
$Form1.Controls.Add($Serverlistbox)
$SubmitButton = New-Object System.Windows.Forms.button
$SubmitButton.text = "Ping Servers"
$SubmitButton.Location = New-Object System.Drawing.Size(171,305)
$SubmitButton.AutoSize = $True
$Form1.Controls.Add($SubmitButton)
$Form1.StartPosition = "CenterScreen"
$OutputPing = New-Object System.Windows.Forms.TextBox
$OutputPing.MultiLine = $True
$OutputPing.ScrollBars = "Vertical"
$OutputPing.text = "Server ping status will be displayed here once done'."
$OutputPing.font = $Fontboxes
$OutputPing.Location = New-Object System.Drawing.Size(226,40)
$OutputPing.Size = New-Object System.Drawing.Size(200,255)
$Form1.Controls.Add($OutputPing)
$SubmitButton.add_click({
#foreach ($objItem in $ServerstoPing)
foreach ($objItem in $Serverlistbox.SelectedItems)
{
$Servers += $objItem
}
write-host $ServerstoPing
Foreach($server in $servers)
{
ping -n 1 -w 1000 $server | out-null
if($? -eq $true){
write-host ("$server ping success")
}
else
{
write-host ("$server did not respond to ping, not extracting events")
}
}
})
$Form1.ShowDialog()

Access datarow from a workflow

I have a script which look like below:
function Invoke-SQL {
param(
[string] $dataSource = ".\MSSQLSERVER",
[string] $database = "master",
[string] $sqlCommand = $(throw "Please specify a query.")
)
$connectionString = "Data Source=$dataSource; Integrated Security=True; Initial Catalog=$database; Connect Timeout=100"
$connection = new-object system.data.SqlClient.SQLConnection($connectionString)
$command = new-object system.data.sqlclient.sqlcommand($sqlCommand,$connection)
Try {
$connection.Open()
} catch {
return, $null
}
$adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataSet) | Out-Null
$connection.Close()
$dataSet.Tables
}
[string]$SQL = "select CmsSrvName from CmsServerList_VM"
$DbInstances = Invoke-SQL "DBSRV" "Test" $SQL
workflow wf1{
Param([System.Data.DataTable]$instance)
ForEach -Parallel -ThrottleLimit 10 ($i in $instance) {
InlineScript{
$t = $using:i
Write-Verbose "$t['CmsSrvName']"
}
}
}
wf1 -Verbose $DbInstances
Output:
VERBOSE: [localhost]:System.Data.DataRow['CmsSrvName']
VERBOSE: [localhost]:System.Data.DataRow['CmsSrvName']
VERBOSE: [localhost]:System.Data.DataRow['CmsSrvName']
The output is not what I expected, it just print out the type name not the value. How can I access the DataRow value in a workflow?(in Powershell 5)
Thanks in advance for the help