how to access string indexer on a collection in Powershell - powershell

Slowly learning Powershell ... I'm working on a script to query a third party AD/AM database (ldap). The specific LDAP property name that I want has a hyphen in the name.
I can do this in c# without thinking about it, but I don't want to fire up Visual Studio just to do some simple scripting stuff that changes frequently.
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
.....
$results = $objSearcher.FindAll()
foreach($result in $results) {
$item = $result.Properties
$item.some-property # this fails because of '-'
$result['some-property'] # 'Unable to index into an object of type System.DirectoryServices.SearchResult.'
}

You can also specify the property name via a variable:
$prop = 'some-property'
$result.$prop

You need to place curly braces around the hyphenated property name. This should work:
$item.{some-property}

Related

Remove users from application using LithnetRMA

Im using LithnetRMA to update MIM application membership.
I have multiple users who are linked to an application (ObjectID: 168b8077-052c-489d-b4c0-1700ff817d1f)
I would like to remove several users who have left the company. Not sure if im on the right path?
# Load the Lithnet Service PowerShell module
Import-Module LithnetRMA
$userList = #"
ObjectID;
139b8058-052c-459d-b3c0-1600fo417d1a
195b8057-042c-439d-b2c0-1800fp117d1b
169b8047-022c-449d-b0c0-1900fi217d1c
162b8037-012c-489d-b1c0-1000fu417d1d
164b8027-002c-488d-b8c0-1300fy317d1e
163b8017-012c-449d-b7c0-1500fr517d1f
"#
#Remove users from the application
foreach($item in $userList) {
#Get the users and application objects
$user = Get-Resource -ID $item.ObjectID
$application = Get-Resource -ID "168b8077-052c-489d-b4c0-1700ff817d1f"
$application.ExplicitMember.Remove($item.ObjectID)
Save-Resource -Resources $application
}
I can't speak to the LithnetRMA part, but it looks like your intent is to define $userList as an array of objects that have an .ObjectId property, but in reality you're simply assigning a single, multiline string.
Therefore, change your assignment as follows:
# Parse the string into objects that have an .ObjectID property.
$userList = ConvertFrom-Csv #"
ObjectID
139b8058-052c-459d-b3c0-1600fo417d1a
195b8057-042c-439d-b2c0-1800fp117d1b
169b8047-022c-449d-b0c0-1900fi217d1c
162b8037-012c-489d-b1c0-1000fu417d1d
164b8027-002c-488d-b8c0-1300fy317d1e
163b8017-012c-449d-b7c0-1500fr517d1f
"#
ConvertFrom-Csv is used to parse the multiline string into the objects of interest, by interpreting the string as single-column CSV data.
Note that the trailing ; was removed from the ObjectID line so that the ; doesn't become part of the property name. (It would be ignored if you used ConvertFrom-Csv -Delimiter ';', however.)

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) { ... }

add double quotes to variable that came from array

I'm creating a function that does some things to Excel files. The list of Excel files are passed into the function as an array called $excelFiles. The code shown below is in progress (it does not yet do all the things it's intended to do). This code, as written so far, appears to be failing because there are no quotes around the string held in $excelFile that sets the $wb variable (right before the nested foreach):
Function CovertExcelFileToTextFiles ($excelFiles)
{
# create an Excel application object (fire off Excel in the background)
$excelApp = New-Object -ComObject Excel.Application
$excelApp.Visible = $false
$excelApp.DisplayAlerts = $false
# get first 3 letters of each file's name
foreach ($excelFile in $excelFiles)
{
$name = Split-Path $excelFile.FullName -Leaf #get filename only from full path
$prefix = $name.Substring(0,3) #get first 3 letters of filename
#look at contents of this variable
$excelFile
$wb = $excelApp.Workbooks.Open($excelFile)
foreach ($ws in $wb.Worksheets)
{
$n = $prefix + "_"+ $ws.Name
$n
}
}
$excelApp.Quit()
}
Here is the error that appears in the console:
The reason I suspect the problem is lack of quotes is because the code works if $wb is set to a hardcoded file path.
I'm having difficulty figuring out how to get double quotes around the variable to feed into the line that sets $wb. I have tried "$excelFile" and the editor puts a red squiggly line under it so apparently that's not allowed. I have also tried creating a new variable and populating that with "$excelFile", then plugging that into the parenthesis in the $wb line. That causes an error in the console as well. How can double quotes be put around $excelFile?
Lack of quotes is not your issue. When you hardcode a file and use quotes, the quotes tell the parser that it's a string; they aren't part of the string value.
You need to know what data type $excelFile is (that is, $excelFiles is an array of what?).
If your use of $excelFile.FullName is correct, then it seems you're dealing with an object.
In that case, the .Open() method is likely expecting a [String] and won't understand the object you're passing it, so try:
$wb = $excelApp.Workbooks.Open($excelFile.FullName)
A good way to troubleshoot this kind of thing is to use PowerShell ISE, then set a breakpoint.
In your case, set a breakpoint on the $wb = ... line, execute, and when the breakpoint is hit, execution will stop at that line (before executing it).
At that point, you can use the console to execute statements that will be run in the context of your running code. So for example you could run:
$excelFile.GetType()
or
$excelFile | Get-Member
and learn some things about the object you're dealing with. You could look at its properties, etc.:
$excelFile.FullName
You can look at the overloads of the method you're calling:
$excelApp.Workbooks.Open # <-- note no parentheses

Dynamically assign information to an "Add_Click" event on a GUI button in powershell

I want to Dynamically assign information to an "Add_Click" event on a GUI button in powershell.
So an overview - I am making a GUI tool, that loads some buttons for custom "fixes" for peoples PC's. Each button is loaded from a script, and each script contains variables for information such as, Button Name, Button Icon, as well as a function that does whatever the script is meant to do.
So as per example below, I am finding the problem is where I assign the Add_Click event. I'm sorry not sure how to explain better, but it does not seem to expand the $script variable, it will save the add_click as the variable, so that when I run the program, all buttons are using whatever was last stored in the variable $script (so all the buttons look different, but run the same script).
What I want to be able to do, is have each button use an Add_Click to run whatever script is stored in $script at the time the Add_Click is assigned in the "Foreach" loop.
Example script:
#Button Container (Array to store all the Buttons)
$ComputerActionButtons = #()
#Get all the script files that we will load as buttons
$buttonsFileArray = Get-ChildItem "$dir\Addin\ " | Select -ExpandProperty Name
#Load Buttons
$ButtonTempCount = 1
Foreach ($script in $buttonsFileArray){
. $dir\Addin\$script
Write-Host "Loading Button: $ButtonName"
# Generate button
$TempButton = New-Object System.Windows.Forms.Button
$TempButton.Location = New-Object System.Drawing.Size(140,20)
$TempButton.Size = New-Object System.Drawing.Size(100,100)
$TempButton.Text = "$ButtonName"
$TempButton.Image = $ButtonIcon
$TempButton.TextImageRelation = "ImageAboveText"
$TempButton.Add_Click({& $dir\Addin\$script})
$ComputerActionButtons += $TempButton
}
#Add Buttons to panel
$CAButtonLoc = 20
Foreach ($button in $ComputerActionButtons){
$button.Location = New-Object System.Drawing.Size(50,$CAButtonLoc)
$ComputerActionsPanel.Controls.Add($button)
$CAButtonLoc = $CAButtonLoc + 120
}
I have been researching variables, found you can do a New-Variable and Get-Variable command to dynamically set and get variable names, but using this does not seem to help because of the original problem.
I hope I have explained this well enough, please let me know if I can give more information to help.
Thank you,
Adam
You need to create a closure over the $script value at the time of assignment, instead of relying on the value of $script at runtime.
Fortunately, all ScriptBlocks support this with the GetNewClosure() method.
foreach($i in 1..3){
$blocks += {echo $i}.GetNewClosure()
}
$blocks |Foreach-Object {$_.Invoke()}
which produces:
1
2
3
As opposed to
foreach($i in 1..3){
$blocks += {echo $i}
}
$blocks |Foreach-Object {$_.Invoke()}
which produces:
3
3
3
What GetNewClosure() does is that it "captures" the variables referenced inside the ScriptBlock at that time and binds it to the local scope of the scriptblock.
This way the original $i variable (or $script for that matter) is "hidden" beneath an $i variable that has the same value as $i used to have - namely at the time GetNewClosure() was called
Instead of using GetNewClosure, which create new dynamic module, capture local scope variables to it and bind ScriptBlock to that module, so in fact you lose access to any function defined not in global scope, you can just store any button specific data in the button itself. Control base class have Tag property where you can store any data you want.
$TempButton.Tag=#{Script=$script}
$TempButton.Add_Click({param($Sender) & "$dir\Addin\$($Sender.Tag.Script)"})

Powershell CheckedListBox check if in string / array

I've started learning Powershell and am now stuck after spending several hours on a problem I can find solutions for in multiple languages except Powershell.
I need to place a check against each item in a CheckedListBox that matches any of the values in a semi-colon delimited string named $MLBSVar_SelectedPackages. (eg. $MLBSVar_SelectedPackages = 'packageA;packageB;packageC;') and so on.
I've come up with this line but it doesn't yet work. Please can you help me?
if ($MLBSVar_SelectedPackages -ne $null) {
ForEach ($PackageName in $MLBSVar_SelectedPackages) {
ForEach ($item in $clb_SC_AvailablePackages.Items) {
if ($item -eq $PackageName) {
$clb_SC_AvailablePackages.Item($PackageName).Checked = $true
}
}
}
}
I've also tried .SetItemCheckState([System.Windows.Forms.CheckState]::Checked) in place of .Checked. The (one) issue seems to be getting a handle on the list item in the final section as it seems to be passed as a string rather than object? I have a VBS background and would really appreciate the assistance.
I think what you're looking for is something like the following code. You can use the SetItemChecked() method of the CheckedListBox class to check an item at a particular index. I see that you have attempted to use SetItemCheckState(), but did not make mention of SetItemChecked().
# Import Windows Forms Assembly
Add-Type -AssemblyName System.Windows.Forms;
# Create a Form
$Form = New-Object -TypeName System.Windows.Forms.Form;
# Create a CheckedListBox
$CheckedListBox = New-Object -TypeName System.Windows.Forms.CheckedListBox;
# Add the CheckedListBox to the Form
$Form.Controls.Add($CheckedListBox);
# Widen the CheckedListBox
$CheckedListBox.Width = 350;
$CheckedListBox.Height = 200;
# Add 10 items to the CheckedListBox
$CheckedListBox.Items.AddRange(1..10);
# Clear all existing selections
$CheckedListBox.ClearSelected();
# Define a list of items we want to be checked
$MyArray = 1,2,5,8,9;
# For each item that we want to be checked ...
foreach ($Item in $MyArray) {
# Check it ...
$CheckedListBox.SetItemChecked($CheckedListBox.Items.IndexOf($Item), $true);
}
# Show the form
$Form.ShowDialog();
After running this code, you should be presented with a dialog that looks similar to the following screenshot.