Powershell - convert local time to different timezone - powershell

I am trying to convert my system time (EST) to a different timezone (GMT Standard Time) using PowerShell. I have to run this PowerShell command through my RPA automation software so I am looking to use a single command (instead of PS script) to accomplish this task if possible. Here's the command I am trying to use:
$test = Get-Date
[System.TimeZoneInfo}::ConvertTime($test, 'GMT Standard Time')
First line is just to show the logic I am thinking but I will pass it as a variable and so that I truly have to execute only one line of code. However, I get the following error:
PS C:\Users\samsi> [System.TimeZoneInfo]::ConvertTime($test, 'GMT Standard Time')
Cannot find an overload for "ConvertTime" and the argument count: "2".
At line:1 char:1
+ [System.TimeZoneInfo]::ConvertTime($test, 'GMT Standard Time')
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
Clearly, I don't know much about PowerShell, can someone please help me with this command.

Try this and see if it gets you what you're after.
[System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($(Get-Date), [System.TimeZoneInfo]::Local.Id, 'GMT Standard Time')
or if you prefer storing the current date in a variable first (the way you have it in your code)
$test = Get-Date
[System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($test, [System.TimeZoneInfo]::Local.Id, 'GMT Standard Time')

Let's look at how to diagnose this.
The error message:
Cannot find an overload for "ConvertTime" and the argument count: "2".
is a common one on PowerShell when calling methods on objects. Sometimes it's misleading. It always means that given the arguments you supplied, none of the method's overloads matched.
Aside: each method has one or more ways you can call it, with parameters of different types, or different number or order of parameters.
In PowerShell, you can see all the overloads by "invoking" the method without parentheses or arguments. So:
[System.TimeZoneInfo]::ConvertTime
Result:
OverloadDefinitions
-------------------
static System.DateTimeOffset ConvertTime(System.DateTimeOffset dateTimeOffset, System.TimeZoneInfo destinationTimeZone)
static datetime ConvertTime(datetime dateTime, System.TimeZoneInfo destinationTimeZone)
static datetime ConvertTime(datetime dateTime, System.TimeZoneInfo sourceTimeZone, System.TimeZoneInfo
destinationTimeZone)
The error message in PowerShell always references the number of arguments, but it sometimes also means that you didn't use the correct type.
So in your example, you did supply 2 arguments, which makes the error confusing.
But the second argument you supplied is a [string]. Looking at the available overloads, you can see that none of the second arguments take a string, they are all looking for an object of type [System.TimeZoneInfo].
Sometimes, you can use a different type, if there's an implicit cast available. For example if a method takes a parameter of type [System.Net.IPAddress] then you can give a string like '127.0.0.1'. That's because [System.Net.IPAddress] knows how to convert an IP string into the object. You can see this by doing something like '127.0.0.1' -as [System.Net.IPAddress] or [System.Net.IPAddress]'127.0.0.1'.
Going back to your use case: it seems you either cannot cast a string to the TimeZoneInfo type, or your string is not valid for that purpose (that is, the cast failed).
So you should first figure out how to create or retrieve a TimeZoneInfo object that represents what you want.
It looks to me like [System.TimeZoneInfo]::GetSystemTimeZones() returns an array of all the time zones on your system.
Filtering that list to find the one you want seems like a good idea. Looking at the list, I can see the string you want to use is in the StandardName property so I'll use this to get the right one:
$gmt = [System.TimeZoneInfo]::GetSystemTimeZones().Where({$_.StandardName -eq 'GMT Standard Time'})[0] # it's an array, so get the first one
Then you can call your original method with that object:
[System.TimeZoneInfo]::ConvertTime($test, $gmt)

Related

Is there a PowerShell equivalent for C#'s default operator, and if so what is the syntax?

In C#, it's possible to get the default value of any type using the default operator:
var i = default(int); // i == 0
// in C# 7.1+
int j = default; // j == 0
Is there a similar construct in PowerShell, and if so what is it? As far as I've been able to determine in my Googling and testing, default is only recognized by PS when present in switch blocks.
PowerShell has no direct language construct for it because it doesn't need it -- due to its loose typing you are almost never required to produce a value of a specific type and there is no support for creating generic types or functions. Untyped variables start off as $null if you do nothing special. Typed variables start off as whatever value you explicitly give them, and that's generally sufficient due to PowerShell's liberal rules for conversion ([int] "" and [int] $null are both 0).
Only in rare cases does this fail, like attempting to declare a variable of type DateTimeOffset, as there is no default constructor and $null or "" won't convert. Arguably, the fix there is to just explicitly construct a value using whatever the type does offer ([DateTimeOffset] $d = [DateTimeOffset]::Now, [DateTimeOffset] $d = [DateTimeOffset]::MinValue, [DateTimeOffset] $d = "0001-01-01 00:00Z"). Only in the very rare case that you have a dynamic type, and you'd like to get what C# would give you with default, would you need some special code. You can do it in pure PowerShell (well, almost, we need to call a method available since .NET 1.0):
Function Get-Default([Type] $t) { [Array]::CreateInstance($t, 1)[0] }
And then [DateTimeOffset] $d = Get-Default DateTimeOffset works (there is no way to infer the type in this case, though you are of course free to omit it from the variable).
Of course this does create a garbage array on every invocation; it does not invoke any constructors of the type itself, however. There are more involved approaches that avoid array creation, but they all involve getting complicated with generic methods (relying on LINQ) or explicitly compiling C# and aren't really worth demonstrating as they're less general. Obviously, even the function above should be used only in the unusual case where it might be needed and not as a general way of initializing variables -- typically you know the type and how to initialize it, or you don't care about the type in the first place.

Cannot convert value of type "System.String" to type "System.Type"

I am trying to build a tfs workstation using PowerShell, but I've become stuck.
In my code are the lines
$teamProjectCollection = [Microsoft.TeamFoundationClient.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tfsServer)
$ws = $teamProjectCollection.GetService([type] "Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore")
I got those lines from this answer on another question I recently asked. The answer solved the problem I had then, but unfortunately that second line gets an exception when trying to convert the string to Type. Specifically, the error I get is:
Cannot convert the "Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore" value of type "System.String" to type "System.Type".
I know that the GetService function I'm using expects a System.Type parameter. I also haven't found a means to convert a System.String object into a System.Type object strictly through PowerShell.
So, how do I either fix this or get around this problem?
You must add an assembly to gain access to the [Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore] type. Assuming you are in the directory that contains the assembly file, you can run the following in your current PowerShell session.
Add-Type -Path ".\Microsoft.TeamFoundation.WorkItemTracking.Client.dll"

Difference between "New-Object WindowsPrincipal([WindowsIdentity]::GetCurrent())" and "[WindowsPrincipal] [WindowsIdentity]::GetCurrent()"

I'm trying to check if Powerhell script is running as Administrator.
After searching the web, I got some sample code that works fine.
In order to get the WindowsPrincipal object, I found two sample code as below.
First:
New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
Second:
[Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
The second one made me confused.
According to this page, I know that the [ ] is a cast operator.
And according to this page, PowerShell is built on the .NET Framework. In my opinion, this means that the above two PowerShell scripts can be converted to C#.
So I try it.
When I convert the first PowerShell script to C#. It work fine as below.
## First PowerShell script
New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
// C#
var wp = new WindowsPrincipal(WindowsIdentity.GetCurrent());
But when I try to convert the second PowerShell script to C#. I get compile error. The IDE tells me that WindowsIdentity cannot be cast to WindowsPrincipal.
## Second PowerShell script
[Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
// both C# code below cause compile error
var wp = System.Security.Principal.WindowsIdentity.GetCurrent() as System.Security.Principal.WindowsPrincipal;
var wp2 = (System.Security.Principal.WindowsPrincipal)System.Security.Principal.WindowsIdentity.GetCurrent();
Just as I tried on C#, the System.Security.Principal.WindowsIdentity type cannot be directly converted to the System.Security.Principal.WindowsPrincipal type. But why is the second PowerShell script available?
Or is the [ ] operator in the second PowerShell script not a type conversion operator?
Maybe this operator do more than just convert object type?
What's the difference between first PowerShell script and second PowerShell script?
Did I missing any other things?
TLDR: PowerShell can do Magic. C# can't do Magic.
PowerShell can juggle Chainsaws, bowling pins, and balls at the same time.
C# can only juggle balls if they are defined ahead of time. Trying to add a new Chainsaw into the juggling routine causes the juggler(compiler) to complain.
The issues are the differences between Functions, Object types, and how to cast object types.
System.Security.Principal is the base .NET library. The library can be used by C# and PowerShell.
WindowsIdentity.GetCurrent() is a function in the library.
WindowsPrincipal is an object type e.g. like string or int.
Calling WindowsIdentity.GetCurrent() returns a WindowsIdentity object, which you can then use in your code.
Since WindowsIdentity is not necessarily the object type you want to work with, we want to use a WindowsPrincipal object. Unfortunately we cannot directly cast from WindowsIdentity to WindowsPrincipal object. We have to use the WindowsPrincipal constructor.
In PowerShell, you create a new object either by the New-Object cmdlet, or by simply using a variable for the first time. This convenience is because PowerShell is a scripting language, where using a variable e.g. $a = 1 implicitly creates a new variable. e.g.
PS C:\> Get-Variable test
Get-Variable : Cannot find a variable with the name 'test'.
At line:1 char:1
+ Get-Variable test
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (test:String) [Get-Variable], ItemNotFoundException
+ FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand
PS C:\> $test = 1
PS C:\> Get-Variable test
Name Value
---- -----
test 1
Using the New-Object example:
New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
This is the right way to do it. You are creating a new object of type WindowsPrincipal, and passing the Windows Identity to the constructor.
The second method:
[Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
Is "wrong" because it uses casting, and earlier we stated that we cannot directly cast from WindowsIdentity to WindowsPrincipal object. So how does this work? Well, I said earlier that PowerShell does Magic, well I'll get to that in a minute. First let's see the correct C# Code:
Calling .NET Framework specific functions in C# do differ in Syntax than PowerShell. And the reason for the specific compile error you are getting is because we are trying to cast the object, which we can't.
The example:
using System.Security.Principal;
var wp = WindowsIdentity.GetCurrent() as WindowsPrincipal; // compile error
The Compiler translates it to:
WindowsIdentity.GetCurrent()
Run function GetCurrent()
as WindowsPrincipal;
Expect a return type of WindowsPrincipal <-- compile time error. The compile error is because the function does not return a type of WindowsPrincipal, instead it returns a type WindowsIdentity.
Second example is also a variation that doesn't work because it can't cast the object directly. e.g.
using System.Security.Principal;
var wp = (WindowsPrincipal) WindowsIdentity.GetCurrent(); // compile error
The Compiler translates it to:
WindowsIdentity.GetCurrent()
Run function GetCurrent() and return with the WindowsIdentity object.
(WindowsPrincipal) WindowsIdentity.GetCurrent();
Take the WindowsIdentity object and directly cast it to a WindowsPrincipal type, which it can't.
The correct C# code also needs the new keyword is:
var wp = new WindowsPrincipal(WindowsIdentity.GetCurrent());
The Compiler translates it to:
WindowsIdentity.GetCurrent()
Run function GetCurrent() and return with the WindowsIdentity object.
new WindowsPrincipal(WindowsIdentity.GetCurrent())
Create a new WindowsPrincipal object passing the WindowsIdentity object into the constructor. Which now works.
So why does the second example:
[Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
Work in PowerShell by doing something that is "wrong"? Because PowerShell is a scripting language, and is interpreted on the fly, and can use Magic, it can interpret the above and perform some Type Conversion Magic Quote:
Direct assignment. If your input is directly assignable, simply cast your input to that type.
Language-based conversion. These language-based conversions are done when the target type is void, Boolean, String, Array, Hashtable, PSReference (i.e.: [ref]), XmlDocument (i.e.: [xml]). Delegate (to support ScriptBlock to Delegate conversions), and Enum.
Parse conversion. If the target type defines a Parse() method that takes that input, use that.
Static Create conversion. If the target type defines a static ::Create() method that takes that input, use that.
Constructor conversion. If the target type defines a constructor that takes your input, use that.
Cast conversion. If the target type defines a implicit or explicit cast operator from the source type, use that. If the source type defines an implicit or explicit cast operator to the target type, use that.
IConvertible conversion. If the source type defines an IConvertible implementation that knows how to convert to the target type, use that.
IDictionary conversion. If the source type is an IDictionary (i.e.: Hashtable), try to create an instance of the destination type using its default constructor, and then use the names and values in the IDictionary to set properties on the source object.
PSObject property conversion. If the source type is a PSObject, try to create an instance of the destination type using its default constructor, and then use the property names and values in the PSObject to set properties on the source object. . If a name maps to a method instead of a property, invoke that method with the value as its argument.
TypeConverter conversion. If there is a registered TypeConverter or PSTypeConverter that can handle the conversion, do that. You can register a TypeConverter through a types.ps1xml file (see: $pshome\Types.ps1xml), or through Update-TypeData.
Basically, whenever you do a type conversion in PowerShell, it will perform Magic and try each method to convert the type dynamically on the fly for you. That is why it can handle you throwing a new Chainsaw or bowling pin at the juggler. PowerShell can interpret that we aren't trying to convert a chainsaw into a ball, and instead that a chainsaw is just another object, and we already know how to juggle objects.
This interpretation isn't a part of .NET, and so C# can't do that, and we have to explicitly define everything, and do everything correctly. This means that you can convert all C# and .NET code into valid PowerShell code, but not the other way around.

What does [parametertype[]]$Parameter mean?

I have seen PowerShell function parameters defined using the syntax param([parametertype[]]$Parameter) as well as param([parametertype]$Parameter).
Personally, I have always used the latter with (as far as I can tell) no problems. Can anyone explain what (if any) is the difference and why both are allowed and work?
[parametertype[]]$Parameter is a parametertype array of $Parameter
[parametertype]$Parameter is a $Parameter of parametertype
i.e.
> [string]$param_s = "parameter"
> [string[]]$param_a = "parameter", "param2", "param3"
> $param_s[1]
a
> $param_a[1]
param2
Note how param_s is a plain string and accesses the second position in the string when accessing index [1] compared to what's returned by param_a[1]
When used by the param keyword in a function / cmdlet definition, it just ensures the function will be passed correct data type
in powershell you don't have to define what parameter you are using but you can.
sometimes it can be handy if you want to define a parameter as [mandatory] for example.
in your example you defind an array type param[] and single type.
you can read more about Defining Parameters.

PowerShell can't use the matching enum type?

Am I doing something stupid here?
I specify that a function takes a particular enum type as an argument:
PS> add-type -AssemblyName System.ServiceProcess
PS> function test([System.ServiceProcess.ServiceControllerStatus]$x) { Write-host $x $x.gettype() }
The type is most definitely in scope since I can access instances of it (and I imported the assembly manually):
PS> [System.ServiceProcess.ServiceControllerStatus]::Stopped
Stopped
Then when I try to pass the function an instance of said enum, it errors out:
PS> test [System.ServiceProcess.ServiceControllerStatus]::Stopped
test : Cannot process argument transformation on parameter 'x'. Cannot convert value
"[System.ServiceProcess.ServiceControllerStatus]::Stopped" to type "System.ServiceProcess.ServiceControllerStatus".
Error: "Unable to match the identifier name [System.ServiceProcess.ServiceControllerStatus]::Stopped to a valid
enumerator name. Specify one of the following enumerator names and try again: Stopped, StartPending, StopPending,
Running, ContinuePending, PausePending, Paused"
At line:1 char:6
+ test [System.ServiceProcess.ServiceControllerStatus]::Stopped
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [test], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,test
It's quite happy coercing a string, though:
PS> test 'Stopped'
Stopped System.ServiceProcess.ServiceControllerStatus
What's going on?
You are running into a small gotcha regarding parsing modes. You can put parens around the argument and it will work:
test ([System.ServiceProcess.ServiceControllerStatus]::Stopped)
Alternatively, conversions from string to enum happen naturally, so you could write:
test Stopped
Here are a couple good links that discuss parsing modes:
About Parsing
Effective PowerShell Item 10: Understanding PowerShell Parsing Modes
You can pass an enum value as a string but you don't pass the typename as part of the argument e.g. this works just fine:
PS> test Stopped
Stopped System.ServiceProcess.ServiceControllerStatus
That said, when I'm calling .NET methods I prefer to use the fully qualified enum value instead of a string. That's because .NET methods tend to have multiple overloads and those that take strings can confuse PowerShell when to comes to picking the right overload.
Apparently, PowerShell thinks I am sending it a string, rather than an enum object. You get the same error message if you quote the fully qualified name:
PS> test '[System.ServiceProcess.ServiceControllerStatus]::Stopped'
test : Cannot process argument transformation on parameter 'x'. Cannot convert value
"[System.ServiceProcess.ServiceControllerStatus]::Stopped" to type "System.ServiceProcess.ServiceControllerStatus".
Error: "Unable to match the identifier name [System.ServiceProcess.ServiceControllerStatus]::Stopped to a valid
enumerator name. Specify one of the following enumerator names and try again: Stopped, StartPending, StopPending,
Running, ContinuePending, PausePending, Paused"
At line:1 char:6
+ test '[System.ServiceProcess.ServiceControllerStatus]::Stopped'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [test], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,test
Putting the enum in parentheses to force PowerShell to evaluate it first does the trick:
PS> test ([System.ServiceProcess.ServiceControllerStatus]::Stopped)
Stopped System.ServiceProcess.ServiceControllerStatus
You're not specifying the $x parameter correctly. It needs to be wrapped in a param() expression.
function test
{
param(
[ServiceProcess.ServiceControllerStatus]
$x
)
Write-host $x $x.gettype()
}
Plus you can omit the System. portion of any type name.