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) { ... }
Related
Background
I have a data object in PowerShell with 4 properties, 3 of which are strings and the 4th a hashtable. I would like to arrange for a new type that is defined as a collection of this data object.
In this collection class, I wish to enforce a particular format that will make my code elsewhere in the module more convenient. Namely, I wish to override the add method with a new definition, such that unique combinations of the 3 string properties add the 4th property as a hashtable, while duplicates of the 3 string properties simply update the hashtable property of the already existing row with the new input hashtable.
This will allow me to abstract the expansion of the collection and ensure that when the Add method is called on it, it will retain my required format of hashtables grouped by unique combinations of the 3 string properties.
My idea was to create a class that extends a collection, and then override the add method.
Code so far
As a short description for my code below, there are 3 classes:
A data class for a namespace based on 3 string properties (which I can reuse in my script for other things).
A class specifically for adding an id property to this data class. This id is the key in a hashtable with values that are configuration parameters in the namespace of my object.
A 3rd class to handle a collection of these objects, where I can define the add method. This is where I am having my issue.
Using namespace System.Collections.Generic
Class Model_Namespace {
[string]$Unit
[string]$Date
[string]$Name
Model_Namespace([string]$unit, [string]$date, [string]$name) {
$this.Unit = $unit
$this.Date = $date
$this.Name = $name
}
}
Class Model_Config {
[Model_Namespace]$namespace
[Hashtable]$id
Model_Config([Model_Namespace]$namespace, [hashtable]$config) {
$this.namespace = $namespace
$this.id = $config
}
Model_Config([string]$unit, [string]$date, [string]$name, [hashtable]$config) {
$this.namespace = [Model_Namespace]::new($unit, $date, $name)
$this.id = $config
}
}
Class Collection_Configs {
$List = [List[Model_Config]]#()
[void] Add ([Model_Config]$newConfig ){
$checkNamespaceExists = $null
$u = $newConfig.Unit
$d = $newConfig.Date
$n = $newConfig.Name
$id = $newConfig.id
$checkNamespaceExists = $this.List | Where { $u -eq $_.Unit -and $d -eq $_.Date -and $n -eq $_.Name }
If ($checkNamespaceExists){
($this.List | Where { $u -eq $_.Unit -and $d -eq $_.Date -and $n -eq $_.Name }).id += $id
}
Else {
$this.List.add($newConfig)
}
}
}
Problem
I would like the class Collection_Configs to extend a built-in collection type and override the Add method. Like a generic List<> type, I could simply output the variable referencing my collection and automatically return the collection. This way, I won't need to dot into the List property to access the collection. In fact I wouldn't need the List property at all.
However, when I inherit from System.Array, I need to supply a fixed array size in the constructor. I'd like to avoid this, as my collection should be mutable. I tried inheriting from List, but I can't get the syntax to work; PowerShell throws a type not found error.
Is there a way to accomplish this?
Update
After mklement's helpful answer, I modified the last class as:
Using namespace System.Collections.ObjectModel
Class Collection_Configs : System.Collections.ObjectModel.Collection[Object]{
[void] Add ([Model_Config]$newConfig ){
$checkNamespaceExists = $null
$newConfigParams = $newConfig.namespace
$u = $newConfigParams.Unit
$d = $newConfigParams.Date
$n = $newConfigParams.Name
$id = $newConfig.id
$checkNamespaceExists = $this.namespace | Where { $u -eq $_.Unit -and $d -eq $_.Date -and $n -eq $_.Name }
If ($checkNamespaceExists){
($this | Where { $u -eq $_.namespace.Unit -and $d -eq $_.namespace.Date -and $n -eq $_.namespace.Name }).id += $id
}
Else {
([Collection[object]]$this).add($newConfig)
}
}
}
Which seems to work. In addition to the inheritance, had to do some other corrections regarding how I dotted into my input types, and I also needed to load the collection class separately after the other 2 classes as well as use the base class's add method in my else statement.
Going forward, I will have to do some other validation to ensure that a model_config type is entered. Currently the custom collection accepts any input, even though I auto-convert the add parameter to model_config, e.g.,
$config = [model_config]::new('a','b','c',#{'h'='t'})
$collection = [Collection_Configs]::new()
$collection.Add($config)
works, but
$collection.Add('test')
also works when it should fail validation. Perhaps it is not overriding correctly and using the base class's overload?
Last update
Everything seems to be working now. The last update to the class is:
using namespace System.Collections.ObjectModel
Class Collection_Configs : Collection[Model_Config]{
[void] Add ([Model_Config]$newConfig ){
$checkNamespaceExists = $null
$namespace = $newConfig.namespace
$u = $namespace.Unit
$d = $namespace.Date
$n = $namespace.Name
$id = $newConfig.id
$checkNamespaceExists = $this.namespace | Where { $u -eq $_.Unit -and $d -eq $_.Date -and $n -eq $_.Name }
If ($checkNamespaceExists){
($this | Where { $u -eq $_.namespace.Unit -and $d -eq $_.namespace.Date -and $n -eq $_.namespace.Name }).id += $id
}
Else {
[Collection[Model_Config]].GetMethod('Add').Invoke($this, [Model_Config[]]$newConfig)
}
}
}
Notice in the else statement that ....GetMethod('Add')... is necessary for Windows PowerShell, as pointed out in the footnote of mklement0's super useful and correct answer. If you are able to work with Core, then mklement0's syntax will work (I tested).
Also mentioned by mklement0, the types need to be loaded separately. FYI this can be done on the commandline for quick provisional testing by typing in the model_namespace and model_config classes and pressing enter before doing the same for Collection_Configs.
In summary this will create a custom collection type with custom methods in PowerShell.
It is possible to subclass System.Collections.Generic.List`1, as this simplified example, which derives from a list with [regex] elements, demonstrates:[1]
using namespace System.Collections.Generic
# Subclass System.Collections.Generic.List`1 with [regex] elements.
class Collection_Configs : List[regex] {
# Override the .Add() method.
# Note: You probably want to override .AddRange() too.
Add([regex] $item) {
Write-Verbose -Verbose 'Doing custom things...'
# Call the base-class method.
([List[regex]] $this).Add($item)
}
}
# Sample use.
$list = [Collection_Configs]::new()
$list.Add([regex] 'foo')
$list
However, as you note, it is recommended to derive custom collections from base class System.Collections.ObjectModel.Collection`1:
using namespace System.Collections.ObjectModel
# Subclass System.Collections.ObjectModel`1 with [regex] elements.
class Collection_Configs : Collection[regex] {
# Override the .Add() method.
# Note: Unlike with List`1, there is no .AddRange() method.
Add([regex] $item) {
Write-Verbose -Verbose 'Doing custom things...'
# Call the base-class method.
([Collection[regex]] $this).Add($item)
}
}
As for the pros and cons:
List`1 has more built-in functionality (methods) than ObjectModel`1, such as .Reverse(), Exists(), and .ForEach().
In the case of .ForEach() that actually works to the advantage of ObjectModel`1: not having such a method avoids a clash with PowerShell's intrinsic .ForEach() method.
Note that in either case it is important to use the specific type that your collection should be composed of as the generic type argument for the base class: [regex] in the example above, [Model_Config] in your real code (see next section).
If you use [object] instead, your collection won't be type-safe, because it'll have a void Add(object item) method that PowerShell will select whenever you call the .Add() method with an instance of a type that is not the desired type (or cannot be converted to it).
However, there's an additional challenge in your case:
As of PowerShell 7.3.1, because the generic type argument that determines the list element type is another custom class, that other class must unexpectedly be loaded beforehand, in a separate script, the script that defines the dependent Collection_Configs class.
This requirement is unfortunate, and at least conceptually related to the general (equally unfortunate) need to ensure that .NET types referenced in class definitions have been loaded before the enclosing script executes - see this post, whose accepted answer demonstrates workarounds.
However, given that all classes involved are part of the same script file in your case, a potential fix should be simpler than the one discussed in the linked post - see GitHub issue #18872.
[1] Note: There appears to be a bug in Windows PowerShell, where calling the base class' .Add() method fails if the generic type argument (element type) happens to be [pscustomobject] aka [psobject]: That is, while ([List[pscustomobject]] $this).Add($item) works as expected in PowerShell (Core) 7+, an error occurs in Windows PowerShell, which requires the following reflection-based workaround: [List[pscustomobject]].GetMethod('Add').Invoke($this, [object[]] $item)
There were a few issues with the original code:
The Using keyword was spelled incorrectly. It should be using.
The $List variable in the Collection_Configs class was not declared with a type. It should be [List[Model_Config]]$List.
The Add method in the Collection_Configs class was missing its return type. It should be [void] Add ([Model_Config]$newConfig).
The Add method was missing its opening curly brace.
Say I have JSON like:
{
"a" : {
"b" : 1,
"c" : 2
}
}
Now ConvertTo-Json will happily create PSObjects out of that. I want to access an item I could do $json.a.b and get 1 - nicely nested properties.
Now if I have the string "a.b" the question is how to use that string to access the same item in that structure? Seems like there should be some special syntax I'm missing like & for dynamic function calls because otherwise you have to interpret the string yourself using Get-Member repeatedly I expect.
No, there is no special syntax, but there is a simple workaround, using iex, the built-in alias[1] for the Invoke-Expression cmdlet:
$propertyPath = 'a.b'
# Note the ` (backtick) before $json, to prevent premature expansion.
iex "`$json.$propertyPath" # Same as: $json.a.b
# You can use the same approach for *setting* a property value:
$newValue = 'foo'
iex "`$json.$propertyPath = `$newValue" # Same as: $json.a.b = $newValue
Caveat: Do this only if you fully control or implicitly trust the value of $propertyPath.
Only in rare situation is Invoke-Expression truly needed, and it should generally be avoided, because it can be a security risk.
Note that if the target property contains an instance of a specific collection type and you want to preserve it as-is (which is not common) (e.g., if the property value is a strongly typed array such as [int[]], or an instance of a list type such as [System.Collections.Generic.List`1]), use the following:
# "," constructs an aux., transient array that is enumerated by
# Invoke-Expression and therefore returns the original property value as-is.
iex ", `$json.$propertyPath"
Without the , technique, Invoke-Expression enumerates the elements of a collection-valued property and you'll end up with a regular PowerShell array, which is of type [object[]] - typically, however, this distinction won't matter.
Note: If you were to send the result of the , technique directly through the pipeline, a collection-valued property value would be sent as a single object instead of getting enumerated, as usual. (By contrast, if you save the result in a variable first and the send it through the pipeline, the usual enumeration occurs). While you can force enumeration simply by enclosing the Invoke-Expression call in (...), there is no reason to use the , technique to begin with in this case, given that enumeration invariably entails loss of the information about the type of the collection whose elements are being enumerated.
Read on for packaged solutions.
Note:
The following packaged solutions originally used Invoke-Expression combined with sanitizing the specified property paths in order to prevent inadvertent/malicious injection of commands. However, the solutions now use a different approach, namely splitting the property path into individual property names and iteratively drilling down into the object, as shown in Gyula Kokas's helpful answer. This not only obviates the need for sanitizing, but turns out to be faster than use of Invoke-Expression (the latter is still worth considering for one-off use).
The no-frills, get-only, always-enumerate version of this technique would be the following function:
# Sample call: propByPath $json 'a.b'
function propByPath { param($obj, $propPath) foreach ($prop in $propPath.Split('.')) { $obj = $obj.$prop }; $obj }
What the more elaborate solutions below offer: parameter validation, the ability to also set a property value by path, and - in the case of the propByPath function - the option to prevent enumeration of property values that are collections (see next point).
The propByPath function offers a -NoEnumerate switch to optionally request preserving a property value's specific collection type.
By contrast, this feature is omitted from the .PropByPath() method, because there is no syntactically convenient way to request it (methods only support positional arguments). A possible solution is to create a second method, say .PropByPathNoEnumerate(), that applies the , technique discussed above.
Helper function propByPath:
function propByPath {
param(
[Parameter(Mandatory)] $Object,
[Parameter(Mandatory)] [string] $PropertyPath,
$Value, # optional value to SET
[switch] $NoEnumerate # only applies to GET
)
Set-StrictMode -Version 1
# Note: Iteratively drilling down into the object turns out to be *faster*
# than using Invoke-Expression; it also obviates the need to sanitize
# the property-path string.
$props = $PropertyPath.Split('.') # Split the path into an array of property names.
if ($PSBoundParameters.ContainsKey('Value')) { # SET
$parentObject = $Object
if ($props.Count -gt 1) {
foreach ($prop in $props[0..($props.Count-2)]) { $parentObject = $parentObject.$prop }
}
$parentObject.($props[-1]) = $Value
}
else { # GET
$value = $Object
foreach ($prop in $props) { $value = $value.$prop }
if ($NoEnumerate) {
, $value
} else {
$value
}
}
}
Instead of the Invoke-Expression call you would then use:
# GET
propByPath $obj $propertyPath
# GET, with preservation of the property value's specific collection type.
propByPath $obj $propertyPath -NoEnumerate
# SET
propByPath $obj $propertyPath 'new value'
You could even use PowerShell's ETS (extended type system) to attach a .PropByPath() method to all [pscustomobject] instances (PSv3+ syntax; in PSv2 you'd have to create a *.types.ps1xml file and load it with Update-TypeData -PrependPath):
'System.Management.Automation.PSCustomObject',
'Deserialized.System.Management.Automation.PSCustomObject' |
Update-TypeData -TypeName { $_ } `
-MemberType ScriptMethod -MemberName PropByPath -Value { #`
param(
[Parameter(Mandatory)] [string] $PropertyPath,
$Value
)
Set-StrictMode -Version 1
$props = $PropertyPath.Split('.') # Split the path into an array of property names.
if ($PSBoundParameters.ContainsKey('Value')) { # SET
$parentObject = $this
if ($props.Count -gt 1) {
foreach ($prop in $props[0..($props.Count-2)]) { $parentObject = $parentObject.$prop }
}
$parentObject.($props[-1]) = $Value
}
else { # GET
# Note: Iteratively drilling down into the object turns out to be *faster*
# than using Invoke-Expression; it also obviates the need to sanitize
# the property-path string.
$value = $this
foreach ($prop in $PropertyPath.Split('.')) { $value = $value.$prop }
$value
}
}
You could then call $obj.PropByPath('a.b') or $obj.PropByPath('a.b', 'new value')
Note: Type Deserialized.System.Management.Automation.PSCustomObject is targeted in addition to System.Management.Automation.PSCustomObject in order to also cover deserialized custom objects, which are returned in a number of scenarios, such as using Import-CliXml, receiving output from background jobs, and using remoting.
.PropByPath() will be available on any [pscustomobject] instance in the remainder of the session (even on instances created prior to the Update-TypeData call [2]); place the Update-TypeData call in your $PROFILE (profile file) to make the method available by default.
[1] Note: While it is generally advisable to limit aliases to interactive use and use full cmdlet names in scripts, use of iex to me is acceptable, because it is a built-in alias and enables a concise solution.
[2] Verify with (all on one line) $co = New-Object PSCustomObject; Update-TypeData -TypeName System.Management.Automation.PSCustomObject -MemberType ScriptMethod -MemberName GetFoo -Value { 'foo' }; $co.GetFoo(), which outputs foo even though $co was created before Update-TypeData was called.
This workaround is maybe useful to somebody.
The result goes always deeper, until it hits the right object.
$json=(Get-Content ./json.json | ConvertFrom-Json)
$result=$json
$search="a.c"
$search.split(".")|% {$result=$result.($_) }
$result
You can have 2 variables.
$json = '{
"a" : {
"b" : 1,
"c" : 2
}
}' | convertfrom-json
$a,$b = 'a','b'
$json.$a.$b
1
I need to create a PowerShell script which will restore language settings and input methods (important) from a file. At first I thought it would be easy to use Get-WinUserLanguageList | Export-CliXML ./mylist.xml to save current settings and then $List = Import-CliXML ./mylist.xml, Set-WinUserLanguageList -LanguageList $List, however it does not work because the data imported from XML into the variable is deserialized and I get an exception:
Set-WinUserLanguageList : Cannot bind parameter 'LanguageList'. Cannot convert the "Microsoft.InternationalSettings.Commands.WinUserLanguage" value of type
"Deserialized.Microsoft.InternationalSettings.Commands.WinUserLanguage" to type
"Microsoft.InternationalSettings.Commands.WinUserLanguage".
I've tried using XML but failed, so I created a workaround which looks like this:
[CmdletBinding()]
param (
[switch]$GenerateList
)
function Generate-List { # Generates language files to restore from.
$GoodList = Get-WinUserLanguageList
[string[]]$LanguageTags = $GoodList.LanguageTag
$LanguageTags | Out-File .\LanguageTags.txt
[string[]]$InputMethods = $GoodList.InputMethodTips
$InputMethods | Out-File .\InputMethods.txt
} # Exporting languages and corresponding input methods in separate files. Can be improved.
if ($GenerateList -eq $true) {
Generate-List
} # Invokes a function based on a switch parameter.
function RestoreFrom-List {
$GoodList = Get-WinUserLanguageList # Make our variable of a proper type
$GoodList.Clear() # Clear the variable contents
[string[]]$LanguageTags = Get-Content .\LanguageTags.txt
[string[]]$InputMethods = Get-Content .\InputMethods.txt
foreach ($language in $LanguageTags) { # This loop fills $GoodList with proper values
$index = $LanguageTags.IndexOf($language)
$GoodList.Add($language) # Add a language to the list
$GoodList[$index].InputMethodTips.Clear() # Remove default input method
$GoodList[$index].InputMethodTips.Add($InputMethods[$index]) # Add an input method from a corresponding position in the saved txt file
}
Set-WinUserLanguageList $GoodList -force # Restore system languages and input methods using a freshly created list
}
RestoreFrom-List
I am new to PowerShell and I am sure this code is ugly and can be improved. Also, it feels that *-WinUserLanguageList cmdlets are a pain to work with - you have to use internal methods to change data instead of the universal Set-Property.
So far my script successfully exports language/input-method settings into two(!) txt(!!) files and doesn't work if a particular language has two or more input methods (because it dumps input methods into an array without relating them to a particular language and then retrieves them based on an index). Please help improving this.
Decided to revisit my old question and post an improved version of the script which uses JSON to store exported languages. Any number of languages and input methods is supported now, and they are stored as a properly structured JSON file. Handwriting and spellchecking settings are also preserved.
# Exports/imports windows language settings to/from a file
function ImportExport-Languages {
[CmdletBinding()]
param (
[switch]$GenerateList,
[string]$Path
)
## Export
function Export-Lang {
Get-WinUserLanguageList | ConvertTo-Json | Out-File $Path
}
## Import
function Import-Lang {
$importedFile = Get-Content $Path | ConvertFrom-Json
$langCollection = New-Object Microsoft.InternationalSettings.Commands.WinUserLanguage[] ""
foreach ($item in $importedFile)
{
$lang = [Microsoft.InternationalSettings.Commands.WinUserLanguage]::new($item.LanguageTag)
$lang.InputMethodTips.Clear() # clear default auto-generated input method
foreach ($inputMethod in $item.InputMethodTips)
{
$lang.InputMethodTips.Add($inputMethod)
}
$lang.Handwriting = $item.Handwriting
$lang.Spellchecking = $item.Spellchecking
$langCollection += $lang
}
Set-WinUserLanguageList $langCollection -Force
}
## Run
if ($GenerateList) { Export-Lang }
Import-Lang
}
ImportExport-Languages -Path "C:\MyFolder\MyFile.json" -GenerateList
Hi I modified it a little bit because it was not working on windows 10 pro. Changed the array to list of languages now works like a charm.
function ImportExport-Languages {
[CmdletBinding()]
param (
[switch]$GenerateList,
[string]$Path
)
## Export
function Export-Lang {
Get-WinUserLanguageList | ConvertTo-Json | Out-File $Path
}
## Import
function Import-Lang {
$importedFile = Get-Content $Path | ConvertFrom-Json
$langCollection = New-Object System.Collections.Generic.List[Microsoft.InternationalSettings.Commands.WinUserLanguage]
foreach ($item in $importedFile)
{
$lang = [Microsoft.InternationalSettings.Commands.WinUserLanguage]::new($item.LanguageTag)
$lang.InputMethodTips.Clear() # clear default auto-generated input method
foreach ($inputMethod in $item.InputMethodTips)
{
$lang.InputMethodTips.Add($inputMethod)
}
$lang.Handwriting = $item.Handwriting
$lang.Spellchecking = $item.Spellchecking
$langCollection.Add($lang)
}
Set-WinUserLanguageList $langCollection -Force
}
## Run
if ($GenerateList) { Export-Lang }
Import-Lang
}
# usage export ImportExport-Languages -Path "C:\Install\languageExport.json" -GenerateList
# usage import ImportExport-Languages -Path "C:\Install\languageExport.json"
In below code, I need to check if version string is not empty then append its value to the request variable.
if ([string]::IsNullOrEmpty($version))
{
$request += "/" + $version
}
How to check not in if condition?
if (-not ([string]::IsNullOrEmpty($version)))
{
$request += "/" + $version
}
You can also use ! as an alternative to -not.
You don't necessarily have to use the [string]:: prefix. This works in the same way:
if ($version)
{
$request += "/" + $version
}
A variable that is null or empty string evaluates to false.
As in many other programming and scripting languages you can do so by adding ! in front of the condition
if (![string]::IsNullOrEmpty($version))
{
$request += "/" + $version
}
If the variable is a parameter then you could use advanced function parameter binding like below to validate not null or empty:
[CmdletBinding()]
Param (
[parameter(mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Version
)
if (!$variablename)
{
Write-Host "variable is null"
}
I hope this simple answer will resolve the question.
Source
I would define $Version as a string to start with
[string]$Version
and if it's a param you can use the code posted by Samselvaprabu
or if you would rather not present your users with an error you can do something like
while (-not($version)){
$version = Read-Host "Enter the version ya fool!"
}
$request += "/" + $version
You can use the [string]::IsNullOrEmpty($version) method if it is a string.
But, I was looking for a universal way to check nulls (regardless of data type)
in Powershell. Checking for null (or not null) values in
PowerShell is tricky. Using ($value -eq $null) or ($value -ne
$null) does not always work. Neither does if($value). Using them
can even cause problems later on.
Just read this Microsoft article below (IN IT'S ENTIRETY) to get a grasp
of how tricky nulls can be in Powershell.
[https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-null?view=powershell-7.1][1]
I wrote these two functions below for checking for null (or not null) values
in PowerShell. I "believe" they should work for any and all values
and data types.
I hope someone finds them helpful.
I am not sure why MS hasn't put something like this into PowerShell natively to
make handling nulls easier (and less dangerous) in PowerShell.
I hope this helps someone.
If anyone knows of an unseen "pitfall" or problem with this method,
please post a comment here so we can know that.
Thanks!
<#
*********************
FUNCTION: ValueIsNull
*********************
Use this function ValueIsNull below for checking for null values
rather using -eq $null or if($value) methods. Those may not work as expected.
See reference below for more details on $null values in PowerShell.
[https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-null?view=powershell-7.1][1]
An if statement with a call to ValueIsNull can be written like this:
if (ValueIsNull($TheValue))
#>
function ValueIsNull {
param($valueToCheck)
# In Powershell when a parameter of a function does not have a data type defined,
# it will create the parameter as a PSObject. It will do this for
# an object, an array, and a base date type (int, string, DateTime, etc.)
# However if the value passed in is $null, then it will still be $null.
# So, using a function to check null gives us the ability to determine if the parameter
# is null or not by checking if the parameter is a PSObject or not.
# This function could be written more efficiently, but intentionally
# putting it in a more readable format.
# Special Note: This cannot tell the difference between a parameter
# that is a true $null and an undeclared variable passed in as the parameter.
# ie - If you type the variable name wrong and pass that in to this function it will see it as a null.
[bool]$returnValue = $True
[bool]$isItAnObject=$True
[string]$ObjectToString = ""
try { $ObjectToString = $valueToCheck.PSObject.ToString() } catch { $isItAnObject = $false }
if ($isItAnObject)
{
$returnValue=$False
}
return $returnValue
}
<#
************************
FUNCTION: ValueIsNotNull
************************
Use this function ValueIsNotNull below for checking values for
being "not-null" rather than using -ne $null or if($value) methods.
Both may not work as expected.
See notes on ValueIsNull function above for more info.
ValueIsNotNull just calls the ValueIsNull function and then reverses
the boolean result. However, having ValueIsNotNull available allows
you to avoid having to use -eq and\or -ne against ValueIsNull results.
You can disregard this function and just use !ValueIsNull($value).
But, it is my preference to have both for easier readability of code.
An if statement with a call to ValueIsNotNull can be written like this:
if (ValueIsNotNull($TheValue))
#>
function ValueIsNotNull {
param($valueToCheck)
[bool]$returnValue = !(ValueIsNull($valueToCheck))
return $returnValue
}
You can use the following list of calls to ValueIsNull to test it out.
$psObject = New-Object PSObject
Add-Member -InputObject $psObject -MemberType NoteProperty -Name customproperty -Value "TestObject"
$valIsNull = ValueIsNull($psObject)
$props = #{
Property1 = 'one'
Property2 = 'two'
Property3 = 'three'
}
$otherPSobject = new-object psobject -Property $props
$valIsNull = ValueIsNull($otherPSobject)
# Now null the object
$otherPSobject = $null
$valIsNull = ValueIsNull($otherPSobject)
# Now an explicit null
$testNullValue = $null
$valIsNull = ValueIsNull($testNullValue)
# Now a variable that is not defined (maybe a type error in variable name)
# This will return a true because the function can't tell the difference
# between a null and an undeclared variable.
$valIsNull = ValueIsNull($valueNotDefine)
[int32]$intValueTyped = 25
$valIsNull = ValueIsNull($intValueTyped)
$intValueLoose = 67
$valIsNull = ValueIsNull($intValueLoose)
$arrayOfIntLooseType = 4,2,6,9,1
$valIsNull = ValueIsNull($arrayOfIntLooseType)
[int32[]]$arrayOfIntStrongType = 1500,2230,3350,4000
$valIsNull = ValueIsNull($arrayOfIntStrongType)
#Now take the same int array variable and null it.
$arrayOfIntStrongType = $null
$valIsNull = ValueIsNull($arrayOfIntStrongType)
$stringValueLoose = "String Loose Type"
$valIsNull = ValueIsNull($stringValueLoose)
[string]$stringValueStrong = "String Strong Type"
$valIsNull = ValueIsNull($stringValueStrong)
$dateTimeArrayLooseValue = #("1/1/2017", "2/1/2017", "3/1/2017").ForEach([datetime])
$valIsNull = ValueIsNull($dateTimeArrayLooseValue)
# Note that this has a $null in the array values. Still returns false correctly.
$stringArrayLooseWithNull = #("String1", "String2", $null, "String3")
$valIsNull = ValueIsNull($stringArrayLooseWithNull)
Does PowerShell call any initialization code when a module is loaded?
I am looking for something like a Perl BEGIN block, or a constructor.
Both NEW-MODULE and IMPORT-MODULE will return a PSCustomObject. I am trying to encapsulate a custom object in a module to avoid lengthy code in scripts. One method that tests well in open code is:
$m = new-module -scriptblock {
New-Object PSCustomObject |
Add-Member NoteProperty -name person -value Frodo -passthru |
Add-Member ScriptMethod Who { $this.person } -passthru |
Add-Member ScriptMethod Mod {
param($x)
$this.person = $x
} -passthru
} -ascustomobject -returnresult
Ideally I would like to drop this code into a module and use something like:
$MyObj = Import-Module -Name ".\MyPackage" -AsCustomObject
and have MyObj be a handle to an object the same as the first snippet provides.
Suggestions appreciated.
It's not clear if you want to run initialization code when a module is loaded (like Perl's BEGIN block) or if you want to create a custom class (which is what "constructor" suggests).
Initialization code in a module is easy. Any code in a module not embedded in a function is executed when the module is imported.
Creating a custom class isn't supported natively in PS. But see: http://psclass.codeplex.com/. You can also write C#, VBScript, etc. and use Add-Type.
Import-module won't work to simulate a class, because you can only have 1 instance of a module with a given name - at best you'd have a singleton class. (BTW, import-module does have a -passthru parameter, which would more or less make your last line of code work - as a singleton. You'd also have to add export-module -variable * -function * to your module code) You could use New-Module to simulate a class though. And you could wrap it in a function named, new-myClass for example.
BTW, if you use the -ASCustomObject parameter you end up with a hashtable, which doesn't support "this" (in words hash table values that are script blocks don't have a built-in way to refer to the hashtable itself). If you use new-module without -AsCustomObject (and potentially use a factory function, for example new-myclass) then you could simulate "this.varInMyModule" with & $myModule $varInMyModule. However if you create a PSCustomObject, using Add-Member, then script method have access to $this and it in general acts a lot more like a typical object with properties and methods.
Modules are really supposed to output cmdlets, not objects. A module should provide a set of related cmdlets. There is a way to send data into the module using Import-Modules's -ArgumentList parameter as show here. You could use the technique to provide a server name for your cmdlets to connect to for example. The PowerCLI module handles that differently using a cmdlet that creates a script scope connection object ($script:connection) that the other cmdlets check for and re-use if it exists similar to this:
#test.psm1
$script:myvar = "hi"
function Show-MyVar {Write-Host $script:myvar}
function Set-MyVar ($Value) {$script:myvar = $Value}
#end test.psm1
Using Modules you can export both innate properties and functions, and don't need to run them through add-member or do much acrobatics. Note however that it has some issues if you don't want to export all properties and methods, and while you can initialize properties to an initial value, you CAN'T call an internal function during initialization without doing some akward acrobatics.
But I think what you really want to do is use Classes which are now available in Powershell 5 (they weren't when you posted). I've provided examples of each.
Sysops has a decent tutorial on the new classes in 4 parts
Here's the older way before powershell 5.0
# powershell 3.0 and above (I think)
$m = new-module -ascustomobject -scriptblock `
{
$person = "Frodo"
function Who
()
{return $this.person}
function Rename
($name)
{$this.person = $name}
Export-ModuleMember -Function * -Variable *
}
write-host "direct property access: $($m.person)"
write-host "method call: $($m.who())"
$m.Rename("Shelob")
write-host "change test: $($m.who())"
Also you can replicate multiple objects from a template like this:
# powershell 3.0 and above (I think)
$template = `
{
$person = "Frodo"
function Who
()
{return $this.person}
function Rename
($name)
{$this.person = $name}
Export-ModuleMember -Function * -Variable *
}
$me = new-module -ascustomobject -scriptblock $template; $me.Rename("Shelob")
$you = new-module -ascustomobject -scriptblock $template
"Me: $($me.Who())"
"You: $($you.Who())"
And in powershell 5 you have actual classes (mostly)
#requires -version 5
Class Person
{
hidden [String] $name #not actually private
[string] Who ()
{return $this.name}
[void] Rename ([string] $name)
{$this.name = $name}
# constructors are weird though, you don't specify return type OR explicitly return value.
Person ([String]$name)
{$this.name = $name}
<#
# The above constructor code is secretly converted to this
[Person] New ([string]$name) #note the added return type and renamed to New
{
$this.name = $name
return $this #note that we are returning ourself, you can exploit this to create chained constructors like [person]::New("gandalf").withWizardLevel("White") but I haven't done so here
}
#>
}
$me = [Person]::new("Shelob")
$you = [Person]::new("Frodo")
# $me|gm # Note that Name doesn't show here
# $me.name # But we can still access it...
# $me|gm -Force # Note that Name DOES show here
"`n"
"Me: $($me.who())"
"You: $($you.who())"
$you.Rename("Dinner")
"You after we meet: $($you.who())"