lørdag den 26. marts 2011

PowerShell PSCmdLets and Pipeline objects

I’m working on a PowerShell interface to the project I’m working on. Most of the commands return Class’s and I'm being very thorough about making sure passing parameters work both normally, unnamed and piped. But one function kept giving me problems.

PS C:\> $customerservice = Get-CustomerService skadefro.dk
PS C:\
> $customerservice | Get-CustomerServiceObject
Get
-CustomerServiceObject : Need to suply either CustomerServiceID or CustomerServiceObjectID
At line:
1 char:45
+ $customerservice | Get-CustomerServiceObject <<<<
+ CategoryInfo : NotSpecified: (:) [Get-CustomerServiceObject], E xception
+ FullyQualifiedErrorId : System.Exception,Cloud.Provisioning.CloudSnapin.
Cloud.Provisioning.GetCustomerServiceObject

PS C:\
> Get-CustomerServiceObject $customerservice

ExtensionData : System.Runtime.Serialization.ExtensionDataObject
CustomerServiceID :
1897
CustomerServiceObjectID :
2260
Label : 36cb002b
-355e-4dd3-9dca-f217922a4a63
Properties : {
2260, 2260, 2260, 2260...}
ServiceObjectID :
15
default : False
private :
True

ExtensionData : System.Runtime.Serialization.ExtensionDataObject
CustomerServiceID :
1897
CustomerServiceObjectID :
2261
Label : c38d1601
-df38-40d8-8ef8-6f8ac96eb09e
Properties : {
2261, 2261, 2261, 2261...}
ServiceObjectID :
15
default : False
private :
True

PS C:\
>

I can pass it as a normal parameter, but when pipelining the object(s) it wouldn’t get the object. I used the same parameter type in other CMDlets with no problems. I even tried deleting the project and then class by class copy’n’pasting over the code in case something behind the scene was messing with me, but to no wail. Then as I for the 1000’th time was giving it a crack, I fell a cross a news post (sorry I cant find the link again ) that instantly made it clear to me what was wrong.


 


PS C:\> trace-command parameterbinding -pshost { $customerservice | Get-Customer
ServiceObject }
This is one cool command. You can do all kinds of neat tricks with that, but most importantly right now, it showed me how PowerShell tries to pass the piped objects, and turns out my CMD let was throwing an exception every time PowerShell tried giving it the parameter
Protected Overrides Sub BeginProcessing()
If _CustomerServiceObjectID.Count = 0 And _CustomerServiceID.Count = 0 Then
Throw New Exception("Need to suply either CustomerServiceID or CustomerServiceObjectID")
End If
End Sub

Once I removed my check from BeginProcessing and moved it to ProcessRecord everything worked as intended.

tirsdag den 22. marts 2011

Calling WCF Web Service from DLL

I need to call a webservice from a DLL ( powershell PSCmdlet ) but when testing it I keept getting below error

PS C:\Users\administrator.INT> Get-CustomerServiceObject -CustomerServiceID 1880
Get-CustomerServiceObject : Could not find default endpoint element that references contract 'webserviceCustomerService
s.CustomerServices' in the ServiceModel client configuration section. This might be because no configuration file was f
ound for your application, or because no endpoint element matching this contract could be found in the client element.
At line:1 char:26
+ Get-CustomerServiceObject <<<<  -CustomerServiceID 1880
    + CategoryInfo          : NotSpecified: (:) [Get-CustomerServiceObject], InvalidOperationException
    + FullyQualifiedErrorId : System.InvalidOperationException,Cloud.Provisioning.CloudSnapin.Cloud.Provisioning.GetCu
   stomerServiceObject

PS C:\Users\administrator.INT>

One way to fix this would be to add the system.serviceModel from app.config to powershell’s .config file,but that just sound wrong in my ears. Google !

So found this post and re-wrote it to my own VB.NET version

'Dim wsCustomerServices As New webserviceCustomerServices.CustomerServicesClient
Dim wsCustomerServices As webserviceCustomerServices.CustomerServices
Try
    Dim asm As System.Reflection.Assembly = System.Reflection.Assembly.GetExecutingAssembly
    Dim assemName As System.Reflection.AssemblyName = asm.GetName()
    Dim dllPath As String = asm.CodeBase.Replace("file:///", "")
    dllPath = IO.Path.GetDirectoryName(dllPath)

    Dim stockConfiguration As Configuration = ConfigurationManager.OpenMappedExeConfiguration(New ExeConfigurationFileMap() With {.ExeConfigFilename = (dllPath & "\Cloud.Provisioning.CloudSnapin.dll.config")}, ConfigurationUserLevel.None)
    Dim stockChannelFactory As ConfigurationChannelFactory(Of webserviceCustomerServices.CustomerServices)
    stockChannelFactory = New ConfigurationChannelFactory(Of webserviceCustomerServices.CustomerServices)("CustomBinding_CustomerServices", stockConfiguration, Nothing)
    wsCustomerServices = stockChannelFactory.CreateChannel()

Catch ex As Exception
    Throw ex
End Try

mandag den 21. marts 2011

AXFR Zone Transfers from PowerShell

So I’m playing around with a script to transfer a zone from another DNS server to my own DNS servers. I’m using DnsShell for most of my operation’s ( it has a few bugs, but nothing that cant be handled by adding abit of WMI  ) but I couldn’t get AXFR requests to work. google sendt me to PowerShell Dig PoshNet but that didn’t work either, and then it hit me (I can be so slow sometimes), I can’t AXFR from my default DNS server I need to ask hosting DNS server, and no need to bother with different PSSnapins. DnsShell does the work just fine. Here is an example.

$zonename = ‘somedomain.com’
$nameservers = (Get-Dns $zonename ns) | Select-Object -ExpandProperty Answer
$dnsserver = (get-dns $nameservers[0].RecordData).Answer[0].RecordData
Write-Host (‘Asking ‘ + $nameservers[0].RecordData + ' ' + $dnsserver)
$zone = Get-Dns -Name $zonename -RecordType axfr -Server $dnsserver | Select-Object -ExpandProperty Answer
$zone

WinRM Remote Management and PowerShell certificates

So I was playing around with DNS. I am configurering that remote though PowerShell. After I had most things working tested against NS1 I wanted to start working on the secondary Zone’s on NS2 and NS3 and started getting

$PSSession = New-PSSession -auth CredSSP -cred $cred -ComputerName ns2 -UseSSL;

[ns2] Connecting to remote server failed with the following error message : The WinRM client received an HTTP server error status (500), but the remote service did not include any other information about the cause of the failure. For more information, see the about_Remote_Troubleshooting Help topic.
    + CategoryInfo          : OpenError: (System.Manageme....RemoteRunspace:RemoteRunspace) [], PSRemotingTransportException
    + FullyQualifiedErrorId : PSSessionOpenFailed

That’s weird, I know it was configured. Ive been playing around with certificates while configurering SCOM 2007 so I knew all the certificates might not be there, so I decided to reconfigure it all. Looking in the event log on the remote machine NS2 I also found

The WinRM service failed to initialize CredSSP.

Additional Data
The error received was 0x80338082.

User Action
Configure CertificateThumbprint setting under the WinRM configuration for the service. Use the thumbprint of a valid certificate and make sure that Network Service has access to the private key of the certificate.

Searching google for that error kept mentioning making sure to use the right certificate. Sure enough, the listener was using a certificate that wasn’t there. ( run “winrm enumerate winrm/config/listener” and check the CertificateThumbprint against the certificates in the local machine MY store )

so I decided to just run my initial powershell script

winrm delete winrm/config/listener?Address=*+Transport=HTTP
winrm delete winrm/config/listener?Address=*+Transport=HTTPS
winrm quickconfig -transport:http
winrm quickconfig -transport:https
Enable-WSManCredSSP -Role server

But no use, I was still getting the above error. Then I looked at the permissions of the certificates and sure enough “network services” didn’t have permissions. ( MMC –> CTRL+M –> Certificates –> Computer account ) ( Personal –> Certificates –> Right click certificate –> All tasks –> Manage Private Keys ..”  ) By the way several sites recommend using “Find Private Key tool” from WCF SDK. No need to do that, powershell is your friend here.

I was still getting the error. then it hit me … The Service also have a CertificateThumbprint ( run “winrm get winrm/config” )

So I wrapped it all up in a little script.

# Get local computer name and FQDN
$computername = get-content  env:computername
$objIPProperties = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties()
$fqdn = "{0}.{1}" -f $objIPProperties.HostName, $objIPProperties.DomainName
$fqdn = $fqdn.ToLower()

# works like a charm if only one certificate is in the the store.
winrm quickconfig -quiet -transport:http
winrm quickconfig -quiet -transport:https

# allow basic authentication
winrm p winrm/config/service '@{AllowUnencrypted="true"}'
winrm p winrm/config/service/auth '@{Basic="true"}'

# Get all local certificates with computer name/fqdn and grant network service permissions on it
$certs = dir cert:\LocalMachine\my
ForEach($cert in $certs){
    if( ($cert.Subject -eq ('CN=' + $computername)) -or ($cert.Subject -eq ('CN=' + $fqdn))){
        Write-Host ("fix permissions on " + $cert.Thumbprint + " " + $cert.Subject)
        $location = $cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
        $folderlocation = gc env:ALLUSERSPROFILE
        $folderlocation = $folderlocation + "\Microsoft\Crypto\RSA\MachineKeys\"
        $filelocation = $folderlocation + $location
        #icacls $filelocation /grant "Network service:(OI)(CI)(F)"
        icacls $filelocation /grant "Network service:(F)"
    }
}

# Force listener and service to use last known good certificate
winrm set winrm/config/Listener?Address=*+Transport=HTTPS ('@{CertificateThumbprint="' + $cert.Thumbprint + '"}')
winrm p winrm/config/service ('@{CertificateThumbprint="' + $cert.Thumbprint + '"}')

# if first time run, set the role to server
Enable-WSManCredSSP -Role server

lørdag den 19. marts 2011

The SSL certificate is signed by an unknown certificate authority

So while I was playing with certificates installation, I was also playing around with my PowerShell DLL and I suddenly started getting this error

[PS] C:\>$cred = New-Object System.Management.Automation.PSCredential $Username, $Password
[PS] C:\>$PSSession = New-PSSession -auth CredSSP -cred $cred -ComputerName ns1 -UseSSL
$PSSession = New-PSSession -auth CredSSP -cred $cred -ComputerName ns1 –UseSSL

$PSSession = New-PSSession -auth CredSSP -cred $cred -ComputerName ns1 –UseSSL

[ns1] Connecting to remote server failed with the following error message : The server certificate on the destination computer (ns1:5986) has the following errors:
The SSL certificate could not be checked for revocation. The server used to check for revocation might be unreachable.
The SSL certificate is signed by an unknown certificate authority. For more information, see the about_Remote_Troubleshooting Help topic.
    + CategoryInfo          : OpenError: (System.Manageme....RemoteRunspace:Re   moteRunspace) [], PSRemotingTransportException
    + FullyQualifiedErrorId : PSSessionOpenFailed

at first I couldn’t quite understand why. My machine was part of the domain so I started checking if the machines could see/connect to the CA and such, but then I remembered one of the functions I was testing while playing around with certificates was importing the root CA to trusted root certification authorities, and when I checked, it was truly missing. I came across this Blog post that makes a good explanation that others might find useful in case they are using self signed certificates instead of certificates from a CA like me.

onsdag den 16. marts 2011

RTFM , learned the hard way

So I was fixing a few bug’s in an application that supports auto updating. Everything was perfect until I suddenly noticed that non of the machines with the application was updating anymore. so I log on to one of the machines and see the service isn't running on the machine either (the application is a windows service). Weird, no errors in the evenlog from the service either, but all my services can also be run from command so I give that a go.

Unhandled Exception: System.BadImageFormatException: Could not load file or assembly XXXXX.exe' or one of its dependencies. This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.
File name: 'XXXXX.exe'

WTF ? hmm, looks like my app.config is screwed so I create a new one and try again.

image
To run this application, you first must install one of the following versions of the .Net Framework: v4.0.30319

that’s weird, I was 100% sure my application was set to Target Framework v3.5. I triede every single combination from 2.0, 3.0, 3.5, 3.5 client profile to 4.0. No matter what I did, it only worked when set to 4.0 AND .net 4.0 was installed on the client. But I don’t want 4.0 on all machines.

After ton of time on google, I realised something. When changing target framework the path to the dll’s in the project references keept pointing to C:\Windows\Microsoft.NET\Framework\v4.0.30319 or C:\Windows\Microsoft.NET\Framework64\v4.0.30319 depending on my target CPU type.

Even if I manuly removed all references and added them pointing to C:\Windows\Microsoft.NET\Framework\v2.0.50727 or C:\Windows\Microsoft.NET\Framework64\v2.0.50727 visual studio 2010 would accept it, but chance the path back to the v4.0.30319 directory.

THIS WAS DRIVING ME NUTS! Sad smile

Then .. and gods know how I found it .. came a cross this page

and BAM it hit me, I had the exact same issue. I too had googled the problem with

Add-PSSnapin : Cannot load Windows PowerShell snap-in XXXXX because of the following error: Coul not load file or assembly 'file:///C:\XXXXX.dll' or one of its dependencies. This assembly i built by a runtime newer than the currently loaded runtime and cannot be loaded.
At line:1 char:13
+ Add-PSSnapin <<<<  XXXXX
    + CategoryInfo          : InvalidArgument: (XXXXX:String) [Add-PSSnapin], PSSnapInException
    + FullyQualifiedErrorId : AddPSSnapInRead,Microsoft.PowerShell.Commands.AddPSSnapinCommand


and the all around solution you find to this problem is adding 1 or both of these 2 registry keys

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework:OnlyUseLatestCLR
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\ Microsoft\.NET Framework:OnlyUseLatestCLR

Except, everywhere you find it they also warn about doing it, but noone seemed to have a solution. but there is on and its simple. if you just want to fix it for you own local powershell, just go to $pshome and create a file named powershell.exe.config and add this text to it

<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0.30319"/>
<supportedRuntime version="v2.0.50727"/>
</startup>
</configuration>

if you want to chance it for all users and all versions (32 bit and 64bit) create the above file in C:\windows\System32\WindowsPowerShell\v1.0 and C:\Windows\SysWOW64\WindowsPowerShell\v1.0

onsdag den 9. marts 2011

Microsoft implements the world’s most stupid error

So I'm working a bit more on my PowerShell class, trying to do some work on remote machines. I add the option to supply a host, username and password and want’s to connect using WSManConnectionInfo. I end up with a ton of weird errors but can’t really figure out what is making all those errors, so desperate as I am, I end up using New-PSSession and Enter-PSSession. ( I should have just sticked with following the error)

I still get errors but now from the Runspace saying its not supported. Digging around on Google I find out I need to implement IHostSupportsInteractiveSession to my PSHost. Well that wasn’t to hard, I add

Imports System.Management.Automation.Host
Public Class CloudPowerShellHost
    Inherits PSHost
    Implements IHostSupportsInteractiveSession

and smack in

Public ReadOnly Property IsRunspacePushed() As Boolean
    Get
        Return Me.pushedRunspace IsNot Nothing
    End Get
End Property

Private pushedRunspace As System.Management.Automation.Runspaces.Runspace = Nothing
Private myRunSpace As System.Management.Automation.Runspaces.Runspace = Nothing

Public Sub PopRunspace() Implements System.Management.Automation.Host.IHostSupportsInteractiveSession.PopRunspace
    myRunSpace = Me.pushedRunspace
    Me.pushedRunspace = Nothing
End Sub

Public Sub PushRunspace(ByVal runspace__1 As System.Management.Automation.Runspaces.Runspace) Implements System.Management.Automation.Host.IHostSupportsInteractiveSession.PushRunspace
    Me.pushedRunspace = Runspace
    myRunSpace = runspace__1
End Sub

Public ReadOnly Property Runspace As System.Management.Automation.Runspaces.Runspace Implements System.Management.Automation.Host.IHostSupportsInteractiveSession.Runspace
    Get
        Return Me.myRunSpace
    End Get
End Property

Public ReadOnly Property IsRunspacePushed1 As Boolean Implements System.Management.Automation.Host.IHostSupportsInteractiveSession.IsRunspacePushed
    Get
        Return Me.pushedRunspace IsNot Nothing
    End Get
End Property

Public Sub SetRunspace(ByVal runspace As System.Management.Automation.Runspaces.Runspace)
    Me.myRunSpace = runspace
End Sub

At the end of the class and I’m good to go. Still after this I’m still getting tons of errors, so I start walking back the stack seeing what I missed. Turns out I forgot some error handling in a function I use, that saved all variables in a runspace, and it is my enumeration of variables that fails.

System.InvalidCastException: Unable to cast object of type 'System.Management.Automation.PSCustomObject' to type 'System.Management.Automation.PSVariable'.

I’m doing something like this

Dim o As System.Management.Automation.PSVariable
Try
    o = ps.BaseObject
Catch ex As Exception
    Throw ex
End Try

I loop all the PSObjects from doing a get-variable so I inspect the PS object and; WTF???

image

image

The BaseObject is “kind of empty” not really sure what it is, but the main object is a variable, so no sweat, I smack in

Dim o As System.Management.Automation.PSVariable
Try
    o = ps.BaseObject
Catch ex As Exception
    Try
        o = ps
    Catch ex2 As Exception
        Throw ex2
    End Try
End Try

and the result is a new exception

System.InvalidCastException: Unable to cast object of type 'System.Management.Automation.PSVariable' to type 'System.Management.Automation.PSVariable'.

LOL of all the world’s most lame error messages that has GOT to be the one.

Screw it. after playing around with a TON of different good/stupid/desperate/brilliant hacks I remember that I used SessionStateProxy.PSVariable.GetValue() when fetching single variables, so I try this approach instead, and it work’s .. Its ugly, I know, but what the hell am I/we suppose to do in a weird situation like this ?

Dim PSVar As System.Management.Automation.PSVariable
Try
    If host.IsRunspacePushed Then
        newPSVariableName = ps.Members("name").Value
        newPSVariableValue = ps.Members("value").Value
        newPSVariable = New PSVariable(newPSVariableName)

        PSVar = newPSVariable
        newPSVariable.Value = newPSVariableValue
    Else
        newPSVariable = ps.BaseObject
        PSVar = newPSVariable
        newPSVariableName = newPSVariable.Name
        newPSVariableValue = newPSVariable.Value
    End If
    If newPSVariableValue Is Nothing Then
    ElseIf InStr(newPSVariableValue.GetType.FullName, "System.Management.Automation") > 0 Then

        If newPSVariableValue.GetType.FullName = "System.Management.Automation.PSObject" Then
            Dim tmpPSObject As PSObject = newPSVariableValue
            newPSVariableValue = tmpPSObject.BaseObject
        Else
            newPSVariableValue = Nothing
        End If
    End If

    If newPSVariableValue Is Nothing Then
        newPSVariable = New PSVariable(newPSVariableName)
        newPSVariable.Value = newPSVariableValue
        PSVar = newPSVariable
    End If