Tag Archives: Powershell

List NordPool electric prices using powershell.

This is a small script using nordpoolgroup api to get todays electric prices for Kr.sand – Norway. This is the same API as their webpage uses. All the urls and parameters can be found using developermode in the browser. This code is only to demonstrate that it can be done. It all started when i first saw the Homeassistant integration on github .https://github.com/custom-components/nordpool

$today=Get-Date -Format "d-M-yyyy"
$url="https://www.nordpoolgroup.com/api/marketdata/page/23" #23 = Hourly ,24 = Daily, 25 = Weekly, 26 = Monthly, 27 = Yearly

$url+="?currency=,NOK,NOK,EUR&endDate=$today"
$response=Invoke-RestMethod -Uri $url
$prices=@()
foreach ($row in $response.data.rows){ 
    $localrow= $row.Columns | Where-Object {($_.name -like "Kr.sand")} 
 if($localrow.IsValid){
  [float]$cost=((($localrow[0].value).tostring()).Replace(' ','')).replace(',','.')
  [datetime]$datetime=$row.StartTime
    Write-Output "$($datetime) - øre/kWh ink mva $([math]::round(($cost/10 * 1.25),2))"
    $prices+=[math]::round(($cost * 1.25),2)
    }
}

$prices|Measure-Object -Average -Maximum -Minimum

List users and licenses in Database

Now that we have a database containing user, tenant and license it would be nice to have a function/command to list this. Took me a while to figure it out but thanks to Bing, I prefer Bing before google. I sync my database on a schedule once a day. This way my data is new. Based on this data you could create a graph indicating office 365 license usage.

function get-tenantuserlicenses {
 
    [CmdletBinding()]
    param(
    )
    DynamicParam {
        $ParameterName = "tenant"
        $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]

        # Create and set the parameters' attributes
        $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
        $ParameterAttribute.Mandatory = $false
        $ParameterAttribute.Position = 1

        # Add the attributes to the attributes collection
        $AttributeCollection.Add($ParameterAttribute)
        # Generate and set the ValidateSet
        # Add the ValidateSet to the attributes collection   
        $SQLInstance = "localhost\SQLExpress"
        $SQLDatabase = "Microsoft365"
        $sqlqr = "select * from [Microsoft365].[dbo].[tenants]"
        $customers = invoke-sqlcmd -query $sqlqr -ServerInstance $SQLInstance -Database $SQLDatabase
        $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($Customers | Select-Object -ExpandProperty tenantname)

        $AttributeCollection.Add($ValidateSetAttribute)

        $Parameter2Name = "ProductName"
        $Parameter2Attribute = New-Object System.Management.Automation.ParameterAttribute
        $Parameter2Attribute.Mandatory = $false
        $Parameter2Attribute.Position = 2
        $Attribute2Collection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
        $Attribute2Collection.Add($Parameter2Attribute)
        $sqlqr2 = "select * from [Microsoft365].[dbo].[skus]"
        $skus = invoke-sqlcmd -query $sqlqr2 -ServerInstance $SQLInstance -Database $SQLDatabase
        #$arrSet = Get-WmiObject Win32_Service -ComputerName $computername | select -ExpandProperty Name
        $ValidateSet2Attribute = New-Object System.Management.Automation.ValidateSetAttribute($skus | Select-Object -ExpandProperty productname)

        $Attribute2Collection.Add($ValidateSet2Attribute)
        $RuntimeParameter2 = New-Object System.Management.Automation.RuntimeDefinedParameter($Parameter2Name, [string], $Attribute2Collection)



        # Create and return the dynamic parameter
        $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
        $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
        $RuntimeParameterDictionary.Add($Parameter2Name, $RuntimeParameter2)
        return $RuntimeParameterDictionary
    }
    begin {

        # Bind the parameter to a friendly variable
        $Kunde = $PsBoundParameters[$ParameterName]
        if ( -not $kunde) {
            $kunde = "%"
        }
        else {
     
        }
        $Product = $PsBoundParameters[$Parameter2Name]
        if ( -not $Product) {
            $Product = "%"
        }
        else {
         
        }


    }
    process {
        $SQLInstance = "localhost\SQLExpress"
        $SQLDatabase = "Microsoft365"
        
    $sqlquery = "SELECT dbo.usersextended.displayname as userdisplayname,userprincipalname,givenname,surname,dbo.usersextended.active as activeuser,tenantname,dbo.tenants.displayname as tenantdisplayname,Productname,usagelocation FROM usersextended Cross apply string_split(usersextended.skus,',') INNER JOIN dbo.tenants ON dbo.usersextended.tenantid = dbo.tenants.tenantID inner join dbo.skus on dbo.Skus.skuid like '%'+value+'%' where usersextended.skus like '%-%' and tenantname like '$($kunde)' and usersextended.userprincipalname not like '%#EXT#%' and productname like '$($product)'" 
        $users = invoke-sqlcmd -query $sqlquery -ServerInstance $SQLInstance -Database $SQLDatabase
        if ($users.count -gt 0) { $f = (((($users[0] | get-member) | Where-Object { $_.membertype -eq "Property" } )).name) | sort-object -desc }
        $users | Select-Object $f 

    }

}

The important part is the T-Sql. This will join the usersextended (previously users) table and tenant table to generate a list of users and licenses. If used in combination with export-excel this is one of my favorite tools.

Hash tables in powershell

We all have the need to store data in some kind of arrays. I use hashtables a lot. Preferred use is as a lookup table, I can use ‘contains’ instead of looping through each item or reference an object by name instead of index number. Lookup table for licenses is where I use it the most at the time being. A table of license ID versus license name. One place for this is Microsoft licenses in partnercenter, and show licenses assigned to users. Using powershell to connect to Microsoft partnercenter to get all available skus by ID , name and displayname. When I have collected this – how do you ask?.
Here you go:

 Import-module partnercenter
 Connect-CompanyPartnerCenter
 
    $lic = @{ }
    $prodid = @{ }
    $customerstenants = Get-PartnerCustomer
    ForEach ($customer in $customerstenants) {
        $message = "Customer :$($customer.name)"
        write-verbose -Message "$message"
        $custskus = Get-PartnerCustomerSubscribedSku -CustomerId $customer.customerid
        foreach ($custsku in $custskus) {
            if ($lic.ContainsKey($custsku.SkuPartNumber)) {
               
            }
            else {
                $lic.Add($custsku.skupartnumber, $custsku.productname)
                $prodid.Add($custsku.SkuId, $custsku.ProductName)
            }

        }  
 }

Connect-companypartnercenter is a custom function. Here is how to connect to Office partnercenter.
The script above will (hopefully , no try-catch) end up in 2 hash tables. One “skuid” and productname:

"SkuID","DisplayName"
"WACONEDRIVEENTERPRISE","OneDrive for Business (Plan 2)"
"O365_BUSINESS_ESSENTIALS","Office 365 Business Essentials"
"MCOSTANDARD","Skype for Business Online (Plan 2)"
"Win10_E3_Local","Windows 10 Enterprise E3 (local only)"
"WACONEDRIVESTANDARD","OneDrive for Business (Plan 1)"

As long as I’m using this in the same script it is ok. Exporting it to files is not that straight forward, even tried ‘convertto-json’ –not working (convertfrom-json returns an array)
So I do this to crate a csv file. Creating correct columns.

       $csvobject = ($lic.GetEnumerator() | Select-Object @{expression = { $_.name }; label = "SkuID" }, @{expression = { $_.value }; label = "DisplayName" } | convertto-csv -Delimiter ',' -NoTypeInformation )
        $csvobject | Out-File -Encoding unicode $ExportSkuIDFile

        $csvobject = ($prodid.GetEnumerator() | Select-Object @{expression = { $_.name }; label = "ProductID" }, @{expression = { $_.value }; label = "DisplayName" } | convertto-csv -Delimiter ',' -NoTypeInformation )
        $csvobject | Out-File -Encoding unicode $ExportProductIDFile

Now that we have the file it would be great to be able to use them 🙂
And this is how it is imported in to a hashtable.

$filecontent=import-csv -Path $importfile -Encoding Unicode -Header "column1","column2"
$hashtable=@{}
$filecontent|ForEach-Object{
    write-verbose -message "$_.column1 , $_.column2"
    $hashtable.add($_.column1,$_.column2)
}

Read the file line by line and build our hash-table. Now we can use : $hashtable[“MCOSTANDARD”] , this will return the full name ‘Skype for Business Online (Plan 2)’
I have this as functions in my default module, the module I import in all my powershell sessions. (Surely you know about powershell profiles?)
Hope this helped.

SecretServer(Thycotic) plugin for powershell SecretsManagement module.

With the new powershell module SecretsManagement it is possible to add plugins. The new module is used to get/add/remove credentials. I created a plugin for secretserver to this module.

To get started you will have to install secretsmanagement:
Install-Module -Name Microsoft.Powershell.SecretsManagement -AllowPrerelease
If -AllowPrerelase is not an option you would have to update PowershellGet first. ( install-script powershellget )

After SecretManagement has been installed you can run :

add-secret -Name "Test" -secret (get-credential) -vault -builtinlocalvault 

, followed by

get-secret Test 

.

But I would like to have a uniform cmdlet for all my credentials. So I used Secrets Management Module Vault Extensions to get me started.

If you would like to install my module from powershell gallery run :

install-module -name secretsmanagement.secretserver

A bit more info for those interested

First created a new folder structure below C:\Program Files\WindowsPowerShell\Modules\
Secretsmanagement\0.0.3\SecretsManagementExtension
In the version folder “0.0.3” i added 2 files : Readme.txt (How to register vault) and SecretsManagement.SecretServer.psd1 ( Datafile for the module)

# Module manifest for module 'SecretsManagement.SecretServer'
# Generated by: Atle Vatland
# Generated on: 2/12/2020
@{
# Script module or binary module file associated with this manifest.
# RootModule = ''
# Version number of this module.
ModuleVersion = '0.0.3'
# Supported PSEditions
CompatiblePSEditions = @('Desktop')
# ID used to uniquely identify this module
GUID = 'e25aacec-637f-4935-bbd9-463a75ba46ea'
# Author of this module
Author = 'Atle Vatland'
# Copyright statement for this module
Copyright = '(c) 2020 Atle Vatland. All rights reserved.'
# Description of the functionality provided by this module
Description = 'Proof of concept for Secretserver( by Thycotic ) vault extension for powershell. Remove-secret is not implemented in this test.'
# Modules that must be imported into the global environment prior to importing this module
PowershellVersion = '5.1'
}

The subfolder has to be named “SecretsManagementExtension” also the scripts has to be named SecretsManagementExtension.psm1 and .psd1
What gave me some hassle was that the add-secret cmdlet actually calls set-secret in the extension.

SecretsManagementExtension.psd1:

@{
    ModuleVersion = '1.0'   
   RootModule = '.\SecretsManagementExtension.psm1'
   PowershellVersion = '5.1'
    FunctionsToExport = @('Get-Secret','Set-Secret','Remove-Secret','Get-SecretInfo')
    PrivateData = @{
    PSData = @{
        # A URL to the license for this module.
        LicenseUri = 'https://opensource.org/licenses/MIT'
   } # End of PSData hashtable
  } # End of PrivateData hashtable
}

Have not implemented remove-secret as we do not want to to delete any secrets.
SecretsManagementExtension.psm1:

# Licensed under the MIT License.

function Get-Secret
{
    param (
        [string]$Name,
        [hashtable]$AdditionalParameters
    )
        $where = $AdditionalParameters.secretserveruri
        $ws = New-WebServiceProxy -uri $where -Credential $AdditionalParameters.secretservercredential #-ErrorAction SilentlyContinue
        $wsResult = $ws.GetSecret([int]$name, $false, $null)
        [PSCredential]::new($wsResult.Secret.Items[1].value.ToString(), ($wsResult.Secret.Items[2].value.ToString()|ConvertTo-SecureString -AsPlainText -Force))       
}

function set-secret
{
    param (
        [string] $Name,
        [object] $Secret,
        [hashtable] $AdditionalParameters
    )
   
    $where =  $AdditionalParameters.secretserveruri
    $cred= $AdditionalParameters.secretservercredential
    $secretserverfolder=$AdditionalParameters.secretserverfolderid    
    if($secretserverfolder -eq $null){
        $secretserverfolder=44
    }
    $domain=$Name
    $templatename=$AdditionalParameters.secretservertemplate
    if($templatename -eq $null){
        $templatename= "Windows Account"
    }
        if($Secret -eq $null){
        throw "Secret can not be null."
    }
    elseif($secret -is [string]){
        throw "String is currently not supported"
    }
    elseif($secret -is [hashtable]){
        throw "String is currently not supported"
    }
    elseif ($secret -is [PSCredential]){
        $username = $Secret.username
        $password = $secret.GetNetworkCredential().password
    }    
    $ws = New-WebServiceProxy -uri $where -Credential $cred 
    # Get Template
    $template = $ws.GetSecretTemplates().SecretTemplates | Where {$_.Name -eq $templateName}
    # Set fields info
    $secretName = $domain + "-" + $UserName
    $secretItemFields = (($template.Fields | Where {$_.DisplayName -eq "Machine"}).Id, ($template.Fields | Where {$_.DisplayName -eq "Username"}).Id, ($template.Fields | Where {$_.DisplayName -eq "Password"}).Id, ($template.Fields | Where {$_.DisplayName -eq "Notes"}).Id)
    $secretItemValues=($domain,$UserName,$password, "")
    $folderId = $secretserverfolder
    # Add secret to secretserver.
    $addResult = $ws.AddSecret($template.Id, $secretName, $secretItemFields, $secretItemValues, $folderId)
    if($addResult.Errors.Count -gt 0){
        return $false
    }else{
        return $true
    }
}

function Remove-Secret
{
    param (
        [string] $Name,
        [hashtable] $AdditionalParameters
    )
    throw "Not implemented"
}

function Get-SecretInfo
{
    param(
        [string] $filter,
        [hashtable] $AdditionalParameters
    )
    if ([string]::IsNullOrEmpty($filter)) { $filter = "*" }
    $where =  $AdditionalParameters.secretserveruri
    $ws = New-WebServiceProxy -uri $where -Credential $AdditionalParameters.secretservercredential # -ErrorAction SilentlyContinue
    $hits=$ws.SearchSecrets($filter,$null,$null)
    $result=@()
    foreach($hit in $hits.SecretSummaries){
    $result+= ([pscustomobject] @{
        Name = $($hit.secretid.tostring())                                                                   
        Value  = $($hit.secretName.ToString())
        })
    }
    $result
}

To register this extension for module for SecretsManagement there are some required parameters.
Credentials used to access secretserver, secretserver web service url, default template and default folder.
Credential and parameters are stored encrypted in Credential Manager.

[pscredential]$c=Get-Credential  # Secretserver credential
 
 Register-SecretsVault -Name "VaultName" -ModuleName secretsmanagement.secretserver -VaultParameters @{
    secretservercredential=$c    # Account used to connect to secret server.
    secretserveruri="https://secretserver.domain.local/secretserver/winauthwebservices/sswinauthwebservice.asmx" # Webservice uri
    secretserverfolderid="44"    # New secrets are stored in this folder. Folder id -1 is default if not specified.
    secretservertemplate="Windows Account"  # Template used when creating new secrets. "Windows Account" is default if not specified.
 }
 

Simple Powershell MRU list

When using using my secret server powershell functions I got tired of constantly searching for secret ID’s. I had to do a new search just because I could not remember the ID’s. So I added some kind of MRU to my get-secretID function. This code block creates to classes mruitem and mrulist. The mrulist has three functions. Updatelist: Check if the ID already is in the list , if so update last used time. If it is not in the list , add it. Also remove oldest item if list is longer then max size. Savelist: saves the list to mru file. Loadlist : Load old list from file. Script also uses a global variable name ssmru. The global variable is declared in the powershell profile as $ssmru = “<filepath to mru list>”. I use a file because I want this MRU to stay persistent during a reboot.

# MRU list and item class 
class mruitem{
[string]$ssid
[string]$name
[datetime]$lastuseddate

mruitem([string]$ssid,[string]$name,[datetime]$lastuseddate)
{
$this.ssid=$ssid
$this.lastuseddate=$lastuseddate
$this.name=$name
}
}

class mrulist: System.Collections.ArrayList {

[int]$MaxSize = 15

updatelist([mruitem]$item){
$pos=$null
if($this.count -gt 0){
if($this.ssid.contains($item.ssid)){
$pos=$this.ssid.indexof($item.ssid)
}
else {$pos=$null}

if($pos){
$this[$pos].lastuseddate=$item.lastuseddate
$this[$pos].name=$item.name
}else{ #Add new
$this.add($item)
}
}else {
$this.add($item)
}
if($this.count -gt $this.MaxSize){
#delete oldest
$new = New-Object System.Collections.ArrayList
$new=($this| Sort-Object -Property lastuseddate )
$ssid=$new[0].ssid
$ssid
$pos=$this.ssid.indexof($ssid)
$this.RemoveAt($pos)
$new=$null
}
}

loadlist(){
if(Test-Path $global:ssmru){
$data=(Get-Content -Path $global:ssmru -Encoding Unicode)|ConvertFrom-Json
foreach($mru in $data){
$this.updatelist([mruitem]::new($mru.ssid,$mru.name,$mru.lastuseddate))
}
}
}

savelist(){
$data=$this|ConvertTo-Json
$data|out-file -FilePath $global:ssmru -Encoding unicode
}
}

$mrulist=New-Object -typename mrulist
$mrulist.loadlist()
if($mru){
$mrulist | Sort-Object -Property lastuseddate
return
}

For those interested , here is the updated version of get-secretID

function Get-SecretID
{
param(
[parameter(ValueFromPipeline=$True)]
[int] $secretID,
[pscredential]$sscred,
[switch]$Cleartext,
[switch]$mru
)
if(!($secretID)){$mru=$true}

# MRU list and item class
class mruitem{
[string]$ssid
[string]$name
[datetime]$lastuseddate

mruitem([string]$ssid,[string]$name,[datetime]$lastuseddate)
{
$this.ssid=$ssid
$this.lastuseddate=$lastuseddate
$this.name=$name
}
}

class mrulist: System.Collections.ArrayList {

[int]$MaxSize = 15

updatelist([mruitem]$item){
$pos=$null
if($this.count -gt 0){
if($this.ssid.contains($item.ssid)){
$pos=$this.ssid.indexof($item.ssid)
}
else {$pos=$null}

if($pos){
$this[$pos].lastuseddate=$item.lastuseddate
$this[$pos].name=$item.name
}else{ #Add new
$this.add($item)
}
}else {
$this.add($item)
}
if($this.count -gt $this.MaxSize){
#delete oldest
$new = New-Object System.Collections.ArrayList
$new=($this| Sort-Object -Property lastuseddate )
$ssid=$new[0].ssid
$ssid
$pos=$this.ssid.indexof($ssid)
$this.RemoveAt($pos)
$new=$null
}
}

loadlist(){
if(Test-Path $global:ssmru){
$data=(Get-Content -Path $global:ssmru -Encoding Unicode)|ConvertFrom-Json
foreach($mru in $data){
$this.updatelist([mruitem]::new($mru.ssid,$mru.name,$mru.lastuseddate))
}
}
}

savelist(){
$data=$this|ConvertTo-Json
$data|out-file -FilePath $global:ssmru -Encoding unicode
}
}

$mrulist=New-Object -typename mrulist
$mrulist.loadlist()
if($mru){
$mrulist | Sort-Object -Property lastuseddate
return
}

$where = 'https://<Server FQDN>/secretserver/winauthwebservices/sswinauthwebservice.asmx'

if($sscred -ne $null){
try{
$ws = New-WebServiceProxy -uri $where -Credential $sscred
}
catch{
Write-host "Error: Error connecting to secret server."
return $null
}
}else{

try{
$ws = New-WebServiceProxy -uri $where -UseDefaultCredential -ErrorAction SilentlyContinue
if($ws -eq $null){
if (!(Test-Path Variable:\ssadmin)){
throw {
Write-host "No secretserver admin specified or variable 'ssadmin' defined.`nThis is to be used by 'get-storedcredential'"
}
}
$adminacc=Get-StoredCredential -UserName $ssadmin
$ws = New-WebServiceProxy -uri $where -Credential $adminacc -ErrorAction SilentlyContinue
if($ws -eq $null){throw{Write-host "Unable to connect to SecretServer"}}
}
}
catch{
Write-host "Error connecting to SecretServer"
return $null
}
}

$wsResult = $ws.GetSecret($secretId, $false, $null)
if($wsresult.errors -ne $null){
$Cred=New-Object PSObject
$Cred | add-member -NotePropertyName "Username" -NotePropertyValue $wsresult.errors
$Cred | Add-Member -NotePropertyName "Password" -NotePropertyValue $wsresult.errors

return $Cred
} else {

$u=$wsResult.Secret.Items[1].value.ToString()
$ep = ConvertTo-SecureString $wsResult.Secret.Items[2].value.ToString() -AsPlainText -Force
[pscredential]$Cred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $u,$ep
if($Cleartext){
[psobject]$Cred=New-Object PSObject
$Cred | add-member -NotePropertyName "Username" -NotePropertyValue $u
$Cred | Add-Member -NotePropertyName "Password" -NotePropertyValue $wsResult.Secret.Items[2].value.ToString()
$Cred | Add-Member -NotePropertyName "Domain" -NotePropertyValue $wsResult.Secret.Items[0].value.ToString()
}
$mrulist.updatelist([mruitem]::new($secretID,$u,(get-date)))
$mrulist.savelist()
return $Cred
}
}

Get Secretserver secret

Since we are using SecretServer as our credential store it is of great help to be able to get credentials directly from powershell. This is a small function that connects to secretserver webservices and retrieve a secret based on secred ID. The function will connect to the webservice as the signedin user or by a supplied credential or lastly by a predefined stored credential. To use stored credential I’am using functions from https://github.com/cunninghamp/PowerShell-Stored-Credentials .

Usually you would use the PS credential object directly. To get the password as text you could use it from the PSobject referring to the get networkcredential().


$cred=get-secretid -secretID 2007
$password_As_text=$cred.GetNetworkCredential().Password

Or if you need the password in clear text, displayed on screen, you could specify that as a an argument.

The function is made for my usage, so there is definitive roomfor improvement .


function Get-SecretID
{
param(
[parameter(ValueFromPipeline=$True)]
[int] $secretID,
[pscredential]$sscred,
[switch]$Cleartext
)

$where = 'https://secretserverdnsname/secretserver/winauthwebservices/sswinauthwebservice.asmx'

if($sscred -ne $null){
    $ws = New-WebServiceProxy -uri $where -Credential $sscred
}else{

  try{
    $ws = New-WebServiceProxy -uri $where -UseDefaultCredential -ErrorAction SilentlyContinue
    if($ws -eq $null){
      if (!(Test-Path Variable:\ssuser)){
        throw {
          Write-Host "No secretserver user specified or variable 'ssuser' defined.`nThis is to be used by 'get-storedcredential'"
        }
      }
        $credacc=Get-StoredCredential -UserName $ssuser
        $ws = New-WebServiceProxy -uri $where -Credential $credacc -ErrorAction SilentlyContinue
        if($ws -eq $null){throw{Write-host "Unable to connect to SecretServer"}}
    }
  }
  catch{

  }
}

$wsResult = $ws.GetSecret($secretId, $false, $null)
if($wsresult.errors -ne $null){
  $Cred=New-Object PSObject
  $Cred | add-member -NotePropertyName "Username" -NotePropertyValue $wsresult.errors
  $Cred | Add-Member -NotePropertyName "Password" -NotePropertyValue $wsresult.errors
 
  return $Cred
} else {
 
$u=$wsResult.Secret.Items[1].value.ToString()
$ep = ConvertTo-SecureString $wsResult.Secret.Items[2].value.ToString() -AsPlainText -Force
[pscredential]$Cred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $u,$ep
if($Cleartext){
  [psobject]$Cred=New-Object PSObject
    $Cred | add-member -NotePropertyName "Username" -NotePropertyValue $u
    $Cred | Add-Member -NotePropertyName "Password" -NotePropertyValue $wsResult.Secret.Items[2].value.ToString()
    $Cred | Add-Member -NotePropertyName "Domain" -NotePropertyValue $wsResult.Secret.Items[0].value.ToString()
  }
return $Cred
}
}

CSP access to tenants using powershell. Part 4

This is a small script that connects to partnercenter list all customers tenants and let you select one. When one is selected it connects to azuread and az for that customer.

All my credentials are stored in SecretServer . I use a web service request to get those credentials. I will show these modules in a later post.


import-module partnercenter

#$app = AppID + AppKey
$app=Get-SecretID -secretID xxxx
#rt = refreshtoken.
$rt=Get-SecretID -secretID yyyy
$refreshtoken=$rt.GetNetworkCredential().Password
$tid="'csp tenant directory id'"

   
   
#Connect PartnerCenter
$pcToken = New-PartnerAccessToken -RefreshToken $refreshToken -Resource https://api.partnercenter.microsoft.com -Credential $app -TenantId $tid
Connect-PartnerCenter -AccessToken $pcToken.AccessToken -ApplicationId $app.username -TenantId $tid

    $customers=get-partnercustomer | sort-object -Property name
    $counter=0
    foreach($cust in $customers){
        Write-Host "$($counter) - $($cust.Name) - $($cust.domain)"
        $counter++
    }
    $custid=Read-Host "Enter customer #"
    $customer=$customers[$custid]
    Write-host " Targeting : $($customer.name) - $($customer.Domain)"

$azureToken = New-PartnerAccessToken -Resource "https://graph.microsoft.com/" -Credential $app -RefreshToken $refreshtoken -TenantId $customer.CustomerId
$graphToken = New-PartnerAccessToken -RefreshToken $refreshToken -Resource "https://graph.windows.net/" -Credential $app -TenantId $customer.CustomerId  

Connect-Azuread -aadAccessToken $graphToken.AccessToken -msAccessToken $azureToken.AccessToken -TenantId $customer.CustomerId  -AccountId $app.username


$azure2Token = New-PartnerAccessToken -Resource https://management.azure.com/ -Credential $app -RefreshToken $refreshtoken -TenantId $customer.CustomerId
$graph2Token = New-PartnerAccessToken -RefreshToken $refreshToken -Resource https://graph.windows.net/ -Credential $app -TenantId $customer.CustomerId  

 Connect-AzAccount -AccessToken $azure2Token.AccessToken -GraphAccessToken $graph2Token.AccessToken -TenantId $customer.CustomerId  -AccountId $app.username

Use powershell to get external IP address

How can you get your external IP address from powershell? I use a simple script to query an external public web service. The service I’m using is hosted by ipinfo.io . I have created a small function that is placed in my powershell library. All my modules are loaded by using powershell profiles.

This simple function uses rest. Usage is simple : get-mypublicip . Could easily be uses in script (get-mypublicip).ip .


function get-mypublicip{
    Write-Verbose "Resolving external IP"
    try {
        $ipaddr = Invoke-RestMethod http://ipinfo.io/json #| Select-Object -ExpandProperty ip
        }
    catch {
        throw "Can't get external IP Address. Quitting."
        }
    if ($ipaddr -eq $null) { throw "Can't get external IP Address. Quitting." }
    Write-Verbose "External IP is $ipaddr"
    return $ipaddr
}

CSP access to tenants using powershell. Part 3

In this part 3 of CSP and powershell I will show how you can connect to azureAD of a customer tenant using your CSP app credentials and refreshtoken. This is almost the same procedure as we use to connect to az. We will start with the same variables as in part 2. Remember to keep your credential and secure, as it will give access to all your tenants.


$app=get-credential # Get AppID and Key for out partnecenter app. (created in part 1)
$refreshtoken = 'refreshtoken' # From part 1 or whenever we get a new one.
$CustomerTenantID= 'Azure directory object id'

Struggled for a while to get this to work. The important thing is the endpoints and when to use the customer tenant ID.


$azureToken = New-PartnerAccessToken -Resource "https://graph.microsoft.com/" -Credential $app -RefreshToken $refreshtoken -TenantId $CustomerTenantID
$graphToken = New-PartnerAccessToken -RefreshToken $refreshToken -Resource "https://graph.windows.net/" -Credential $app -TenantId $CustomerTenantID  

Connect-Azuread -aadAccessToken $graphToken.AccessToken -msAccessToken $azureToken.AccessToken -TenantId $CustomerTenantID  -AccountId $app.username

So now you can use get-azureaduser to get users from this customer tenant.

You could also use the MS online module msol to query for users, this module requires you to use tenantid as an argument.

In part 4 I will wrap this up in a simple script allowing you to select customer tenant.

CSP access to tenants using powershell. Part 2

In part 1 we created the Azure Enterprise App for Partnercenter and used this information to connect using powershell and connect-partnercenter. Now we will use this to connect to one of our customers tenants. First we will use AZ module and connect-azaccount. We will use the AZ module and the partnercenter module. So if those at not installed please install :


install-module az
install-module partnercenter

I will use the partnercenter module to request an accesstoken for azure.


$app=get-credential # Get AppID and Key for out partnecenter app. (created in part 1)
$refreshtoken = 'refreshtoken' # From part 1 or whenever we get a new one.
$CustomerTenantID= 'Azure directory object id'

Now we have all the required info to connect. The credentials should be stored securely!!!!


$azureToken = New-PartnerAccessToken -Resource https://management.azure.com/ -Credential $app -RefreshToken $refreshtoken -TenantId $CustomerTenantID

$grapToken = New-PartnerAccessToken -RefreshToken $refreshToken -Resource https://graph.windows.net/ -Credential $app -TenantId $CustomerTenantID  

Connect-AzAccount -AccessToken $azureToken.AccessToken -GraphAccessToken $graphToken.AccessToken -TenantId $CustomerTenantID  -AccountId $app.username

There. We are now connected to our azure of our customer. In next part we will connect to azureAD

CSP access to tenants using powershell. Part 1

A short explanation of how to access customer tenant using a CSP tenant SPN credential connectiong to AzureAD and AZ. Have been struggling for a while to manage all our customers tenants using powershell scripts. It can be complicated to organize all the credentials, tenant domain, tenant id’s password expiry.

First step is to be able to use powershell in the CSP tenants and access the partnercenter module. To get this started Microsoft has published a script to create the SPN required for this. https://docs.microsoft.com/en-us/powershell/partnercenter/secure-app-model?view=partnercenterps-1.5 This script will help you create the SPN . When using the SPN for the first time you will have to consent it using an admin account. The “ConfigurePreconsent” argument adds the spn to the adminagents group, this result in the account being a global admin in the customer tenants. Next:


$credential = Get-Credential
$token = New-PartnerAccessToken -Consent -Credential $credential -Resource https://api.partnercenter.microsoft.com -TenantId 'Your Tenant Id'

This is to consent the spn and get the refresh token we will in further logins. TenantId is the ID of your partner tenant. First it asks for the credential of the newly created spn (appID and key), next it will require you to login and consent using a service account . In return you get a token. Remember to store the refresh token part in a secure place as this will be used in our next login.


$refreshToken = 'Enter the refresh token value here'
$credential = Get-Credential
$tenantId = 'Your Tenant Id'
$pcToken = New-PartnerAccessToken -RefreshToken $refreshToken -Resource https://api.partnercenter.microsoft.com -Credential $credential -TenantId $tenantId
Connect-PartnerCenter -AccessToken $pcToken.AccessToken -ApplicationId $appId -TenantId $tenantId

Here we connect to partnercenter. We got the $refreshtoken in the previous step. $credential is our appid and key returned from the script. $tenantid is the tenantid of the partner tenant. Returned from the connection is a new $pcToken. This $pcToken includes a new refresh token that we could store and use at next login, but the one we already got would still last for a default value of 90 days. We’ve had some issues it the MFA settings in the tenant allow the user to “not be asked for credentials in xx days” (So we always uncheck this box).

Part 2 will for the AZ connection to customer tenant.

Powershell to get DirectAccess connection history.

This is a simple powershell to get data from the DirectAccess database. It reguired some serious Bing’ing (and google) to get the time field. You will have to configure reporting database in DirectAccess config. I used windows internal database.

$server="\\.\pipe\MICROSOFT##WID\tsql\query"
$database="RaAcctDb"
#$table="connectiontable"
$table="sessiontable"
#$table="endpointsaccessedtable"
#$table="serverendpointtable"

$cs="server=$server;database=$database;Integrated Security=True;"
$connection=New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString=$cs
$connection.Open()

#$query="Select * from $table"
#$query="Select * from $database.INFORMATION_SCHEMA.TABLES"
$query="declare @start bigint=131277336299720000;select dateadd(mi,datediff(mi,getutcdate(),getdate()),([sessionstarttime]/864000000000.0-109207)) AS DATO,* from sessiontable join connectiontable on sessiontable.connectionid=connectiontable.connectionid where sessionstarttime &amp;gt;=@start"

$command=$connection.CreateCommand()
$command.CommandText=$query
$result=$command.ExecuteReader()

$resulttable=New-Object System.Data.DataTable
$resulttable.Load($result)
$resulttable | Out-GridView
$connection.Close()
 

Use Powershell to get LeakedCredentials from Azure using Graph

Leaked credentials listed from Azure using powershell and Microsoft Graph 
We need one Azure AD Premium X license to get this log.

Would it be nice to list all leakedcredentials using powershell?(or riskysignins or identiyriskevents). All of this could be achieved using powershell and REST api at Microsoft Graph. I have a scheduled task running to get this reports. Using a appilcation in Azure. All credentials are stored in SecretServer. First we need an Application Registration in Azure.

Application Registration list

The registered application. The home page URL can be any url, it is not used.

After we have created the AppReg. Add a password, app key. Combined with the application id this is our username and password.

Now it is time to give this app the required permissions from microsoft we can identify witch permissions are needed to run this query. https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/leakedcredentialsriskevent_get 

Permission required.
Some of the permissions set in Azure.
Remeber to click “Grant Permissions” after they are added.

Next would be to set the enterprise application to “user assignment required” and “Enabled for users to sign-in.” also “Hide it from users.

Settings of the Enterprise application.

Now we are ready to start with our powershell script.

$loginURL="https://login.microsoft.com"
$resource="https://graph.microsoft.com"
$l_tenantdomain="&lt;domain&gt;.onmicrosoft.com"
$l_ClientID ="&lt;APPID&gt;"
$l_ClientSecret="&lt;APP password Key&gt;"
    $body= @{grant_type="client_credentials";
    resource=$resource;
    client_id=$l_ClientID;
    client_secret=$l_ClientSecret
}
$oauth=Invoke-RestMethod -Method Post -Uri $loginURL/$l_tenantdomain/oauth2/token?api-version=1.0 -Body $body
if ($oauth.access_token -ne $null)
 {
      $headerParams = @{'Authorization'="$($oauth.token_type) $($oauth.access_token)"
      }
 # https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/leakedcredentialsriskevent_get
$url = "https://graph.microsoft.com/beta/leakedCredentialsRiskEvents"
$myReport = (Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url)
} else {
Write-Host "ERROR: No Access Token"
} 
($myReport.Content | ConvertFrom-Json).value |where-object {$_.riskeventstatus -eq "active"} | ft risk&lt;em&gt;,user