PowerShell Class; property "System.Collections.Specialized.OrderedDictionary"; how to add dictionary entry - powershell

(As I have searched the web quite intensively for an answer and didn't get any hint in the right direction, I allow myself to ask you the following question:)
I have a PowerShell class like this:
class settings
{
[string]$CFG_Name
[string]$CFG_Template_Path
[string]$CFG_Target_Path
[System.Collections.Specialized.OrderedDictionary]$ConfigSettings
}
I then instantiate this class and want to use it like this:
$CurrentSettings = New-Object settings <-- works fine
$CurrentSettings.CFG_Name = 'MPD' <-- works fine
$CurrentSettings.CFG_Template_Path = $CFG_Template_MPD_Path <-- works fine
$CurrentSettings.CFG_Target_Path = $CFG_MPD_Path <-- works fine
$CurrentSettings.ConfigSettings.Add("TID",$EFT_TID) <-- doesn't work
The line above throws: You cannot call a method on a null-valued expression.
According to this page: Documentation from Microsoft the add method is supported.
Why can't I just use this property like I can use for example $CFG_Name?
How would I add dictionary entries to this property?

The solution is the following:
Before the adding of the first dictionary entry, you have to initialize the property like this:
$CurrentSettings.ConfigSettings = New-Object System.Collections.Specialized.OrderedDictionary
Then you can add the entry:
$CurrentSettings.ConfigSettings.Add("TID",$EFT_TID)

Related

Is there a way to add a method to built-in/native powershell object (or type?/class¿)?

Is there a way to add a method to built-in/native powershell object (or type?/class¿)?
In particular, I'm looking at you [hashtable], but I suppose my question is also a general one... for instance, I could see wanting to add functionality to all my arrays...
For instance, I would like all my [hashtable] objects to have the method: .AddAndOverwrite(..) which would replace the value of the key if it exists; otherwise it creates a new key.
The only way I seem to be able to do this is to:
create an empty hashtable, $HashTableNew
add the ScriptMethod(s) to $HashTableNew (i.e. .AddAndOverwrite(..))
then when I use it, make a copy of $HashTableNew
$SomeOtherHashTable = $HashTableNew.PSObject.Copy()
This just seems like not "the way"...
Note: I will admit, this is not the best example use of a data type extension method (as #SantiagoSquarzon points out)... but it is a simple one, and it allows for a simple example in the accepted answer; so I'm intentionally leaving it as is, rather than changing question / the extension method to .foo() returning widgets...
There is indeed a better and easier way to update a type as a whole by using Update-TypeData.
Here is an example that add an .AddOrOverwrite method to the hashtable.
$TypeParam = #{
TypeName = 'System.Collections.Hashtable'
MemberType = 'ScriptMethod'
MemberName = 'AddOrOverwrite'
Value = { Param($Key, $Value) $this.$key = $Value }
}
Update-TypeData #TypeParam -Force
$SomeHashTable.AddOrOverwrite('aaa','2222222')
$this, in the scriptblock of the method definition, correspond to the object reference that is targeted, in this case, the hashtable.
-Force will overwrite the definition every time without error stating the type was already added.
That method is not super useful as it does something that the hashtable manage pretty well on its own by just using assignment but it demonstrates how to do it.
Bonus example
Here's an example on how you would apply this principle and create 2 script properties (readonly) for a string so you can convert to base 64 back and forth.
$TypeParam = #{
TypeName = 'System.String'
MemberType = 'ScriptProperty'
Force = $true
}
Update-TypeData #TypeParam -MemberName 'Base64' -Value { [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($this)) }
Update-TypeData #TypeParam -MemberName 'Base64Decoded' -Value { [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($this)) }
# Encode the string to base 64 (Output: U29tZVN0cmluZw==)
"SomeString".Base64
# Decode the string from Base64 (Output: SomeString)
"U29tZVN0cmluZw==".Base64Decoded
References
Msdocs - About-Types
Dr Scripto - Easily Update Powershell Type Data by Using a Cmdlet

How to add roles, members, and permissions?

i am trying to add roles and members to a tabular database (cube)
$Server = new-Object Microsoft.AnalysisServices.Tabular.Server
$Server.Connect("$server")
$TabDB = $Tabular_Analysis_Server.Databases[$DB]
[Microsoft.AnalysisServices.Tabular.ModelRole] $AddRole = new-Object([Microsoft.AnalysisServices.Tabular.ModelRole])("NewRole1")
$AddRole.Members.Add("member1")
$TabDB.Roles.Add($AddRole)
$AddRole.Update()
I get this error:
new-Object : Cannot find an overload for "ModelRole" and the argument
count: "1".
and
This $TabDB.Model.Roles.members gives me the roles and members just fine
if i try doing it this way:
$TabDB.Model.roles.Add("newrole1")
I get this error
Cannot find an overload for "Add" and the argument count: "1".
This $TabDB.Model.roles.Add() results in
OverloadDefinitions
-------------------
void Add(Microsoft.AnalysisServices.Tabular.ModelRole metadataObject)
void ICollection[ModelRole].Add(Microsoft.AnalysisServices.Tabular.ModelRole item)
i found a script here but this is for early cube models (1103 and below) so it doesnt work on tabular models
https://bhavikmerchant.wordpress.com/2010/09/06/adding-ssas-roles-via-powershell/
UPDATE: following the MADMagician's answer, i am able to see this
According to the documentation for the ModelRole class, the constructor doesn't accept any arguments like the older Role class did. You'll have to create a blank ModelRole, update any properties you want, then add it to the ModelRoleCollection.
$AddRole = new-Object Microsoft.AnalysisServices.Tabular.ModelRole
$AddRole.Name = 'NewRole1'
$RoleMember = New-Object Microsoft.AnalysisServices.Tabular.ModelRoleMember
$RoleMember.MemberName = 'member1'
$AddRole.Members.Add($RoleMember)
$TabDB.Model.Roles.Add($AddRole)
I don't actually have anything to test on, but the above should create a role, then a member, add the member to the role, then add the role to your database if I correctly understand the documentation that I linked.
As noted, there is not a constructor for ModelRoleMember, instead you have to use the derived classes that do have a constructor:
$RoleMember = New-Object Microsoft.AnalysisServices.Tabular.ExternalModelRoleMember
or
$RoleMember = New-Object Microsoft.AnalysisServices.Tabular.WindowsModelRoleMember
As for adding permissions you'll want to reference the documentation. It looks like you'll have to make Microsoft.AnalysisServices.Tabular.TablePermission objects, set their properties, and then add them to the TablePermissions property of $AddRole.

How to debug in TYPO3 if <f:debug> returns strings instead of object?

In a custom TYPO3 8.7.12 extbase extension I am unable to f:debug items in templates.
We are in the listAction controller and simply do:
$institutions = $this->institutionRepository->findAll();
$this->view->assignMultiple([
'institutions' => $institutions,
// ... pagination limit ...
]
);
And in the template:
<f:debug>
{institutions}
</f:debug>
This returns
sometimes the string 'Array' in the fluid debugger (can't reproduce now)
When the code is on 3 lines: #1273753083: Cannot cast object of type "TYPO3\CMS\Extbase\Persistence\Generic\QueryResult" to string.
Or also #1273753083: Cannot cast object of type "TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage" to string.
When the code is on 1 line: #1234386924: Cannot create empty instance of the class "TYPO3\CMS\Extbase\Persistence\ObjectStorage" because it does not implement the TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface.
If I loop through {institutions} with f:for and then f:debug:
<f:for each="{institutions}" as="institution" iteration="i">
<f:debug>
{institution}
</f:debug>
</f:for>
I get the first property of the object, e.g. the name.
EDIT: this is due to a __toString() magic method in the model. If I remove it, instead I get the namespace and uid STUBR\Extension\Domain\Model\Institution:55 – this looks again as if the object isn't rendered.
Wait... php.net says The __toString() method allows a class to decide how it will react when it is treated like a string. So could something be treating (typecasting?) the object as a string?
Working with the properties is normal, the issue just occurs when trying to print the whole object.
Where should I look? Lazy loading? There are some lazy loading properties, but not that many. Or maybe something is missing from the class? Or is there a workaround.
PS:
Unable to print_r or var_dump the query result, I get a memory limit error.
I saw https://wiki.typo3.org/Exception/CMS/1234386924 but initStorageObjects() is already called in the constructor
To answer the question;
<f:debug>{institutions}</f:debug>
will be parsed as an object, but any whitespace inside will make it parse as a string so.
The following methods do the same job as <f:debug> and work similarly in my case:
\TYPO3\CMS\Core\Utility\DebugUtility::debug(
$var = $variable,
$header = 'Institutions',
$group = ''
);
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump(
$variable,
$title = 'Institutions',
$maxDepth = 8,
$plainText = FALSE,
$ansiColors = TRUE,
$return = FALSE,
$blacklistedClassNames = NULL,
$blacklistedPropertyNames = NULL
);
execute in list or show action in controller.
It's less convenient than with f:debug (because you have to do the work in two different places, e.g. when you're in a loop in the template, you have to go to the controller and build that loop again), but it's a helpful workaround.
EDIT: I found it's sufficient to do
<f:debug>{var}</f:debug>
on one line

Joining together strings into Powershell variable names

I'm working with treeviews in Powershell - I have a different node for each of our buildings. In my code I'm grabbing variables, Joining them together, and using that as the variable name - but my code is seeing the variable as a string instead of the name of a node that already exists... so I'm getting
You cannot call a method on a null-valued expression.
How can I do this? It would save me from hard-coding in every floor in every building. Here's what my code looks like:
$bldg = "SG1-1" //for example
function refreshPrinterList ( $bldg )
{
$bldg1 = $bldg.substring(0,3)
$floor = $bldg.substring(4,1)
$refreshNode = -join('$TreeNode_',$bldg1,'_',$floor)
$refreshNode.gettype() //Returns System.String`
if($bldg1 -eq "SG1") {
if($floor -eq "1") {
$count = $refreshNode.Nodes.Count
while($refreshNode.Nodes.Count -gt 0)
{
$refreshNode.Nodes.RemoveAt($count)
$count--
}
The -join operator is for strings, and dutifully gives you one back instead of a TreeNode that you want. If you are passing in a string ($bldg looks like a string from your example), then you can do all the string manipulation you want, but there is no TreeNode object in that function to assign a name to. So, we need to make a TreeNode that your function could use. What about something like this?
$newNodeName = -join('$TreeNode_',$bldg1,'_',$floor)
$refreshNode = New-Object System.Windows.Forms.TreeNode($newNodeName )
// do stuff with $refreshNode as it is a TreeNode object like you expect
This $refreshNode will have no Nodes inside of it since we just fabbed it up. Since it looks like you want to modify an existing TreeNode object, pass in the $refreshNode as an argument then modify its friendly description with the $newNodeName.
I was pointed in the right direction over on the Technet Social forum
My question on Technet
The answer was using 'Get-Variable'
I had the two variables $bldg1 and $floor which I joined into a string:
$newNodeName = -join('TreeNode_',$bldg1,'_',$floor)
and then I passed that using 'Get-Variable' - but I had to put the variable name within parantheses, like so:
$refreshNode = (Get-Variable ($newNodeName)).Value
Now, instead of returning a string type it returns my existing string!

How to get rid of the __actions__ entry in the CrudRestController's response?

I'm subclassing the CrudRestController to implement an REST interface. It works fine, but the response dict contains an __actions__ entry which contains some html code that I really don't want in my response.
According to the TableFiller class' docstring something like this should work:
class ProcessController(CrudRestController):
model = Process
#...
class table_filler_type(TableFiller):
__model__ = Process
__actions__ = False
But the page always throws an AttributeError: 'Process' object has no attribute '__actions__'
Any advice?
Despite the inline docs, the correct way seems to be:
class table_filler_type(TableFiller):
__model__ = Process
__omit_fields__ = ['__actions__', ]