Why splatting not working if we direcly write Hash table? - powershell

Powershell splatting works if we pass parameter as below.
$Vm = #{
Name = "NewVm"
MemoryStartupBytes = 1GB
NewVHDPath = d:\vhd\base.vhdx
}
New-VM #VM
But if tried as below it doesn't work.
New-VM #{
Name = "NewVm"
MemoryStartupBytes = 1GB
NewVHDPath = d:\vhd\base.vhdx
}
Also i tried
New-VM #( #{
Name = "NewVm"
MemoryStartupBytes = 1GB
NewVHDPath = d:\vhd\base.vhdx
}
)
Here also Hash table is being created (unnamed hash table ). But why it doesn't accept? Is there any trick to make it believe it is splatting variable?
One trick is to write a proxy as below.
function New-myVM
{
param
(
[Hashtable] $ht
)
New-VM #ht
}
New-MyVM #{
Name = "NewVm"
MemoryStartupBytes = 1GB
NewVHDPath = d:\vhd\base.vhdx
}
But if we do this way then each command require proxy which is heavy.
If it can be written like this , It can look like DSL. So i am trying to figure out a way.

Related

Using Splat in Invoke-Command AND passing arguments relevant to the machine

I'm using Invoke-Command, but this question can be relevant to any command using splat. I essentially want to pass two sets of variables that will be useful in the splat command, but I'm not sure how I can do this.
In the code below, the Invoke-Command successfully connects to both servers, but the output I get is "Server1 Info" from both servers, which makes sense since the code is reading it like I'm trying to pass two arguments to both servers and it is taking what is in the first argument and writing it to host. What I really want it to do though is only pass one argument each time and to move down the list of which argument is being passed as it connects to successive servers.
$ServerList = "Server1","Server2"
$ServerArgs = "Server1 Info","Server2 Info"
$SB = {
param($Arg1)
Write-Host $Arg1
}
$SplatInfo = #{
ComputerName = $ServerList
ArgumentList = $ServerArgs
}
Invoke-Command #SplatInfo -ScriptBlock $SB
You can only send one set of arguments per invocation of Invoke-Command.
If you want to pass different arguments per remote machine, you need to call Invoke-Command once per server:
$ServerList = "Server1","Server2"
$ServerArgs = "Server1 Info","Server2 Info"
$SB = {
param($Arg1)
Write-Host $Arg1
}
for($i = 0; $i -lt $ServerList.Count; $i++){
$SplatInfo = #{
ComputerName = $ServerList[$i]
ArgumentList = $ServerArgs[$i]
}
Invoke-Command #SplatInfo -ScriptBlock $SB
}
... in which case you might want to organize the input data slightly differently:
$inputList = #(
#{ ComputerName = "Server1"; ArgumentList = #("Server1 Info") }
#{ ComputerName = "Server2"; ArgumentList = #("Server2 Info") }
)
$baseSplat = #{
ScriptBlock = {
param($Arg1)
Write-Host $Arg1
}
}
foreach($entry in $inputList){
Invoke-Command #baseSplat #entry
}
Using the hashtable idea. This should run in parallel still.
import-csv file.csv |
% { $ServerArgs = #{} } { $ServerArgs[$_.server] = $_.args }
$ServerList = $ServerArgs | % keys
$SB = {
param($Arg1)
[pscustomobject]#{result = $Arg1[$env:computername]}
}
$SplatInfo = #{
ComputerName = $ServerList
ArgumentList = $ServerArgs
ScriptBlock = $SB
}
Invoke-Command #SplatInfo
result PSComputerName RunspaceId
------ -------------- ----------
Server1 Info Server1 gacbbb30-2492-41df-b181-9ebf6395b8b6
Server2 Info Server2 ebca4b52-c349-4ff7-972e-e67d07d9c0c3

Start-Job not returning custom class object properly when PSCustomObject returns as intended

I'm running into a problem that isn't really a hindrance, but a possible bug from serialization in extracting job output (guessing?). The code below is a snippet from Start-Job and later captured output by foreach (Start-Job | Receive-Job)
class ComputerResult {
$computerName
$bldg
$room
$organization
$user
$lastUpdate
$printerlist = #()
$finalprinterlist = #()
}
# This one below doesn't work.
$return_result = New-Object -TypeName ComputerResult
# This done does work.
$return_result = New-Object -TypeName PSCustomObject -Property #{ComputerName = ""; BLDG = ""; room = ""; organization = ""; user = ""; lastupdate = ""; printerlist = #(); finalprinterlist = #() }
# Here I would start assigning values to $return_result
# Once assigned, return from the Job process to await Receive-Job
return $return_result
The main issue is the $return_result where it works as intended when it's a type PSCustomObject, unlike the ComputerResult class object defined above it. When the script runs, a Get-WmiObject -Class win32_printer -ComputerName $computername Is done to add some WMI objects into my $return_result.printerlist, but when returned, the ComputerResult.printerlist is returning an array of strings - with values of the __PATH property. What should be returned is WMI objects.
PSCustomObject returns just fine with keeping its methods, properties, etc. The ComputerResult.printerlist is kept with full WMI objects.
My assumption is the PSCustomObject is handled differently from the rest of the custom classes, in some way, and perhaps uses a different underlying library when serializing and piping back into the main process.
Why is this? Is this a bug or am I misunderstanding something?
I created a function with your code like this:
Function RetPrinter
{
class ComputerResult {
$computerName
$bldg
$room
$organization
$user
$lastUpdate
$printerlist = #()
$finalprinterlist = #()
}
$return_result = New-Object -TypeName ComputerResult
$return_result.PrinterList += gwmi win32_printer
return $return_result
}
I then retrieved my printer list like so:
$Obj = RetPrinter
By piping them to gm, $Obj was returned as TypeName:ComputerResult, and $ObjName.PrinterList was seen as TypeName: System.Management.ManagementObject#root\cimv2\Win32_Printer. All seemed to work as expected.

New-ADUser + OtherAttributes w/ Splatting for Readability

foreach ($Person in $People) {
$NewUserParams = #{
Name = $Person.Name
Server = 'xxxx.com:389'
Path = 'CN=Users,CN=addressBook,DC=xxxx,DC=com'
Credential = $Credentials
givenName = $Person.givenName
otherAttributes = #{sn=$Person.sn}
}
New-ADUser #NewUserParams
}
I have many additional attributes (otherAttributes) that I would like to add that are available to me in the formart New-ADUser -Name XXX -OtherAttributes #{sn=xxx} . However, I am trying to using splatting to make the OtherAttributes more readable, along with other required parameters. I don't need to using splatting for the entire command, my goal was to break up otherAttributes so it wasn't a long string that wrapped. Ideas?
The value of otherAttributes is just another hashtable, and can be wrapped like any other hashtable:
$NewUserParams = #{
'Name' = $Person.Name
'Server' = 'server.example.com:389'
'Path' = 'cn=Users,cn=addressBook,dc=example,dc=com'
'Credential' = $Credentials
'givenName' = $Person.givenName
'otherAttributes' = #{
'sn' = $Person.sn
}
}
Personally, I recommend putting the keys of the hashtables in quotes to avoid surprises, but at least in the above example that's not required.
If the above doesn't work for you you need to provide more details about the code you're running and the error(s) you're getting. Displaying the content of the generated hashtable inside the loop usually helps troubleshooting problems with particular values.

$configdata and parameters

Is there any option to pass values to $configdata block (use for -ConfigurationData) as a parameter/variable?
Something like:
Configuration Config1
{
...
...
...
}
$configdata = #{
AllNodes = #(
#{
NodeName = servername
CertificateFile = "$path\CertFile.cer"
Thumbprint = $CertThumb
}
The Configuration Data is the way to pass values to the Configuration.
On the other hand, the configuration data itself is nothing more, but a Hash Table. You can edit it in any way you like.
Consider the following example.
You are in Push mode and have the following configuration:
Configuration MyFileCreator
{
Import-DscResource –ModuleName 'PSDesiredStateConfiguration'
Node localhost
{
File sampleFile
{
Ensure = $ConfigurationData.fileEnsure
Type = 'File'
DestinationPath = 'c:\temp\file.txt'
Force = $true
}
# Configure LCM
LocalConfigurationManager
{
ConfigurationMode = 'ApplyAndAutoCorrect'
RefreshMode = 'PUSH'
RebootNodeIfNeeded = $False
}
}
}
You see that I have used $ConfigurationData.fileEnsure. This variable refers to a value I set in the ConfigurationData.
My ConfigurationData could look like this:
$myConfigurationData = #{
AllNodes = #()
fileEnsure = 'absent'
}
If I want to apply my configuration with the configuration data, I can run the following commands:
MyFileCreator -ConfigurationData $myConfigurationData
Start-DscConfiguration -ComputerName localhost .\MyFileCreator
If I want to change the configuration data, I can simply modify the Hash Table and apply my configuration again:
$myConfigurationData.fileEnsure = 'present'
MyFileCreator -ConfigurationData $myConfigurationData
Start-DscConfiguration -ComputerName localhost .\MyFileCreator -Force
You can read more about the idea behind Configuration and Environment Data in the official MSDN Documentation.

PowerShell splatting merging to one hashtable

This code does what I want it to do:
$EventParams = #{
LogName = $LogName
Source = $Source
}
$EventInfoParams = $EventParams + #{
EntryType = 'Information'
EventID = '0'
}
$EventWarnParams = $EventParams + #{
EntryType = 'Warning'
EventID = '1'
}
$EventErrorParams = $EventParams + #{
EntryType = 'Error'
EventID = '2'
}
On this blog I found out there are maybe nicer/cleaner ways of writing this in one big hashtable. So I tried the following:
$EventParams = #{
LogName = $LogName
Source = $Source
Info = #{
EntryType = 'Information'
EventID = '0'
}
Warn = #{
EntryType = 'Warning'
EventID = '1'
}
Error = #{
EntryType = 'Error'
EventID = '2'
}
}
$EventParams.Info
This works fine to except that I can't get the variables from in the first example $EventParams in each of the single hashtables without duplicating data. Is there a way to have it all in one big hasthable?
Typically, you'd write all or most of the events from a given script to a common log and source. If you're wanting to avoid code duplication you can set this once for all the events that will be written from the script using $PSDefaultParameters at the beginning of the script:
#Set event loggin defaults
$PSDefaultParameterValues =
$PSDefaultParameterValues.Clone() + #{
'Write-Eventlog:LogName' = $LogName
'Write-Eventlog:Source' = $Source
}
Cloning it will create a new copy in the script, inheriting whatever defaults are already set in the parent or global scope without altering the hash table in that scope. The new $PSDefaultParameterValues will be disposed when the script finishes and the settings will revert back to whatever there are in the parent scope.
If you need to write to some other log or source somewhere in the script you can do that by specifying the LogName and Source for that event, overriding the defaults.
As far as I understand your question, you want to reference LogName and Source within the same hashtable? I doubt that this is possible.
However, You could go with a function:
function Get-EventParams
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,Position=0)]
[string]$LogName,
[Parameter(Mandatory=$true,Position=1)]
[string]$Source,
[Parameter(Mandatory=$true,Position=2, ParameterSetName='info')]
[switch]$Info,
[Parameter(Mandatory=$true,Position=2, ParameterSetName='warn')]
[switch]$Warn,
[Parameter(Mandatory=$true,Position=2, ParameterSetName='error')]
[switch]$Error
)
#{
LogName = $LogName
Source = $Source
}
if ($Info)
{
#{
EntryType = 'Information'
EventID = '0'
}
}
if ($Warn)
{
#{
EntryType = 'Warning'
EventID = '1'
}
}
if ($Error)
{
#{
EntryType = 'Error'
EventID = '2'
}
}
}
Now you can get the desired hashtable using for example:
Get-EventParams -LogName "Azure" -Source "Application" -Info
For convenience, you could also define a ValidateSet for your LogName and Soruce parameter.