Powershell input box with multiple inputfields - powershell

I've found this code to create simple input box for powershell:
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
$title = 'Demographics'
$msg = 'Enter your demographics:'
$text = [Microsoft.VisualBasic.Interaction]::InputBox($msg, $title)
Does anyone know how to add more input fields?

WPF, XAML, .NET, and ShowUI are all options available to you.
https://learn-powershell.net/2012/09/13/powershell-and-wpf-introduction-and-building-your-first-window/
http://showui.codeplex.com/
alternatively, here is an example using the Show-Command cmdlet
function askforinfo {
param (
[string]$Demographics,
[validateset(1, 2, 3)]
[string]$otherstuff
)
[pscustomobject]#{
Demographics = $Demographics
OtherStuff = $otherstuff
}
}
$result = Invoke-Expression (Show-Command askforinfo -PassThru)
$result

Related

How to implement `DynamicParam` through dynamic parameters?

Assumption:
$a = 1,2,3
if $a =1,2
Intent:Display parameter b
PS >test -<tab>
a
PS >test -a 1 -<tab>
b
PS >test -a 3 -<tab>
PS >test -a 3
How to achieve the following intent
Function test {
[CmdletBinding()]
Param()
DynamicParam {
parameter a
Dynamic parameter b (a is 1 =$true)
Dynamic parameter c (b is 3)
???
}
}
You don't have to assume. Research it.
What Dynamics Params are and how to use them if a fully documented use case via the MS docs site and many blog posts.
Searching for 'powershell dynamics parameters', will give you a solid list. Be sure to read the details, so as to limit any confusion about their use case.
Examples:
How can I pass dynamic parameters to powershell script and iterate
over the list?
Cmdlet dynamic parameters
PowerShell Deep Dive: Discovering dynamic parameters
Dynamic Parameters in PowerShell
How To Implement Dynamic Parameters in Your PowerShell Functions
enter link description here
# Example from the above link.
function Get-ConfigurationFile
{
[OutputType([System.IO.FileInfo])]
[CmdletBinding()]
param
()
DynamicParam
{
$ParamAttrib = New-Object System.Management.Automation.ParameterAttribute
$ParamAttrib.Mandatory = $true
$ParamAttrib.ParameterSetName = '__AllParameterSets'
$AttribColl = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$AttribColl.Add($ParamAttrib)
$configurationFileNames = Get-ChildItem -Path 'C:\ConfigurationFiles' | Select-Object -ExpandProperty Name
$AttribColl.Add((New-Object System.Management.Automation.ValidateSetAttribute($configurationFileNames)))
$RuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter('FileName', [string], $AttribColl)
$RuntimeParamDic = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$RuntimeParamDic.Add('FileName', $RuntimeParam)
return $RuntimeParamDic
}
process
{
$configFileFolder = 'C:\ConfigurationFiles'
Get-ChildItem -Path $configFileFolder -Filter "$($PSBoundParameters.FileName).txt"
}
}
As for this...
As you can see,tabs cannot return a parameter
..., what you are showing is not the way this use case works.
Based on what you seem to be after, this sampler should get you there. please read the whole article for a better understanding.
Dynamic Parameters in PowerShell
Function Get-Order {
[CmdletBinding()]
Param(
[Parameter(
Mandatory=$true,
Position=1,
HelpMessage="How many cups would you like to purchase?"
)]
[int]$cups,
[Parameter(
Mandatory=$false,
Position=2,
HelpMessage="What would you like to purchase?"
)]
[ValidateSet("Lemonade","Water","Tea","Coffee")]
[string]$product="Lemonade"
)
Process {
$order = #()
for ($cup = 1; $cup -le $cups; $cup++) {
$order += "$($cup): A cup of $($product)"
}
$order
}
}
Or this one...
Using Dynamic Parameters
function Test-Department
{
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true)]
[ValidateSet('Microsoft','Amazon','Google','Facebook')]
$Company
)
dynamicparam
{
# this hash table defines the departments available in each company
$data = #{
Microsoft = 'CEO', 'Marketing', 'Delivery'
Google = 'Marketing', 'Delivery'
Amazon = 'CEO', 'IT', 'Carpool'
Facebook = 'CEO', 'Facility', 'Carpool'
}
# check to see whether the user already chose a company
if ($Company)
{
# yes, so create a new dynamic parameter
$paramDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
$attributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
# define the parameter attribute
$attribute = New-Object System.Management.Automation.ParameterAttribute
$attribute.Mandatory = $false
$attributeCollection.Add($attribute)
# create the appropriate ValidateSet attribute, listing the legal values for
# this dynamic parameter
$attribute = New-Object System.Management.Automation.ValidateSetAttribute($data.$Company)
$attributeCollection.Add($attribute)
# compose the dynamic -Department parameter
$Name = 'Department'
$dynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($Name,
[string], $attributeCollection)
$paramDictionary.Add($Name, $dynParam)
# return the collection of dynamic parameters
$paramDictionary
}
}
end
{
# take the dynamic parameters from $PSBoundParameters
$Department = $PSBoundParameters.Department
"Chosen department for $Company : $Department"
}
}
As well as this step by step one with full explanations...
How To: Add Dynamic Parameters to Your PowerShell Functions
As noted in my initial response, there are plenty of examples of this use case, but it requires study to fully grasp the concept, and how to use it/them.

PowerShell - Passing hashtables to a function

I'm in the process of writing a CLI for a thing I'm working on, and at various points in the process I want to ask the user for input. Each time I ask the user for input, the questions/answers are likely to be different so I started out with something like:
$choices = [Management.Automation.Host.ChoiceDescription[]] #(
New-Object Management.Automation.Host.ChoiceDescription("&Yes","Option Description for Yes")
New-Object Management.Automation.Host.ChoiceDescription("&No","Option Description for No.")
)
$choice = $Host.UI.PromptForChoice("Question Title","Question Text",$choices,1)
This works pretty well, but it's a bit clunky when it comes to re-use especially if the number of choices expands.
I want to wrap it in a function that I can call easier - like:
$options = #{
Yes = "Option Description for Yes"
No = "Option Description for No"
}
askQuestion -title "Question Title" -question "Question Text" -options $options
So far so good. The bit I'm struggling with is accessing the properties of $options:
function askQuestion {
param (
[hashtable]$options,
[string]$title,
[string]$question
)
Write-Host $title -ForegroundColor Cyan
Write-Host $question -ForegroundColor Cyan
foreach($option in $options)
{
# Do stuff HERE
}
}
If I just access $option directly in the foreach, it prints out a table like:
Name Value
---- -----
No Option Description for No
Yes Option Description for Yes
If I try accessing $option.Name or .Value it doesn't seem to do anything at all.
Can someone point out where I'm going wrong with this please?
I think you can use the GetNumerator() method to iterate through the hash table entries. Then create a custom message using the format operator -f. $i here is just something to track a number for each line in the output. This should be fairly dynamic provided your values/descriptions are consistently worded so there are no grammatical/comprehension issues.
$i = 1
foreach ($option in $options.GetEnumerator()) {
"{2}. Enter {0} for {1}" -f $option.key,$option.value,$i++
}
For anyone interested, this is what this finished up looking like:
function askQuestion {
param (
[hashtable]$options,
[string]$title,
[string]$question
)
$choices = [Management.Automation.Host.ChoiceDescription[]] #(
foreach ($option in $options.GetEnumerator()) {
$selection = $option.key
$description = $option.value
New-Object Management.Automation.Host.ChoiceDescription("&$selection",$description)
}
)
$choice = $Host.UI.PromptForChoice($title,$question,$choices,1)
return
}
And it can be called with something like this, where $options is very flexible.
$options = #{
Yes = "Yes Description"
No = "No Description"
Maybe = "Maybe Description"
}
askQuestion -title "Question Title" -question "Question Text" -options $options

Send Outlook Task Reminder Using Powershell

I wish to send an email outlook task with reminder by using powershell, is that possible? The task similar as image below:
Is powershell built in function able to create this task instead of create a normal email using "new-object Net.Mail.MailMessage"? Any sample code/documentation to refer?
A quick google search brought up these
https://blogs.technet.microsoft.com/heyscriptingguy/2008/02/21/hey-scripting-guy-how-can-i-set-a-reminder-on-all-my-office-outlook-appointments/
http://www.leeholmes.com/blog/2007/03/01/getting-things-done-outlook-task-automation-with-powershell/
http://www.amandhally.net/2013/08/08/powershell-and-outlook-create-calendar-meetings-using-powershell-function/
No, that is not what the does. That is a .Net class as documented here.
Mail​Message Class
To use PowerShell with Outlook, you have to use the Outlook Object Model (DCOM). There are lots of examples of using PowerShell with Outlook all over the web. Doing a search will bring those up for you.
Here is just one example of dealing with Outlook Tasks with a few other references for you.
Managing an Outlook Mailbox with PowerShell
Getting Things Done – Outlook Task Automation with PowerShell
## Add-OutlookTask.ps1
## Add a task to the Outlook Tasks list
param( $description = $(throw "Please specify a description"), $category, [switch] $force )
## Create our Outlook and housekeeping variables.
## Note: If you don't have the Outlook wrappers, you'll need
## the commented-out constants instead
$olTaskItem = "olTaskItem"
$olFolderTasks = "olFolderTasks"
#$olTaskItem = 3
#$olFolderTasks = 13
$outlook = New-Object -Com Outlook.Application
$task = $outlook.Application.CreateItem($olTaskItem)
$hasError = $false
## Assign the subject
$task.Subject = $description
## If they specify a category, then assign it as well.
if($category)
{
if(-not $force)
{
## Check that it matches one of their already-existing categories, but only
## if they haven't specified the -Force parameter.
$mapi = $outlook.GetNamespace("MAPI")
$items = $mapi.GetDefaultFolder($olFolderTasks).Items
$uniqueCategories = $items | Sort -Unique Categories | % { $_.Categories }
if($uniqueCategories -notcontains $category)
{
$OFS = ", "
$errorMessage = "Category $category does not exist. Valid categories are: $uniqueCategories. " +
"Specify the -Force parameter to override this message in the future."
Write-Error $errorMessage
$hasError = $true
}
}
$task.Categories = $category
}
## Save the item if this didn't cause an error, and clean up.
if(-not $hasError) { $task.Save() }
$outlook = $null
See also this module:
Powershell and Outlook: Create a New Outlook Task using Powershell OutlookTools Module
https://github.com/AmanDhally/OutlookTools

How can I get programmatic access to the "Date taken" field of an image or video using powershell?

I'm trying convert a bunch of pictures and videos, but when I convert it to a new format I obviously lose the properties of the original file. I'd like to be able to read the "Date taken" property from the old file and update it on the new one using powershell.
I can't test it right now (don't have any images with XIF data laying around, but I think this should work:
[reflection.assembly]::LoadWithPartialName("System.Drawing")
$pic = New-Object System.Drawing.Bitmap('C:\PATH\TO\SomePic.jpg')
$bitearr = $pic.GetPropertyItem(36867).Value
$string = [System.Text.Encoding]::ASCII.GetString($bitearr)
$DateTime = [datetime]::ParseExact($string,"yyyy:MM:dd HH:mm:ss`0",$Null)
$DateTime
In general, you can access any extended property for a file shown in explorer through the shell GetDetailsOf method. Here's a short example, adapted from another answer:
$file = Get-Item IMG_0386.jpg
$shellObject = New-Object -ComObject Shell.Application
$directoryObject = $shellObject.NameSpace( $file.Directory.FullName )
$fileObject = $directoryObject.ParseName( $file.Name )
$property = 'Date taken'
for(
$index = 5;
$directoryObject.GetDetailsOf( $directoryObject.Items, $index ) -ne $property;
++$index ) { }
$value = $directoryObject.GetDetailsOf( $fileObject, $index )
However, according to the comments on another question, there is no general-purpose mechanism for setting these properties. The System.Drawing.Bitmap class that EBGreen mentioned will work for images, but I'm afraid I also do not know of a .NET option for video files.
This works for me, thanks to the above help and others.
try{
Get-ChildItem C:\YourFolder\Path | Where-Object {$_.extension -eq '.jpg'} |
ForEach-Object {
$path = $_.FullName
Add-Type -AssemblyName System.Drawing
$bitmap = New-Object System.Drawing.Bitmap($path)
$propertyItem = $bitmap.GetPropertyItem(36867)
$bytes = $propertyItem.Value
$string = [System.Text.Encoding]::ASCII.GetString($bytes)
$dateTime = [DateTime]::ParseExact($string,"yyyy:MM:dd HH:mm:ss`0",$Null)
$bitmap.Dispose()
$_.LastWriteTime = $dateTime
$_.CreationTime = $dateTime
}}
finally
{
}
To read and write the "date taken" property of an image, use the following code (building on the answer of #EBGreen):
try
{
$path = "C:\PATH\TO\SomePic.jpg"
$pathModified = "C:\PATH\TO\SomePic_MODIFIED.jpg"
Add-Type -AssemblyName System.Drawing
$bitmap = New-Object System.Drawing.Bitmap($path)
$propertyItem = $bitmap.GetPropertyItem(36867)
$bytes = $propertyItem.Value
$string = [System.Text.Encoding]::ASCII.GetString($bytes)
$dateTime = [DateTime]::ParseExact($string,"yyyy:MM:dd HH:mm:ss`0",$Null)
$dateTimeModified = $dateTime.AddDays(1) # Set new date here
$stringModified = $dateTimeModified.ToString("yyyy:MM:dd HH:mm:ss`0",$Null)
$bytesModified = [System.Text.Encoding]::ASCII.GetBytes($stringModified)
$propertyItem.Value = $bytesModified
$bitmap.SetPropertyItem($propertyItem)
$bitmap.Save($pathModified)
}
finally
{
$bitmap.Dispose()
}

Render SSRS Report with parameters using SOAP in Powershell

I've been toying with this for days with no luck. Essentially I'm trying to build a simple library to render SSRS reports using Powershell. I'm using Powershell in an attempt to ease development later on (Instead of coding a C# app for each project). Mostly this will be used to schedule various things with reports.
I've got report rendering mostly working in Powershell. The one thing I can't figure out is how to supply parameters to the report before calling the render method. I've found plenty of code pertaining to C# and VB (which I've used in other SSRS projects), however I'm unable to convert this to Powershell.
As I'm fairly new to Powershell, I'm unfamiliar with the proper way to do this. Here's the code I've been using:
$ReportExecutionURI = "http://glitas10//ReportServer//ReportExecution2005.asmx?wsdl"
$ReportPath = "/Financial/ExpenseReportStub"
$format = "PDF"
$deviceInfo = "<DeviceInfo><NoHeader>True</NoHeader></DeviceInfo>"
$extension = ""
$mimeType = ""
$encoding = ""
$warnings = $null
$streamIDs = $null
$Reports = New-WebServiceProxy -Uri $ReportExecutionURI -UseDefaultCredential
# Load the report
$Report = $Reports.GetType().GetMethod("LoadReport").Invoke($Reports, #($ReportPath, $null))
# Render the report
$RenderOutput = $Reports.Render($format, $deviceInfo, [ref] $extension, [ref] $mimeType, [ref] $encoding, [ref] $warnings, [ref] $streamIDs)
That works fine on reports that don't require parameters, obviously.
Any ideas on what I need to do to instantiate the proper object and pass parameters?
Here's some information on the solution that I ended up using, in case anyone else needs to do the same. It works really well.
The first approach that worked was building a DLL to use by the Powershell script. This worked fine, but it causes two problems. First, your script had to tote around a DLL. Second, this DLL was tied to a specific SSRS server. In order to access another server, you had to use multiple DLLs.
Eventually, I moved back to using a web proxy. The key here is to use namespaces so that you can instantiate a ParameterValue object. Here's the code:
# Create a proxy to the SSRS server and give it the namespace of 'RS' to use for
# instantiating objects later. This class will also be used to create a report
# object.
$reportServerURI = "http://<SERVER>/ReportServer/ReportExecution2005.asmx?WSDL"
$RS = New-WebServiceProxy -Class 'RS' -NameSpace 'RS' -Uri $reportServerURI -UseDefaultCredential
$RS.Url = $reportServerURI
# Set up some variables to hold referenced results from Render
$deviceInfo = "<DeviceInfo><NoHeader>True</NoHeader></DeviceInfo>"
$extension = ""
$mimeType = ""
$encoding = ""
$warnings = $null
$streamIDs = $null
# Next we need to load the report. Since Powershell cannot pass a null string
# (it instead just passses ""), we have to use GetMethod / Invoke to call the
# function that returns the report object. This will load the report in the
# report server object, as well as create a report object that can be used to
# discover information about the report. It's not used in this code, but it can
# be used to discover information about what parameters are needed to execute
# the report.
$reportPath = "/PathTo/Report"
$Report = $RS.GetType().GetMethod("LoadReport").Invoke($RS, #($reportPath, $null))
# Report parameters are handled by creating an array of ParameterValue objects.
$parameters = #()
$parameters += New-Object RS.ParameterValue
$parameters[0].Name = "Parameter 1"
$parameters[0].Value = "Value"
$parameters += New-Object RS.ParameterValue
$parameters[1].Name = "Parameter 2"
$parameters[1].Value = "Value"
# Add the parameter array to the service. Note that this returns some
# information about the report that is about to be executed.
$RS.SetExecutionParameters($parameters, "en-us") > $null
# Render the report to a byte array. The first argument is the report format.
# The formats I've tested are: PDF, XML, CSV, WORD (.doc), EXCEL (.xls),
# IMAGE (.tif), MHTML (.mhtml).
$RenderOutput = $RS.Render('PDF',
$deviceInfo,
[ref] $extension,
[ref] $mimeType,
[ref] $encoding,
[ref] $warnings,
[ref] $streamIDs
)
# Convert array bytes to file and write
$Stream = New-Object System.IO.FileStream("output.pdf"), Create, Write
$Stream.Write($RenderOutput, 0, $RenderOutput.Length)
$Stream.Close()
It seems rather easy, and it is. This method works exceptionally well and is the method I'm using now to render and email scheduled reports, as it provides much more flexibility than the built in SSRS scheduling. In addition, it's relatively fast. One of the scripts I'm using to mail out reports can render and send out about 20-30 reports a minute.
I had a similar issue.
It took time to figure out the issue.
You should not "revoke" the report without parameters if needed
Therefore the code should look like this:
try {
<# Despose and clear resources if open #>
if ($RS) { $RS.Dispose() }
if ($Stream) { $Stream.Close() }
<# Create Report Service #>
[string]$reportServerURI = "<SSRS Service URL>"
$RS = New-WebServiceProxy -Class 'RS' -NameSpace 'RS' -Uri $reportServerURI -UseDefaultCredential
$RS.Url = $reportServerURI
<# Set up some variables to hold referenced results from Render #>
$deviceInfo = "<DeviceInfo><NoHeader>True</NoHeader></DeviceInfo>"
$extension = ""
$mimeType = ""
$encoding = ""
$warnings = $null
$streamIDs = $null
<# Initial Report #>
$reportPath = "<Full path/URL to rdl file>"
## Do not revoke the report ## $Report = $RS.GetType().GetMethod("LoadReport").Invoke($RS, #($reportPath, $null))
<# Initial Report Parameters Array #>
$Parameters = $RS.GetType().GetMethod("LoadReport").Invoke($RS, #($reportPath, $null)).Parameters
<# Populate Report Parameters values #>
$Params = #()
Foreach ($Parameter in $Parameters ) {
$par1 = New-Object RS.ParameterValue;
$Par1.Name = $Parameter.Name;
$Par1.Label = $Parameter.Name;
switch ($Par1.Name) {
"<1st Param Name>" { $par1.Value = <1st Param Value>; break }
"<2nd Param Name>" { $par1.Value = <2nd Param Value>; break }
...
"<#n Param Name>" { $par1.Value = <#n Param Value>; break }
}
$Params += $Par1;
}
<# Execute/invoke the report with the parameters #>
$RS.SetExecutionParameters($Params, "en-us") > $null
<# Set report render output format#>
[string]$format = <"PDF","Excel" etc.>
<# Eecute Report render #>
try { $RenderOutput = $RS.Render($format,
$deviceInfo,
[ref] $extension,
[ref] $mimeType,
[ref] $encoding,
[ref] $warnings,
[ref] $streamIDs)
} catch { Log-Message -message "Unable to render or save the report due to an error." -IsError $true; throw
}
<# Convert array bytes to file and write #>
$Stream = New-Object System.IO.FileStream(<Final Report Output File), Create, Write
$Stream.Write($RenderOutput, 0, $RenderOutput.Length)
$Stream.Close()
if ($RS) { $RS.Dispose() }
}catch{ Log-Message -message "Error in Execute-Report, could not stream or other error." -IsError $true; throw }
Problem solved.
Had the same issue, furthermore wanted to send the generated MHT file as an email body:
The following was found to work
The old CDO.Message is the only thing I found that allows sending a MHTML file as an email body.
Below is a (working) translation of a VB program
Old but simple ;-)!
################## Send MHTML email ##############################
# use antiquated CDO to send mhtml as email body
$smtpServer = "my-mail-server"
$smtpSubject = "MHT file sent as body of email"
$smtpTo = "you#work.com"
$smtpFrom = "me#home.org"
$MHTMLfile = "my-MHT-File.mht
# e.g. from an SSRS.Render
$AdoDbStream = New-Object -ComObject ADODB.Stream
$AdoDbStream.Charset = "ascii"
$AdoDbStream.Open()
$AdoDbStream.LoadFromFile($MHTMLfile)
$CdoMessage = New-Object -ComObject CDO.Message
$CdoMessage.DataSource.OpenObject($AdoDbStream,"_Stream")
$SendUsingPort = 2
$smtpPort = 25
$cfg = "http://schemas.microsoft.com/cdo/configuration/"
$CdoMessage.Configuration.Fields.Item($cfg + "sendusing") = $SendUsingPort
$CdoMessage.Configuration.Fields.Item($cfg + "smtpserver") = $SmtpServer
$CdoMessage.Configuration.Fields.Item($cfg + "smtpserverport") = $smtpPort
$CdoMessage.To = $smtpTo
$CdoMessage.From = $smtpFrom
$CdoMessage.Subject = $smtpSubject
$CdoMessage.MimeFormatted = $true
$CdoMessage.Configuration.Fields.Update()
WRITE-HOST "Sending email"
$CdoMessage.Send()