Skip to content

Instantly share code, notes, and snippets.

@thedavecarroll
Last active May 30, 2023 13:35
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save thedavecarroll/a0db4e3b3c97941ddf11e161288408d7 to your computer and use it in GitHub Desktop.
Save thedavecarroll/a0db4e3b3c97941ddf11e161288408d7 to your computer and use it in GitHub Desktop.
Creating a Class Definition from an Existing Object - Ironscripter Challenge
#Requires -Version 5.1
function ConvertTo-ClassDefinition {
param(
[Parameter(Position = 0, Mandatory, ValueFromPipeline)]
[object]$Object,
[ValidateNotNullOrEmpty()]
[string]$ClassName,
[ValidateNotNullOrEmpty()]
[Alias('Include')]
[string[]]$IncludeProperty = '*',
[ValidateNotNullOrEmpty()]
[Alias('Exclude')]
[string[]]$ExcludeProperty
)
# get the immediate object type
$ObjectType = $Object.psobject.TypeNames[0]
# set the name of the class
if ($ClassName) {
$BaseClassName = $ClassName
} else {
$BaseClassName = "My" + $ObjectType
}
# get properties
if ($IncludeProperty -ne '*') {
$Properties = $Object.psobject.properties | Where-Object {
$_.Name -in $IncludeProperty -and
$_.Name -notin $ExcludeProperty
}
} else {
$Properties = $Object.psobject.properties | Where-Object {
$_.Name -notin $ExcludeProperty
}
}
# create stringbuilder
$Indent = ' ' * 4
$ClassDefinition = [StringBuilder]::new()
[void]$ClassDefinition.AppendLine(('# class definition created by {0} at {1} for object type {2}' -f $MyInvocation.MyCommand,(Get-Date),$ObjectType))
[void]$ClassDefinition.AppendLine('')
[void]$ClassDefinition.AppendLine(('class {0} {{' -f $BaseClassName ))
[void]$ClassDefinition.AppendLine('')
# add all properties
[void]$ClassDefinition.AppendLine(('{0}# properties' -f $Indent))
foreach ($Property in $Properties) {
[void]$ClassDefinition.AppendLine(('{0}[{1}]${2}' -f $Indent,$Property.TypeNameOfValue,$Property.Name))
}
[void]$ClassDefinition.AppendLine('')
# add simple constructors
[void]$ClassDefinition.AppendLine(('{0}# constructors' -f $Indent))
[void]$ClassDefinition.AppendLine(('{0}{1} () {{ }}' -f $Indent,$BaseClassName))
[void]$ClassDefinition.AppendLine(('{0}{1} ([{2}]$InputObject) {{' -f $Indent,$BaseClassName,$ObjectType))
foreach ($Property in $Properties) {
[void]$ClassDefinition.AppendLine(('{0}{0}$this.{1} = $InputObject.{1}' -f $Indent,$Property.Name))
}
[void]$ClassDefinition.AppendLine(('{0}}}' -f $Indent))
[void]$ClassDefinition.AppendLine('')
# end class
[void]$ClassDefinition.AppendLine('}')
# output class definition
$ClassDefinition.ToString()
}
# class definition created by ConvertTo-ClassDefinition at 1/30/2021 11:55:19 AM for object type PSCustomObject
class BluebirdPS {
# properties
[System.Int64]$Id
hidden [String]$IdStr
[String]$Name
[String]$ScreenName
[String]$Location
[System.Object]$ProfileLocation
[String]$Description
[Uri]$Url
[PSCustomObject]$Entities
[System.Boolean]$Protected
[System.Int64]$FollowersCount
[System.Int64]$FriendsCount
[System.Int64]$ListedCount
[DateTime]$CreatedAt
[System.Int64]$FavouritesCount
[System.Object]$UtcOffset
[System.Object]$TimeZone
[System.Boolean]$GeoEnabled
[System.Boolean]$Verified
[System.Int64]$StatusesCount
[System.Object]$Lang
[PSCustomObject]$Status
[System.Boolean]$ContributorsEnabled
[System.Boolean]$IsTranslator
[System.Boolean]$IsTranslationEnabled
[String]$ProfileBackgroundColor
[Uri]$ProfileBackgroundImageUrl
[Uri]$ProfileBackgroundImageUrlHttps
[System.Boolean]$ProfileBackgroundTile
[Uri]$ProfileImageUrl
[Uri]$ProfileImageUrlHttps
[Uri]$ProfileBannerUrl
[String]$ProfileLinkColor
[String]$ProfileSidebarBorderColor
[String]$ProfileSidebarFillColor
[String]$ProfileTextColor
[System.Boolean]$ProfileUseBackgroundImage
[System.Boolean]$HasExtendedProfile
[System.Boolean]$DefaultProfile
[System.Boolean]$DefaultProfileImage
[System.Boolean]$Following
[System.Boolean]$FollowRequestSent
[System.Boolean]$Notifications
[String]$TranslatorType
[System.Boolean]$Suspended
[System.Boolean]$NeedsPhoneVerification
# constructors
BluebirdPS () { }
BluebirdPS ([PSCustomObject]$InputObject) {
$this.Id = $InputObject.id
$this.IdStr = $InputObject.id_str
$this.Name = $InputObject.name
$this.ScreenName = $InputObject.screen_name
$this.Location = $InputObject.location
$this.ProfileLocation = $InputObject.profile_location
$this.Description = $InputObject.description
$this.Url = $InputObject.url
$this.Entities = $InputObject.entities
$this.Protected = $InputObject.protected
$this.FollowersCount = $InputObject.followers_count
$this.FriendsCount = $InputObject.friends_count
$this.ListedCount = $InputObject.listed_count
$this.CreatedAt = [datetime]::ParseExact($InputObject.created_at,'ddd MMM dd HH:mm:ss zzz yyyy',(Get-Culture))
$this.FavouritesCount = $InputObject.favourites_count
$this.UtcOffset = $InputObject.utc_offset
$this.TimeZone = $InputObject.time_zone
$this.GeoEnabled = $InputObject.geo_enabled
$this.Verified = $InputObject.verified
$this.StatusesCount = $InputObject.statuses_count
$this.Lang = $InputObject.lang
$this.Status = $InputObject.status
$this.ContributorsEnabled = $InputObject.contributors_enabled
$this.IsTranslator = $InputObject.is_translator
$this.IsTranslationEnabled = $InputObject.is_translation_enabled
$this.ProfileBackgroundColor = $InputObject.profile_background_color
$this.ProfileBackgroundImageUrl = $InputObject.profile_background_image_url
$this.ProfileBackgroundImageUrlHttps = $InputObject.profile_background_image_url_https
$this.ProfileBackgroundTile = $InputObject.profile_background_tile
$this.ProfileImageUrl = $InputObject.profile_image_url
$this.ProfileImageUrlHttps = $InputObject.profile_image_url_https
$this.ProfileBannerUrl = $InputObject.profile_banner_url
$this.ProfileLinkColor = $InputObject.profile_link_color
$this.ProfileSidebarBorderColor = $InputObject.profile_sidebar_border_color
$this.ProfileSidebarFillColor = $InputObject.profile_sidebar_fill_color
$this.ProfileTextColor = $InputObject.profile_text_color
$this.ProfileUseBackgroundImage = $InputObject.profile_use_background_image
$this.HasExtendedProfile = $InputObject.has_extended_profile
$this.DefaultProfile = $InputObject.default_profile
$this.DefaultProfileImage = $InputObject.default_profile_image
$this.Following = $InputObject.following
$this.FollowRequestSent = $InputObject.follow_request_sent
$this.Notifications = $InputObject.notifications
$this.TranslatorType = $InputObject.translator_type
$this.Suspended = $InputObject.suspended
$this.NeedsPhoneVerification = $InputObject.needs_phone_verification
}
}
#Requires -Version 5.1
using namespace System.Management.Automation
using namespace System.Text
function ConvertTo-ClassDefinition {
param(
[Parameter(Position = 0, Mandatory, ValueFromPipeline)]
[object]$Object,
[ValidateNotNullOrEmpty()]
[string]$ClassName,
[ValidateNotNullOrEmpty()]
[Alias('Include')]
[string[]]$IncludeProperty = '*',
[ValidateNotNullOrEmpty()]
[Alias('Exclude')]
[string[]]$ExcludeProperty,
[ValidateNotNullOrEmpty()]
[Alias('Hidden')]
[string[]]$HiddenProperty
)
# get the immediate object type
$ObjectTypeBase = $Object.psobject.TypeNames[0]
# part 2: correct PSCustomObject input object
if ($ObjectTypeBase -match 'PSCustomObject') {
$ObjectType = 'PSCustomObject'
} else {
$ObjectType = $ObjectTypeBase
}
# set the name of the class
# part 2: PascalCase the class name
if ($ClassName) {
$BaseClassName = $ClassName | ConvertTo-PascalCase
} else {
$BaseClassName = "My." + $ObjectType | ConvertTo-PascalCase
}
# get properties
if ($IncludeProperty -ne '*') {
$Properties = $Object.psobject.properties | Where-Object {
$_.Name -in $IncludeProperty -and
$_.Name -notin $ExcludeProperty
}
} else {
$Properties = $Object.psobject.properties | Where-Object {
$_.Name -notin $ExcludeProperty
}
}
# create stringbuilder
$Indent = ' ' * 4
$ClassDefinition = [StringBuilder]::new()
[void]$ClassDefinition.AppendLine(('# class definition created by {0} at {1} for object type {2}' -f $MyInvocation.MyCommand,(Get-Date),$ObjectType))
[void]$ClassDefinition.AppendLine('')
[void]$ClassDefinition.AppendLine(('class {0} {{' -f $BaseClassName))
[void]$ClassDefinition.AppendLine('')
# part 1: add all properties
# part 2: create a new stringbuilder for the properties in the constructor so we only have to loop through properties once
$ConstructorProperty = [StringBuilder]::new()
[void]$ClassDefinition.AppendLine(('{0}# properties' -f $Indent))
foreach ($Property in $Properties) {
# part 2: ensure proper PascalCase of the property
$PascalCaseProperty = ConvertTo-PascalCase -String $Property.Name
# part 2: ensure best guess for string values for PSCustomObject objects
$PropertySetter = '$InputObject.{0}' -f $Property.Name
if ($Property.TypeNameOfValue -match 'string' -and $ObjectType -eq 'PSCustomObject' ) {
$PropertyTypeConversion = Resolve-PropertyType -PropertyValue $Property.Value
$PropertyTypeName = $PropertyTypeConversion | Select-Object -First 1
if ('Parse' -in $PropertyTypeConversion) {
$PropertySetter = '[datetime]::Parse($InputObject.{0},(Get-Culture))' -f $Property.Name
} elseif ('ParseExact' -in $PropertyTypeConversion) {
$PropertySetter = '[datetime]::ParseExact($InputObject.{0},''{1}'',(Get-Culture))' -f $Property.Name,$PropertyTypeConversion[2]
}
} else {
if ($Property.TypeNameOfValue -match 'PSCustomObject') {
$PropertyTypeName = 'PSCustomObject'
} else {
$PropertyTypeName = $Property.TypeNameOfValue
}
}
# part 2: if a property needs to be hidden, add that to the property statement
if ($Property.Name -in $HiddenProperty -or $PascalCaseProperty -in $HiddenProperty) {
$PropertyStatement = '{0}hidden [{1}]${2}' -f $Indent,$PropertyTypeName,$PascalCaseProperty
} else {
$PropertyStatement = '{0}[{1}]${2}' -f $Indent,$PropertyTypeName,$PascalCaseProperty
}
[void]$ClassDefinition.AppendLine($PropertyStatement)
[void]$ConstructorProperty.AppendLine(('{0}{0}$this.{1} = {2}' -f $Indent,$PascalCaseProperty,$PropertySetter))
}
[void]$ClassDefinition.AppendLine('')
# add simple constructors
[void]$ClassDefinition.AppendLine(('{0}# constructors' -f $Indent))
[void]$ClassDefinition.AppendLine(('{0}{1} () {{ }}' -f $Indent,$BaseClassName))
[void]$ClassDefinition.AppendLine(('{0}{1} ([{2}]$InputObject) {{' -f $Indent,$BaseClassName,$ObjectType))
# part 2: ensure proper PascalCase in the constructor
[void]$ClassDefinition.AppendLine($ConstructorProperty.ToString())
[void]$ClassDefinition.AppendLine(('{0}}}' -f $Indent))
[void]$ClassDefinition.AppendLine('')
# end class
[void]$ClassDefinition.AppendLine('}')
# output class definition
$ClassDefinition.ToString()
}
function ConvertTo-PascalCase {
[CmdletBinding()]
param(
[Parameter(Mandatory,ValueFromPipeline)]
[string[]]$String
)
begin {
$Culture = Get-Culture
}
process {
foreach ($Word in $String) {
$Words = $Word -split '\W|_'
$PascalCase = if ($Words.Count -gt 1) {
$Words | ForEach-Object {
$Culture.TextInfo.ToTitleCase($_.ToLower())
}
} else {
if ($Word -cnotmatch '^[A-Z]') {
return $Culture.TextInfo.ToTitleCase($Word)
} else {
return $Word
}
}
}
$PascalCase -join ''
}
}
function Resolve-PropertyType {
[CmdletBinding()]
param(
[Parameter(Mandatory,ValueFromPipeline)]
[string]$PropertyValue
)
$TwitterDateFormat ='ddd MMM dd HH:mm:ss zzz yyyy'
# check for url
try {
[void][Uri]::new($PropertyValue)
return 'Uri'
}
catch { } # do nothing, continue to next text
# check for known date time
try {
[void][datetime]::Parse($PropertyValue,(Get-Culture))
return 'DateTime','Parse'
}
catch { }
# check for Twitter date time
try {
[void][datetime]::ParseExact($PropertyValue,$TwitterDateFormat,(Get-Culture))
return 'DateTime','ParseExact',$TwitterDateFormat
}
catch { }
return 'String'
}
# class definition created by ConvertTo-ClassDefinition at 2/2/2021 12:19:34 AM for object type PSCustomObject
class BluebirdPSUser {
# properties
hidden [String]$IdStr
[String]$Location
[String]$ScreenName
[System.Int64]$Id
[System.Boolean]$DefaultProfile
# constructors
BluebirdPSUser () { }
BluebirdPSUser ([PSCustomObject]$InputObject) {
$this.IdStr = $InputObject.id_str
$this.Location = $InputObject.location
$this.ScreenName = $InputObject.screen_name
$this.Id = $InputObject.id
$this.DefaultProfile = $InputObject.default_profile
}
<# original output from ConvertTo-ClassDefinition
[OutputTypeName] UpdateProfile (
[TypeName]$Param1,
[TypeName]$Param2
) {
# your code
return $Output
}
[OutputTypeName] SetLocation (
[TypeName]$Param1,
[TypeName]$Param2
) {
# your code
return $Output
}
#>
# update code
[string] UpdateProfile ( ) {
return ("Updated Profile for {0}" -f $this.ScreenName)
}
# update code
[bool] SetLocation () {
return $false
}
}
# class definition created by ConvertTo-ClassDefinition at 2/2/2021 1:01:11 AM for object type PSCustomObject
class BluebirdPSUserStatusRetweetedStatusEntities {
# properties
[System.Object[]]$Hashtags
[System.Object[]]$UserMentions
[System.Object[]]$Symbols
[System.Object[]]$Urls
# constructors
BluebirdPSUserStatusRetweetedStatusEntities () { }
BluebirdPSUserStatusRetweetedStatusEntities ([PSCustomObject]$InputObject) {
$this.Hashtags = $InputObject.hashtags
$this.UserMentions = $InputObject.user_mentions
$this.Symbols = $InputObject.symbols
$this.Urls = $InputObject.urls
}
}
# class definition created by ConvertTo-ClassDefinition at 2/2/2021 1:01:11 AM for object type PSCustomObject
class BluebirdPSUserStatusRetweetedStatus {
# properties
[System.Object]$Contributors
[System.Object]$InReplyToUserIdStr
[System.Boolean]$PossiblySensitive
[String]$Text
[System.Object]$Geo
[System.Int64]$Id
[System.Object]$InReplyToStatusId
[System.Boolean]$Truncated
[System.Object]$Coordinates
[System.Boolean]$IsQuoteStatus
[System.Boolean]$Retweeted
[System.Object]$InReplyToScreenName
[String]$Lang
[DateTime]$CreatedAt
[System.Int64]$RetweetCount
[PSCustomObject]$Entities
[System.Object]$InReplyToStatusIdStr
[String]$IdStr
[System.Boolean]$Favorited
[System.Object]$InReplyToUserId
[System.Object]$Place
[String]$Source
[System.Int64]$FavoriteCount
# constructors
BluebirdPSUserStatusRetweetedStatus () { }
BluebirdPSUserStatusRetweetedStatus ([PSCustomObject]$InputObject) {
$this.Contributors = $InputObject.contributors
$this.InReplyToUserIdStr = $InputObject.in_reply_to_user_id_str
$this.PossiblySensitive = $InputObject.possibly_sensitive
$this.Text = $InputObject.text
$this.Geo = $InputObject.geo
$this.Id = $InputObject.id
$this.InReplyToStatusId = $InputObject.in_reply_to_status_id
$this.Truncated = $InputObject.truncated
$this.Coordinates = $InputObject.coordinates
$this.IsQuoteStatus = $InputObject.is_quote_status
$this.Retweeted = $InputObject.retweeted
$this.InReplyToScreenName = $InputObject.in_reply_to_screen_name
$this.Lang = $InputObject.lang
$this.CreatedAt = [datetime]::ParseExact($InputObject.created_at,'ddd MMM dd HH:mm:ss zzz yyyy',(Get-Culture))
$this.RetweetCount = $InputObject.retweet_count
$this.Entities = $InputObject.entities
$this.InReplyToStatusIdStr = $InputObject.in_reply_to_status_id_str
$this.IdStr = $InputObject.id_str
$this.Favorited = $InputObject.favorited
$this.InReplyToUserId = $InputObject.in_reply_to_user_id
$this.Place = $InputObject.place
$this.Source = $InputObject.source
$this.FavoriteCount = $InputObject.favorite_count
}
}
# class definition created by ConvertTo-ClassDefinition at 2/2/2021 1:01:11 AM for object type PSCustomObject
class BluebirdPSUserStatusEntities {
# properties
[System.Object[]]$Hashtags
[System.Object[]]$UserMentions
[System.Object[]]$Symbols
[System.Object[]]$Urls
# constructors
BluebirdPSUserStatusEntities () { }
BluebirdPSUserStatusEntities ([PSCustomObject]$InputObject) {
$this.Hashtags = $InputObject.hashtags
$this.UserMentions = $InputObject.user_mentions
$this.Symbols = $InputObject.symbols
$this.Urls = $InputObject.urls
}
}
# class definition created by ConvertTo-ClassDefinition at 2/2/2021 1:01:11 AM for object type PSCustomObject
class BluebirdPSUserStatus {
# properties
[System.Object]$Contributors
[System.Object]$InReplyToUserIdStr
[String]$Text
[System.Object]$Geo
[System.Int64]$Id
[System.Object]$InReplyToStatusId
[System.Boolean]$Truncated
[System.Object]$Coordinates
[PSCustomObject]$RetweetedStatus
[System.Boolean]$Retweeted
[System.Object]$InReplyToScreenName
[String]$Lang
[DateTime]$CreatedAt
[System.Int64]$RetweetCount
[PSCustomObject]$Entities
[System.Boolean]$IsQuoteStatus
[System.Object]$InReplyToStatusIdStr
[String]$IdStr
[System.Boolean]$Favorited
[System.Object]$InReplyToUserId
[System.Object]$Place
[String]$Source
[System.Int64]$FavoriteCount
# constructors
BluebirdPSUserStatus () { }
BluebirdPSUserStatus ([PSCustomObject]$InputObject) {
$this.Contributors = $InputObject.contributors
$this.InReplyToUserIdStr = $InputObject.in_reply_to_user_id_str
$this.Text = $InputObject.text
$this.Geo = $InputObject.geo
$this.Id = $InputObject.id
$this.InReplyToStatusId = $InputObject.in_reply_to_status_id
$this.Truncated = $InputObject.truncated
$this.Coordinates = $InputObject.coordinates
$this.RetweetedStatus = $InputObject.retweeted_status
$this.Retweeted = $InputObject.retweeted
$this.InReplyToScreenName = $InputObject.in_reply_to_screen_name
$this.Lang = $InputObject.lang
$this.CreatedAt = [datetime]::ParseExact($InputObject.created_at,'ddd MMM dd HH:mm:ss zzz yyyy',(Get-Culture))
$this.RetweetCount = $InputObject.retweet_count
$this.Entities = $InputObject.entities
$this.IsQuoteStatus = $InputObject.is_quote_status
$this.InReplyToStatusIdStr = $InputObject.in_reply_to_status_id_str
$this.IdStr = $InputObject.id_str
$this.Favorited = $InputObject.favorited
$this.InReplyToUserId = $InputObject.in_reply_to_user_id
$this.Place = $InputObject.place
$this.Source = $InputObject.source
$this.FavoriteCount = $InputObject.favorite_count
}
}
# class definition created by ConvertTo-ClassDefinition at 2/2/2021 1:01:11 AM for object type PSCustomObject
class BluebirdPSUserEntitiesDescription {
# properties
[System.Object[]]$Urls
# constructors
BluebirdPSUserEntitiesDescription () { }
BluebirdPSUserEntitiesDescription ([PSCustomObject]$InputObject) {
$this.Urls = $InputObject.urls
}
}
# class definition created by ConvertTo-ClassDefinition at 2/2/2021 1:01:11 AM for object type PSCustomObject
class BluebirdPSUserEntitiesUrl {
# properties
[System.Object[]]$Urls
# constructors
BluebirdPSUserEntitiesUrl () { }
BluebirdPSUserEntitiesUrl ([PSCustomObject]$InputObject) {
$this.Urls = $InputObject.urls
}
}
# class definition created by ConvertTo-ClassDefinition at 2/2/2021 1:01:11 AM for object type PSCustomObject
class BluebirdPSUserEntities {
# properties
[PSCustomObject]$Description
[PSCustomObject]$Url
# constructors
BluebirdPSUserEntities () { }
BluebirdPSUserEntities ([PSCustomObject]$InputObject) {
$this.Description = $InputObject.description
$this.Url = $InputObject.url
}
}
# class definition created by ConvertTo-ClassDefinition at 2/2/2021 1:01:11 AM for object type PSCustomObject
class BluebirdPSUser {
# properties
[System.Boolean]$NeedsPhoneVerification
[System.Boolean]$Protected
[String]$ProfileBackgroundColor
[System.Boolean]$HasExtendedProfile
[System.Boolean]$IsTranslationEnabled
[Uri]$ProfileBackgroundImageUrl
[Uri]$Url
[System.Int64]$Id
[String]$Description
[System.Object]$TimeZone
[System.Object]$Lang
[System.Boolean]$Suspended
[PSCustomObject]$Status
[System.Int64]$FavouritesCount
[System.Boolean]$ProfileBackgroundTile
[System.Boolean]$Verified
[System.Boolean]$FollowRequestSent
[System.Int64]$ListedCount
[System.Boolean]$ProfileUseBackgroundImage
[Uri]$ProfileImageUrl
[System.Int64]$FriendsCount
[String]$ProfileLinkColor
[System.Boolean]$GeoEnabled
[System.Boolean]$ContributorsEnabled
[String]$ProfileSidebarFillColor
[String]$Location
[System.Boolean]$Notifications
[System.Boolean]$DefaultProfile
[String]$ProfileTextColor
[String]$ScreenName
[System.Object]$ProfileLocation
[System.Int64]$FollowersCount
[String]$Name
[System.Boolean]$Following
[Uri]$ProfileBannerUrl
[String]$TranslatorType
[String]$IdStr
[System.Object]$UtcOffset
[Uri]$ProfileImageUrlHttps
[DateTime]$CreatedAt
[System.Int64]$StatusesCount
[Uri]$ProfileBackgroundImageUrlHttps
[PSCustomObject]$Entities
[String]$ProfileSidebarBorderColor
[System.Boolean]$IsTranslator
[System.Boolean]$DefaultProfileImage
# constructors
BluebirdPSUser () { }
BluebirdPSUser ([PSCustomObject]$InputObject) {
$this.NeedsPhoneVerification = $InputObject.needs_phone_verification
$this.Protected = $InputObject.protected
$this.ProfileBackgroundColor = $InputObject.profile_background_color
$this.HasExtendedProfile = $InputObject.has_extended_profile
$this.IsTranslationEnabled = $InputObject.is_translation_enabled
$this.ProfileBackgroundImageUrl = $InputObject.profile_background_image_url
$this.Url = $InputObject.url
$this.Id = $InputObject.id
$this.Description = $InputObject.description
$this.TimeZone = $InputObject.time_zone
$this.Lang = $InputObject.lang
$this.Suspended = $InputObject.suspended
$this.Status = $InputObject.status
$this.FavouritesCount = $InputObject.favourites_count
$this.ProfileBackgroundTile = $InputObject.profile_background_tile
$this.Verified = $InputObject.verified
$this.FollowRequestSent = $InputObject.follow_request_sent
$this.ListedCount = $InputObject.listed_count
$this.ProfileUseBackgroundImage = $InputObject.profile_use_background_image
$this.ProfileImageUrl = $InputObject.profile_image_url
$this.FriendsCount = $InputObject.friends_count
$this.ProfileLinkColor = $InputObject.profile_link_color
$this.GeoEnabled = $InputObject.geo_enabled
$this.ContributorsEnabled = $InputObject.contributors_enabled
$this.ProfileSidebarFillColor = $InputObject.profile_sidebar_fill_color
$this.Location = $InputObject.location
$this.Notifications = $InputObject.notifications
$this.DefaultProfile = $InputObject.default_profile
$this.ProfileTextColor = $InputObject.profile_text_color
$this.ScreenName = $InputObject.screen_name
$this.ProfileLocation = $InputObject.profile_location
$this.FollowersCount = $InputObject.followers_count
$this.Name = $InputObject.name
$this.Following = $InputObject.following
$this.ProfileBannerUrl = $InputObject.profile_banner_url
$this.TranslatorType = $InputObject.translator_type
$this.IdStr = $InputObject.id_str
$this.UtcOffset = $InputObject.utc_offset
$this.ProfileImageUrlHttps = $InputObject.profile_image_url_https
$this.CreatedAt = [datetime]::ParseExact($InputObject.created_at,'ddd MMM dd HH:mm:ss zzz yyyy',(Get-Culture))
$this.StatusesCount = $InputObject.statuses_count
$this.ProfileBackgroundImageUrlHttps = $InputObject.profile_background_image_url_https
$this.Entities = $InputObject.entities
$this.ProfileSidebarBorderColor = $InputObject.profile_sidebar_border_color
$this.IsTranslator = $InputObject.is_translator
$this.DefaultProfileImage = $InputObject.default_profile_image
}
}
#Requires -Version 5.1
using namespace System.Management.Automation
using namespace System.Text
function ConvertTo-ClassDefinition {
param(
[Parameter(Position = 0, Mandatory, ValueFromPipeline)]
[object]$Object,
[ValidateNotNullOrEmpty()]
[string]$ClassName,
[ValidateNotNullOrEmpty()]
[Alias('Include')]
[string[]]$IncludeProperty = '*',
[ValidateNotNullOrEmpty()]
[Alias('Exclude')]
[string[]]$ExcludeProperty,
[ValidateNotNullOrEmpty()]
[Alias('Hidden')]
[string[]]$HiddenProperty,
[ValidateNotNullOrEmpty()]
[string[]]$Method,
[switch]$NoComments,
[switch]$PassThru
)
# get the immediate object type
$ObjectTypeBase = $Object.psobject.TypeNames[0]
# part 2: correct PSCustomObject input object
if ($ObjectTypeBase -match 'PSCustomObject') {
$ObjectType = 'PSCustomObject'
} else {
$ObjectType = $ObjectTypeBase
}
# set the name of the class
# part 2: PascalCase the class name
if ($ClassName) {
$BaseClassName = $ClassName | ConvertTo-PascalCase
} else {
$BaseClassName = "My." + $ObjectType | ConvertTo-PascalCase
}
# huge bugfix: original property names or PascalCase property names should be allowed for included and excluded properties.
$PascalCaseProperties = @{}
$Object.psobject.properties | ForEach-Object {
$PascalCaseProperties.Add((ConvertTo-PascalCase -String $_.Name),$_)
}
if ($IncludeProperty -ne '*') {
$IncludeProperties = $PascalCaseProperties.Keys | Where-Object {
($_ -in $IncludeProperty -or $PascalCaseProperties[$_].Name -in $IncludeProperty)
}
} else {
$IncludeProperties = $PascalCaseProperties.Keys
}
if ($ExcludeProperty) {
$ExcludeProperties = $PascalCaseProperties.Keys | Where-Object {
($_ -in $ExcludeProperty -or $PascalCaseProperties[$_].Name -in $ExcludeProperty)
}
}
if ($ExcludeProperties) {
$Properties = Compare-Object -ReferenceObject $IncludeProperties -DifferenceObject $ExcludeProperties -PassThru | Where-Object { $_.SideIndicator -eq '<=' }
} else {
$Properties = $IncludeProperties
}
# create stringbuilder
$Indent = ' ' * 4
$ClassDefinition = [StringBuilder]::new()
if (-Not $PSBoundParameters.ContainsKey('NoComments')) {
[void]$ClassDefinition.AppendLine(('# class definition created by {0} at {1} for object type {2}' -f $MyInvocation.MyCommand,(Get-Date),$ObjectType))
[void]$ClassDefinition.AppendLine('')
}
[void]$ClassDefinition.AppendLine(('class {0} {{' -f $BaseClassName))
[void]$ClassDefinition.AppendLine('')
# part 1: add all properties
# part 2: create a new stringbuilder for the properties in the constructor so we only have to loop through properties once
$ConstructorProperty = [StringBuilder]::new()
[void]$ClassDefinition.AppendLine(('{0}# properties' -f $Indent))
foreach ($PascalCaseProperty in $Properties) {
# part 2: ensure best guess for string values for PSCustomObject objects
$PropertySetter = '$InputObject.{0}' -f $PascalCaseProperties[$PascalCaseProperty].Name
if ($PascalCaseProperties[$PascalCaseProperty].TypeNameOfValue -match 'string' -and $ObjectType -eq 'PSCustomObject' ) {
$PropertyTypeConversion = Resolve-PropertyType -PropertyValue $PascalCaseProperties[$PascalCaseProperty].Value
$PropertyTypeName = $PropertyTypeConversion | Select-Object -First 1
if ('Parse' -in $PropertyTypeConversion) {
$PropertySetter = '[datetime]::Parse($InputObject.{0},(Get-Culture))' -f $PascalCaseProperties[$PascalCaseProperty].Name
} elseif ('ParseExact' -in $PropertyTypeConversion) {
$PropertySetter = '[datetime]::ParseExact($InputObject.{0},''{1}'',(Get-Culture))' -f $PascalCaseProperties[$PascalCaseProperty].Name,$PropertyTypeConversion[2]
}
} else {
if ($PascalCaseProperties[$PascalCaseProperty].TypeNameOfValue -match 'PSCustomObject') {
#$PropertyTypeName = 'PSCustomObject'
$PropertyTypeName = "$BaseClassName$PascalCaseProperty"
# part 3: make it recursive
$ConvertParams = @{
ClassName = "$BaseClassName$PascalCaseProperty"
}
if ($PSBoundParameters.ContainsKey('NoComments')) {
$ConvertParams.Add('NoComments',$true)
}
$PascalCaseProperties[$PascalCaseProperty].Value | ConvertTo-ClassDefinition @ConvertParams
} else {
$PropertyTypeName = $PascalCaseProperties[$PascalCaseProperty].TypeNameOfValue
}
}
# part 2: if a property needs to be hidden, add that to the property statement
if ($PascalCaseProperties[$PascalCaseProperty].Name -in $HiddenProperty -or $PascalCaseProperty -in $HiddenProperty) {
$PropertyStatement = '{0}hidden [{1}]${2}' -f $Indent,$PropertyTypeName,$PascalCaseProperty
} else {
$PropertyStatement = '{0}[{1}]${2}' -f $Indent,$PropertyTypeName,$PascalCaseProperty
}
[void]$ClassDefinition.AppendLine($PropertyStatement)
[void]$ConstructorProperty.AppendLine(('{0}{0}$this.{1} = {2}' -f $Indent,$PascalCaseProperty,$PropertySetter))
}
[void]$ClassDefinition.AppendLine('')
# add simple constructors
[void]$ClassDefinition.AppendLine(('{0}# constructors' -f $Indent))
[void]$ClassDefinition.AppendLine(('{0}{1} () {{ }}' -f $Indent,$BaseClassName))
[void]$ClassDefinition.AppendLine(('{0}{1} ([{2}]$InputObject) {{' -f $Indent,$BaseClassName,$ObjectType))
# part 2: ensure proper PascalCase in the constructor
[void]$ClassDefinition.AppendLine($ConstructorProperty.ToString().TrimEnd())
[void]$ClassDefinition.AppendLine(('{0}}}' -f $Indent))
# part 3: add methods, ensure proper PascalCase
[void]$ClassDefinition.AppendLine('')
foreach ($ThisMethod in $Method) {
$PascalCaseMethod = ConvertTo-PascalCase -String $ThisMethod
[void]$ClassDefinition.AppendLine(('{0}[OutputTypeName] {1} (' -f $Indent,$PascalCaseMethod))
[void]$ClassDefinition.AppendLine(('{0}{0}[TypeName]$Param1,' -f $Indent))
[void]$ClassDefinition.AppendLine(('{0}{0}[TypeName]$Param2' -f $Indent))
[void]$ClassDefinition.AppendLine(('{0}) {{' -f $Indent))
[void]$ClassDefinition.AppendLine(('{0}{0}# your code' -f $Indent))
[void]$ClassDefinition.AppendLine(('{0}{0}return $Output' -f $Indent))
[void]$ClassDefinition.AppendLine(('{0}}}' -f $Indent))
[void]$ClassDefinition.AppendLine('')
}
# end class
[void]$ClassDefinition.AppendLine('}')
[void]$ClassDefinition.AppendLine('')
# output class definition
$ClassDefinition.ToString()
}
function Resolve-PropertyType {
[CmdletBinding()]
param(
[Parameter(Mandatory,ValueFromPipeline)]
[string]$PropertyValue
)
$TwitterDateFormat ='ddd MMM dd HH:mm:ss zzz yyyy'
# check for url
try {
[void][Uri]::new($PropertyValue)
return 'Uri'
}
catch { } # do nothing, continue to next text
# check for known date time
try {
[void][datetime]::Parse($PropertyValue,(Get-Culture))
return 'DateTime','Parse'
}
catch { }
# check for Twitter date time
try {
[void][datetime]::ParseExact($PropertyValue,$TwitterDateFormat,(Get-Culture))
return 'DateTime','ParseExact',$TwitterDateFormat
}
catch { }
return 'String'
}
function ConvertTo-PascalCase {
[CmdletBinding()]
param(
[Parameter(Mandatory,ValueFromPipeline)]
[string[]]$String
)
begin {
$Culture = Get-Culture
}
process {
foreach ($Word in $String) {
$Words = $Word -split '\W|_'
$PascalCase = if ($Words.Count -gt 1) {
$Words | ForEach-Object {
$Culture.TextInfo.ToTitleCase($_.ToLower())
}
} else {
if ($Word -cnotmatch '^[A-Z]') {
return $Culture.TextInfo.ToTitleCase($Word)
} else {
return $Word
}
}
}
$PascalCase -join ''
}
}
@thedavecarroll
Copy link
Author

thedavecarroll commented Jan 30, 2021

A PowerShell Conversion Challenge

For this challenge, I wrote a seriers of extensive walk-through articles.

IronScripter: A PowerShell Conversion Challenge

Part 1

In the part 1 article, I cover the basics for PowerShell objects and classes, Get-Member command, the Requires statement, ValueFromPipeline, and the StringBuilder class. My solution (part 1) solved 7 primary requirements and 1 personal requirement.

Standard

  • Copy selected properties
  • Insert placeholder for methods
  • Work from pipeline
  • Allow user to specify a new class name
  • Support Windows PowerShell 5.1 and PowerShell 7.x

Bonus

  • Allow the user to include or exclude properties
  • Include a placeholder for a constructor
  • Let the user specify a method
  • Be VSCode aware an insert the new class automatically into the current file
  • Support cross-platform

Personal

  • Contained in a small module, as there will be a few private functions.
  • Use an existing PSCustomObject with non-conventional property names.
    • A private function would be used to enforce proper PascalCase casing and removal of punctuation (with maybe exception of period).
  • Should be recursive.
    • Any object that contains a property which is itself another complex object should generate a separate class definition.
  • Generate two constructors:
    • One empty constructor to create a clean instance of the class.
    • One constructor that will use the input object to populate a new instance.
  • Allow user to specify hidden properties.
  • If not specified, detect the property type and include in definition.

Part 2

In part 2, I solved 3 personal requirements, which included converting property and class names to PascalCase, detecting property types, and hiding properties.

Primary

  • Insert placeholder for methods
  • Let the user specify a method
  • Be VSCode aware an insert the new class automatically into the current file

Personal

  • Contained in a small module, as there will be a few private functions.
  • Use an existing PSCustomObject with non-conventional property names.
    • A private function would be used to enforce proper PascalCase casing and removal of punctuation (with maybe exception of period).
  • Should be recursive.
    • Any object that contains a property which is itself another complex object should generate a separate class definition.
  • Allow user to specify hidden properties.
  • If not specified, detect the property type and include in definition.

Part 3

In part 3, I solved 2 primary requirements and the remaining of my personal requirements, which included adding class methods and recursive capability to the ConvertTo-ClassDefinition function.

Primary

  • Insert placeholder for methods
  • Let the user specify a method
  • Be VSCode aware an insert the new class automatically into the current file

Personal

  • Contained in a small module, as there will be a few private functions.
  • Should be recursive.
    • Any object that contains a property which is itself another complex object should generate a separate class definition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment