| | 0

Unattended Login to OneDrive with Username and Password – Feasibility test

In February I wrote a PowerShell module for the PowerShellGallery which makes some commands available to interact with OneDrive (https://www.sepago.com/blog/2016/02/21/Use-PowerShell-Module-OneDrive-from-PowerShellGallery-command-line). The command Get-ODAuthentication authenticates the user to OneDrive by opening a browser window for the live login, so that the user can enter the credentials. As far as I know it’s currently not possible to login automatically via a script.

In this blog I offer a “dirty” solution to login automatically. But careful: This method uses an Internet Explorer com object to perform the login process. If Microsoft changes something, the automatic login can fail. I also assume that this way is (for sure) not supported by Microsoft nor desired. I used this challenge to train PowerShell and Internet Explorer remoting – which is very spooky (IE 11 on Win10 and Server handles events differently, etc.).

I tested the script with the following OS:

  • Windows 10: Englisch, Deutsch, IE11
  • Windows Server 2012 R2: Englisch, Deutsch, IE11

The script performs the following steps:

  1. Open a single IE instance and log a possible user out
  2. Go to the login page
  3. Fill out the form (username and password)
  4. Send “change” events to the fields
  5. Press submit
  6. Confirm access
  7. Extract the access token
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
$DebugPreference = "Continue"
#Variables
$LoginName="Marcel.Meurer@sepago.de"
$Password="------------"
$ClientID="00000000XXXXXXXXXXXXXXX"
#Add *.live.com and *.gfx.ms to trusted sites
New-Item -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\EscDomains" -Name "live.com" –Force | Out-Null
Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\EscDomains\live.com" -Name "https" -Value 2
New-Item -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\EscDomains" -Name "gfx.ms" –Force | Out-Null
Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\EscDomains\gfx.ms" -Name "https" -Value 2
#Enable protected mode for trusted sites
Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\2" -Name "2500" -Value 0
$URIGetAccessToken="https://login.live.com/oauth20_authorize.srf?client_id="+$ClientID+"&scope=onedrive.readwrite&response_type=token&redirect_uri=https://login.live.com/oauth20_desktop.srf"
#Create IE Com-object
$ie = New-Object -com "InternetExplorer.Application.1"
$ieLogout = New-Object -com "InternetExplorer.Application.1"
$ie.visible = $false
$ie.silent = $true
write-debug ("Logout first")
$ieLogout.Navigate("https://login.live.com/logout.srf")
while($ieLogout.busy){Start-Sleep 1}
$ieLogout.Quit()
write-debug ("Go to login page")
$ie.Navigate($URIGetAccessToken)
while($ie.busy){Start-Sleep 1}
#Check, if function createEvent exist
$legacy=$false
try {$ie.document.createEvent()| Out-Null} catch {$legacy=$true}
if ($legacy) {write-debug ("Working in legacy mode")}
if (!$legacy) {
    #Prepare event
    $evt = $ie.document.createEvent("HTMLEvents")
    $evt.initEvent("change",$true,$false)
}
write-debug ("Filling the formular")
$ie.Document.IHTMLDocument3_getElementsByName("login").item().Value=$LoginName
$ie.Document.IHTMLDocument3_getElementsByName("loginfmt").item().Value=$LoginName
$ie.Document.IHTMLDocument3_getElementsByName("passwd").item().Value=$Password
write-debug ("Sending change events to the fields")
if ($legacy)
{
    $ie.Document.IHTMLDocument3_getElementsByName("loginfmt").item().FireEvent("onChange") | Out-Null
    $ie.Document.IHTMLDocument3_getElementsByName("passwd").item().FireEvent("onChange") | Out-Null
} else
{
    $ie.Document.IHTMLDocument3_getElementsByName("loginfmt").item().DispatchEvent($evt) | Out-Null
    $ie.Document.IHTMLDocument3_getElementsByName("passwd").item().DispatchEvent($evt) | Out-Null
}
write-debug ("Press submit")
$ie.Document.IHTMLDocument3_getElementsByName("idSIButton9").item().click()
while($ie.busy){Start-Sleep 1}
write-debug ("Confirm access")
$ie.Document.IHTMLDocument3_getElementsByName("ucaccept").item().click()
while($ie.busy){Start-Sleep 1}
$ReturnURI=($ie.LocationURL).ToString().Replace("#","&")
$ie.Quit()
$Authentication = New-Object PSObject
ForEach ($element in $ReturnURI.Split("?")[1].Split("&"))
{
    $Authentication | add-member Noteproperty $element.split("=")[0] $element.split("=")[1]
}
if ($Authentication.PSobject.Properties.name -match "expires_in")
{
    $Authentication | add-member Noteproperty "expires" ([System.DateTime]::Now.AddSeconds($Authentication.expires_in))
}
if (!($Authentication.PSobject.Properties.name -match "expires_in"))
{
    write-warning("There is maybe an errror, because there is no access_token!")
}
$Authentication.scope=$Authentication.scope.Replace("%20"," ")
$Authentication