Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversion of [datetime] property in class does not work #20401

Open
5 tasks done
eddie-zalar opened this issue Sep 29, 2023 · 7 comments
Open
5 tasks done

Conversion of [datetime] property in class does not work #20401

eddie-zalar opened this issue Sep 29, 2023 · 7 comments
Labels
Issue-Bug Issue has been identified as a bug in the product Up-for-Grabs Up-for-grabs issues are not high priorities, and may be opportunities for external contributors WG-Engine core PowerShell engine, interpreter, and runtime

Comments

@eddie-zalar
Copy link

eddie-zalar commented Sep 29, 2023

Prerequisites

Steps to reproduce

I implemented this class in file "statistics.ps1" and want to use the automatic cast/conversion utilities of PowerShell:

class Statistics {
	[ValidateNotNullOrEmpty()]
	[nullable[datetime]] $LastDownload

	[ValidateRange("Positive")]
	[int] $Downloads

	[ValidateNotNull()]
	[string] $DownloadedBy = "n/a"
}

Expected behavior

PS> . .\statistics.ps1
PS> $object = [pscustomobject]@{ LastDownload = (Get-Date); Downloads = 123; DownloadedBy = "Jesus!" }
PS> [StatisticsExp]$object

LastDownload        Downloads DownloadedBy
------------        --------- ------------
29.09.2023 15:04:40       123 Jesus!

Actual behavior

PS> . .\statistics.ps1
PS> $object = [pscustomobject]@{ LastDownload = (Get-Date); Downloads = 123; DownloadedBy = "Jesus!" }
PS> [StatisticsExp]$object
InvalidArgument: Cannot convert value "@{LastDownload=29.09.2023 15:04:40; Downloads=123; DownloadedBy=Jesus!}" to type "Statistics". Error: "Cannot convert value "29.09.2023 15:04:40" to type "System.Nullable`1[System.DateTime]". Error: "Cannot process argument because the value of argument "obj" is null. Change the value of argument "obj" to a non-null value.""

Error details

PS> Get-Error

Exception             : 
    Type           : System.Management.Automation.RuntimeException
    ErrorRecord    : 
        Exception             : 
            Type    : System.Management.Automation.ParentContainsErrorRecordException
            Message : Cannot convert value "@{LastDownload=29.09.2023 15:04:40; Downloads=123; DownloadedBy=Jesus!}" to type "Statistics". Error: "Cannot convert value "29.09.2023 15:04:40" to type "System.Nullable`1[System.DateTime]". Error: "Cannot process argument 
because the value of argument "obj" is null. Change the value of argument "obj" to a non-null value.""
            HResult : -2146233087
        CategoryInfo          : InvalidArgument: (:) [], ParentContainsErrorRecordException
        FullyQualifiedErrorId : InvalidCastConstructorException
        InvocationInfo        : 
            ScriptLineNumber : 1
            OffsetInLine     : 1
            HistoryId        : -1
            Line             : [Statistics]$object
            PositionMessage  : At line:1 char:1
                               + [Statistics]$object
                               + ~~~~~~~~~~~~~~~~~~~
            CommandOrigin    : Internal
        ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
    Message        : Cannot convert value "@{LastDownload=29.09.2023 15:04:40; Downloads=123; DownloadedBy=Jesus!}" to type "Statistics". Error: "Cannot convert value "29.09.2023 15:04:40" to type "System.Nullable`1[System.DateTime]". Error: "Cannot process argument  
because the value of argument "obj" is null. Change the value of argument "obj" to a non-null value.""
    InnerException : 
        Type           : System.Management.Automation.PSInvalidCastException
        ErrorRecord    : 
            Exception             : 
                Type    : System.Management.Automation.ParentContainsErrorRecordException
                Message : Cannot convert value "@{LastDownload=29.09.2023 15:04:40; Downloads=123; DownloadedBy=Jesus!}" to type "Statistics". Error: "Cannot convert value "29.09.2023 15:04:40" to type "System.Nullable`1[System.DateTime]". Error: "Cannot process      
argument because the value of argument "obj" is null. Change the value of argument "obj" to a non-null value.""
                HResult : -2146233087
            CategoryInfo          : InvalidArgument: (:) [], ParentContainsErrorRecordException
            FullyQualifiedErrorId : InvalidCastConstructorException
            InvocationInfo        : 
                ScriptLineNumber : 1
                OffsetInLine     : 1
                HistoryId        : -1
                Line             : [Statistics]$object
                PositionMessage  : At line:1 char:1
                                   + [Statistics]$object
                                   + ~~~~~~~~~~~~~~~~~~~
                CommandOrigin    : Internal
            ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
        TargetSite     : 
            Name          : Convert
            DeclaringType : System.Management.Automation.LanguagePrimitives+ConvertViaNoArgumentConstructor, System.Management.Automation, Version=7.3.7.500, Culture=neutral, PublicKeyToken=31bf3856ad364e35
            MemberType    : Method
            Module        : System.Management.Automation.dll
        Message        : Cannot convert value "@{LastDownload=29.09.2023 15:04:40; Downloads=123; DownloadedBy=Jesus!}" to type "Statistics". Error: "Cannot convert value "29.09.2023 15:04:40" to type "System.Nullable`1[System.DateTime]". Error: "Cannot process       
argument because the value of argument "obj" is null. Change the value of argument "obj" to a non-null value.""
        Data           : System.Collections.ListDictionaryInternal
        InnerException : 
            Type           : System.Management.Automation.PSInvalidCastException
            ErrorRecord    : 
                Exception             : 
                    Type    : System.Management.Automation.ParentContainsErrorRecordException
                    Message : Cannot convert value "29.09.2023 15:04:40" to type "System.Nullable`1[System.DateTime]". Error: "Cannot process argument because the value of argument "obj" is null. Change the value of argument "obj" to a non-null value."
                    HResult : -2146233087
                CategoryInfo          : InvalidArgument: (:) [], ParentContainsErrorRecordException
                FullyQualifiedErrorId : InvalidCastConstructorException
            TargetSite     : 
                Name          : Convert
                DeclaringType : System.Management.Automation.LanguagePrimitives+ConvertViaNoArgumentConstructor, System.Management.Automation, Version=7.3.7.500, Culture=neutral, PublicKeyToken=31bf3856ad364e35
                MemberType    : Method
                Module        : System.Management.Automation.dll
            Message        : Cannot convert value "29.09.2023 15:04:40" to type "System.Nullable`1[System.DateTime]". Error: "Cannot process argument because the value of argument "obj" is null. Change the value of argument "obj" to a non-null value."
            InnerException : 
                Type        : System.Management.Automation.PSArgumentNullException
                ErrorRecord : 
                    Exception             : 
                        Type    : System.Management.Automation.ParentContainsErrorRecordException
                        Message : Cannot process argument because the value of argument "obj" is null. Change the value of argument "obj" to a non-null value.
                        HResult : -2146233087
                    CategoryInfo          : InvalidArgument: (:) [], ParentContainsErrorRecordException
                    FullyQualifiedErrorId : ArgumentNull
                Message     : Cannot process argument because the value of argument "obj" is null. Change the value of argument "obj" to a non-null value.
                ParamName   : obj
                TargetSite  : 
                    Name          : AsPSObject
                    DeclaringType : psobject
                    MemberType    : Method
                    Module        : System.Management.Automation.dll
                Source      : System.Management.Automation
                HResult     : -2147467261
                StackTrace  : 
   at System.Management.Automation.PSObject.AsPSObject(Object obj, Boolean storeTypeNameAndInstanceMembersLocally)
   at System.Management.Automation.LanguagePrimitives.SetObjectProperties(Object o, IDictionary properties, Type resultType, MemberNotFoundError memberNotFoundErrorAction, MemberSetValueError memberSetValueErrorAction, Boolean enableMethodCall, IFormatProvider
formatProvider, Boolean recursion, Boolean ignoreUnknownMembers)
   at System.Management.Automation.LanguagePrimitives.ConvertViaNoArgumentConstructor.Convert(Object valueToConvert, Type resultType, Boolean recursion, PSObject originalValueToConvert, IFormatProvider formatProvider, TypeTable backupTable, Boolean ignoreUnknownMembers) 
            Source         : System.Management.Automation
            HResult        : -2147467262
            StackTrace     : 
   at System.Management.Automation.LanguagePrimitives.ConvertViaNoArgumentConstructor.Convert(Object valueToConvert, Type resultType, Boolean recursion, PSObject originalValueToConvert, IFormatProvider formatProvider, TypeTable backupTable, Boolean ignoreUnknownMembers) 
   at System.Management.Automation.LanguagePrimitives.SetObjectProperties(Object o, IDictionary properties, Type resultType, MemberNotFoundError memberNotFoundErrorAction, MemberSetValueError memberSetValueErrorAction, Boolean enableMethodCall, IFormatProvider
formatProvider, Boolean recursion, Boolean ignoreUnknownMembers)
   at System.Management.Automation.LanguagePrimitives.SetObjectProperties(Object o, PSObject psObject, Type resultType, MemberNotFoundError memberNotFoundErrorAction, MemberSetValueError memberSetValueErrorAction, IFormatProvider formatProvider, Boolean recursion,       
Boolean ignoreUnknownMembers)
   at System.Management.Automation.LanguagePrimitives.ConvertViaNoArgumentConstructor.Convert(Object valueToConvert, Type resultType, Boolean recursion, PSObject originalValueToConvert, IFormatProvider formatProvider, TypeTable backupTable, Boolean ignoreUnknownMembers) 
        Source         : System.Management.Automation
        HResult        : -2147467262
        StackTrace     : 
   at System.Management.Automation.LanguagePrimitives.ConvertViaNoArgumentConstructor.Convert(Object valueToConvert, Type resultType, Boolean recursion, PSObject originalValueToConvert, IFormatProvider formatProvider, TypeTable backupTable, Boolean ignoreUnknownMembers) 
   at System.Management.Automation.LanguagePrimitives.ConvertViaNoArgumentConstructor.Convert(Object valueToConvert, Type resultType, Boolean recursion, PSObject originalValueToConvert, IFormatProvider formatProvider, TypeTable backupTable)
   at CallSite.Target(Closure, CallSite, Object)
   at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
   at System.Management.Automation.Interpreter.DynamicInstruction`2.Run(InterpretedFrame frame)
   at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
    HResult        : -2146233087
CategoryInfo          : InvalidArgument: (:) [], RuntimeException
FullyQualifiedErrorId : InvalidCastConstructorException
InvocationInfo        : 
    ScriptLineNumber : 1
    OffsetInLine     : 1
    HistoryId        : -1
    Line             : [Statistics]$object
    PositionMessage  : At line:1 char:1
                       + [Statistics]$object
                       + ~~~~~~~~~~~~~~~~~~~
    CommandOrigin    : Internal
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1

Environment data

PS> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      7.3.7
PSEdition                      Core
GitCommitId                    7.3.7
OS                             Microsoft Windows 10.0.19044
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Visuals

No response

@eddie-zalar eddie-zalar added the Needs-Triage The issue is new and needs to be triaged by a work group. label Sep 29, 2023
@eddie-zalar
Copy link
Author

I found a workaround for the problem by implementing the explicit operator:

class StatisticsExp {
	[ValidateNotNullOrEmpty()]
	[nullable[datetime]] $LastDownload

	[ValidateRange("Positive")]
	[int] $Downloads

	[ValidateNotNull()]
	[string] $DownloadedBy = "n/a"

	static [StatisticsExp] op_Explicit(
		$Other <# intentionally typeless to fulfill the interface definition #>
	) {
		[StatisticsExp] $converted = [StatisticsExp]::new()
		$converted.LastDownload = $Other.LastDownload
		$converted.Downloads = $Other.Downloads
		$converted.DownloadedBy = $Other.DownloadedBy
		return $converted
	}
}

Now it works as expected.

@jhoneill
Copy link

It's odd - if you remove nullable the object is created without setting the date. Which makes me wonder if there are limitations on the property-types which transfer on an automatic cast.

@eddie-zalar
Copy link
Author

In the meantime I found out that it works with a hashtable as input - instead of a PSCustomObject:

PS > $ht = @{ LastDownload = (Get-Date); Downloads = 123; DownloadedBy = "Jesus!" }
PS > [Statistics]$ht    

LastDownload        Downloads DownloadedBy
------------        --------- ------------
29.09.2023 15:49:08       123 Jesus!

@eddie-zalar
Copy link
Author

From the call stack it may be that this line of code is the one responsible for the error message:

IDictionary properties = valueToConvert as IDictionary;

If I use a PSCustomObject as input, the properties evaluates to null because it does not implement IDictionary and the usage of the as cast operator.
In case I pass a Hashtable as input, properties is not null because the Hashtable implements IDictionary and it works.

@mklement0
Copy link
Contributor

Let me try to summarize:

The following repro code demonstrates this:

class Statistics1 { [nullable[datetime]] $LastDownload } # nullable
class Statistics2 { [datetime] $LastDownload } # non-nullable

[pscustomobject] @{
  'Nullable, pscustomobject' = $(try { [Statistics1] [pscustomobject] @{ LastDownLoad = Get-Date } } catch { "ERROR: $_" }) | Out-String
  'Nullable, pscustomobject, no psobject wrapper' = $(try { [Statistics1] [pscustomobject] @{ LastDownLoad = [datetime]::Now } } catch { "ERROR: $_" }) | Out-String
  'Nullable, hashtable' =  [Statistics1] @{ LastDownLoad = Get-Date } | Out-String
  'Non-nullable, pscustomobject' =  [Statistics2] [pscustomobject] @{ LastDownLoad = Get-Date } | Out-String
  'Non-nullable, pscustomobject, no psobject wrapper' =  [Statistics2] [pscustomobject] @{ LastDownLoad = [datetime]::Now } | Out-String
  'Non-nullable, hashtable' =  [Statistics2] @{ LastDownLoad = Get-Date } | Out-String
} | Format-List

Output:

Nullable, pscustomobject                          : ERROR: Cannot convert value "@{LastDownLoad=9/29/2023 11:25:34 AM}" to type "Statistics1". Error: "Cannot convert value
                                                    "9/29/2023 11:25:34 AM" to type "System.Nullable`1[System.DateTime]". Error: "Cannot process argument because the value of
                                                    argument "obj" is null. Change the value of argument "obj" to a non-null value.""

Nullable, pscustomobject, no psobject wrapper     :
                                                    LastDownload
                                                    ------------
                                                    9/29/2023 11:25:34 AM


Nullable, hashtable                               :
                                                    LastDownload
                                                    ------------
                                                    9/29/2023 11:25:34 AM


Non-nullable, pscustomobject                      :
                                                    LastDownload
                                                    ------------
                                                    1/1/0001 12:00:00 AM


Non-nullable, pscustomobject, no psobject wrapper :
                                                    LastDownload
                                                    ------------
                                                    9/29/2023 11:25:34 AM


Non-nullable, hashtable                           :
                                                    LastDownload
                                                    ------------
                                                    9/29/2023 11:25:34 AM

Note how the use of non-[psobject]-wrapped property values works in all cases.


Conceptually related (in that initialization by [pscustomobject] behaves differently than initialization by hashtable):

@StevenBucher98 StevenBucher98 added Issue-Bug Issue has been identified as a bug in the product WG-Engine core PowerShell engine, interpreter, and runtime labels Oct 9, 2023
@SeeminglyScience SeeminglyScience removed the Needs-Triage The issue is new and needs to be triaged by a work group. label Nov 13, 2023
@SeeminglyScience
Copy link
Collaborator

SeeminglyScience commented Nov 13, 2023

The Engine WG discussed this and agree it's a bug

And also that most likely the cause of the bug is that the conversion path isn't handling when DateTime is wrapped in PSObject

@SeeminglyScience SeeminglyScience added the Up-for-Grabs Up-for-grabs issues are not high priorities, and may be opportunities for external contributors label Nov 13, 2023
@microsoft-github-policy-service microsoft-github-policy-service bot added the Resolution-No Activity Issue has had no activity for 6 months or more label May 12, 2024
@eddie-zalar
Copy link
Author

Resolved and closed by "no activity". OK, it is not such a big bug...
But I wonder if this is the way how MS will implement the announced "security first initiative"?
Just asking...

@microsoft-github-policy-service microsoft-github-policy-service bot removed the Resolution-No Activity Issue has had no activity for 6 months or more label May 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue-Bug Issue has been identified as a bug in the product Up-for-Grabs Up-for-grabs issues are not high priorities, and may be opportunities for external contributors WG-Engine core PowerShell engine, interpreter, and runtime
Projects
None yet
Development

No branches or pull requests

5 participants