/*
Deployment script for BulkEncryptionDatabase_1

This code was generated by a tool.
Changes to this file may cause incorrect behavior and will be lost if
the code is regenerated.
*/

GO
SET ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON;

SET NUMERIC_ROUNDABORT OFF;


GO
:setvar DatabaseName "BulkEncryptionDatabase_1"
:setvar DefaultFilePrefix "BulkEncryptionDatabase_1"
:setvar DefaultDataPath "C:\Users\damoo\AppData\Local\Microsoft\VisualStudio\SSDT\BulkEncryption"
:setvar DefaultLogPath "C:\Users\damoo\AppData\Local\Microsoft\VisualStudio\SSDT\BulkEncryption"

GO
:on error exit
GO
/*
Detect SQLCMD mode and disable script execution if SQLCMD mode is not supported.
To re-enable the script after enabling SQLCMD mode, execute the following:
SET NOEXEC OFF; 
*/
:setvar __IsSqlCmdEnabled "True"
GO
IF N'$(__IsSqlCmdEnabled)' NOT LIKE N'True'
    BEGIN
        PRINT N'SQLCMD mode must be enabled to successfully execute this script.';
        SET NOEXEC ON;
    END


GO
USE [$(DatabaseName)];


GO
IF EXISTS (SELECT 1
           FROM   [master].[dbo].[sysdatabases]
           WHERE  [name] = N'$(DatabaseName)')
    BEGIN
        ALTER DATABASE [$(DatabaseName)]
            SET ARITHABORT ON,
                CONCAT_NULL_YIELDS_NULL ON,
                CURSOR_DEFAULT LOCAL 
            WITH ROLLBACK IMMEDIATE;
    END


GO
IF EXISTS (SELECT 1
           FROM   [master].[dbo].[sysdatabases]
           WHERE  [name] = N'$(DatabaseName)')
    BEGIN
        ALTER DATABASE [$(DatabaseName)]
            SET PAGE_VERIFY NONE,
                DISABLE_BROKER 
            WITH ROLLBACK IMMEDIATE;
    END


GO
ALTER DATABASE [$(DatabaseName)]
    SET TARGET_RECOVERY_TIME = 0 SECONDS 
    WITH ROLLBACK IMMEDIATE;


GO
IF EXISTS (SELECT 1
           FROM   [master].[dbo].[sysdatabases]
           WHERE  [name] = N'$(DatabaseName)')
    BEGIN
        ALTER DATABASE [$(DatabaseName)]
            SET QUERY_STORE (QUERY_CAPTURE_MODE = AUTO, OPERATION_MODE = READ_WRITE) 
            WITH ROLLBACK IMMEDIATE;
    END


GO
PRINT N'Creating [dbo].[Files]...';


GO
CREATE TABLE [dbo].[Files] (
    [Id]               INT             IDENTITY (1, 1) NOT NULL,
    [FilePath]         NVARCHAR (1023) NOT NULL,
    [FileServer]       NVARCHAR (1023) NOT NULL,
    [Location]         NVARCHAR (1023) NULL,
    [Status]           INT             NOT NULL,
    [StartedWhen]      DATETIME        NULL,
    [CompletedWhen]    DATETIME        NULL,
    [Exception]        NVARCHAR (MAX)  NULL,
    [RetryCount]       INT             NULL,
    [InstanceId]       INT             NULL,
    [LabelId]          INT             NOT NULL,
    [NewFileName]      NVARCHAR (1023) NULL,
    [NewFileSize]      BIGINT          NULL,
    [OriginalFileSize] BIGINT          NULL,
    PRIMARY KEY CLUSTERED ([Id] ASC)
);


GO
PRINT N'Creating [dbo].[GlobalConfig]...';


GO
CREATE TABLE [dbo].[GlobalConfig] (
    [Id]    INT             IDENTITY (1, 1) NOT NULL,
    [Key]   NVARCHAR (1023) NOT NULL,
    [Value] NVARCHAR (1023) NOT NULL,
    PRIMARY KEY CLUSTERED ([Id] ASC)
);


GO
PRINT N'Creating [dbo].[Instances]...';


GO
CREATE TABLE [dbo].[Instances] (
    [Id]              INT            IDENTITY (1, 1) NOT NULL,
    [StartTime]       DATETIME       NOT NULL,
    [EndTime]         DATETIME       NULL,
    [NumberProcessed] INT            NULL,
    [NumberErrors]    INT            NULL,
    [Exception]       NVARCHAR (MAX) NULL,
    [ServerId]        INT            NOT NULL,
    PRIMARY KEY CLUSTERED ([Id] ASC)
);


GO
PRINT N'Creating [dbo].[Labels]...';


GO
CREATE TABLE [dbo].[Labels] (
    [Id]        INT            IDENTITY (1, 1) NOT NULL,
    [LabelName] NVARCHAR (255) NOT NULL,
    [LabelGuid] NVARCHAR (255) NOT NULL,
    PRIMARY KEY CLUSTERED ([Id] ASC)
);


GO
PRINT N'Creating [dbo].[Servers]...';


GO
CREATE TABLE [dbo].[Servers] (
    [Id]              INT             IDENTITY (1, 1) NOT NULL,
    [StartTime]       TIME (7)        NOT NULL,
    [EndTime]         TIME (7)        NOT NULL,
    [ServerName]      NVARCHAR (1023) NOT NULL,
    [NumberInstances] INT             NOT NULL,
    [ServerComplete]  BIT             NULL,
    [IsActive]        BIT             NULL,
    PRIMARY KEY CLUSTERED ([Id] ASC)
);


GO
PRINT N'Creating [dbo].[ServersFileServers]...';


GO
CREATE TABLE [dbo].[ServersFileServers] (
    [Id]         INT             IDENTITY (1, 1) NOT NULL,
    [ServerId]   INT             NOT NULL,
    [FileServer] NVARCHAR (1023) NOT NULL,
    PRIMARY KEY CLUSTERED ([Id] ASC)
);


GO
PRINT N'Creating unnamed constraint on [dbo].[Files]...';


GO
ALTER TABLE [dbo].[Files]
    ADD DEFAULT 1 FOR [Status];


GO
PRINT N'Creating unnamed constraint on [dbo].[Servers]...';


GO
ALTER TABLE [dbo].[Servers]
    ADD DEFAULT 1 FOR [IsActive];


GO
PRINT N'Creating unnamed constraint on [dbo].[Files]...';


GO
ALTER TABLE [dbo].[Files] WITH NOCHECK
    ADD FOREIGN KEY ([InstanceId]) REFERENCES [dbo].[Instances] ([Id]);


GO
PRINT N'Creating unnamed constraint on [dbo].[Files]...';


GO
ALTER TABLE [dbo].[Files] WITH NOCHECK
    ADD FOREIGN KEY ([LabelId]) REFERENCES [dbo].[Labels] ([Id]);


GO
PRINT N'Creating unnamed constraint on [dbo].[Instances]...';


GO
ALTER TABLE [dbo].[Instances] WITH NOCHECK
    ADD FOREIGN KEY ([ServerId]) REFERENCES [dbo].[Servers] ([Id]);


GO
PRINT N'Creating unnamed constraint on [dbo].[ServersFileServers]...';


GO
ALTER TABLE [dbo].[ServersFileServers] WITH NOCHECK
    ADD FOREIGN KEY ([ServerId]) REFERENCES [dbo].[Servers] ([Id]);


GO
PRINT N'Creating [dbo].[GetActiveInstances]...';


GO
CREATE PROCEDURE [dbo].[GetActiveInstances]
	@serverId INT
AS

-- Get all the running scripts for this server
SELECT * FROM [Instances] i 
	WHERE i.[ServerId] = @serverId AND i.[EndTime] IS NULL
GO
PRINT N'Creating [dbo].[GetActiveServers]...';


GO
CREATE PROCEDURE [dbo].[GetActiveServers]

AS

-- Get all the servers which should be running
DECLARE @timeNow TIME(7) = CONVERT (time, GETUTCDATE())

SELECT * FROM [Servers] s 
	WHERE 
		s.[StartTime] < @timeNow 
		AND s.[EndTime] > @timeNow 
		AND (s.[ServerComplete] IS NULL OR s.[ServerComplete] <> 1)
		AND s.IsActive = 1
GO
PRINT N'Creating [dbo].[GetServerConfig]...';


GO
CREATE PROCEDURE [dbo].[GetServerConfig]
	@serverName NVARCHAR(1023)
AS

SELECT * FROM [Servers] s 
	WHERE s.[ServerName] = @serverName
GO
PRINT N'Creating [dbo].[LogScriptEnd]...';


GO
CREATE PROCEDURE [dbo].[LogScriptEnd]
	@instanceId INT,
	@numberProcessed INT = NULL,
	@numberErrors INT = NULL,
	@exception NVARCHAR(MAX) = NULL
AS

-- Update the instances table to mark this script as 'done'... at least for now
UPDATE Instances
SET 
	[EndTime] = GETUTCDATE(),
	[NumberProcessed] = @numberProcessed,
	[NumberErrors] = @numberErrors,
	[Exception] = @exception
WHERE [Id] = @instanceId
GO
PRINT N'Creating [dbo].[LogScriptStart]...';


GO
CREATE PROCEDURE [dbo].[LogScriptStart]
	@serverName NVARCHAR(1023)
AS

DECLARE @rowId INT = NULL

BEGIN TRANSACTION

	-- Get the row from the Servers table
	DECLARE @serverId INT
	SELECT @serverId = [Id] FROM [Servers] s WHERE s.ServerName = @serverName

	-- Return null if there isn't a row in the server table for this
	IF @serverId = NULL RETURN NULL

	-- Add a row to the instances table to log this script as 'running'
	INSERT INTO Instances (StartTime, ServerId)
		VALUES (GETUTCDATE(), @serverId)	

	-- Return the new Id for the script to use
	SELECT SCOPE_IDENTITY()

COMMIT
GO
PRINT N'Creating [dbo].[LogServerComplete]...';


GO
CREATE PROCEDURE [dbo].[LogServerComplete]
	@serverName NVARCHAR(1023)
AS

-- Update the servers table that this server is done
UPDATE [Servers] SET [ServerComplete] = 1 WHERE [ServerName] = @serverName
GO
PRINT N'Creating [dbo].[SelectNextFileToProcess]...';


GO
CREATE PROCEDURE [dbo].[SelectNextFileToProcess]
	@serverId INT,
	@scriptInstanceId INT,
	@maxRetries INT,
	@mode NVARCHAR(50)
AS

BEGIN TRANSACTION

	-- Get the next row to process
	DECLARE @rowId INT = 0

	IF @mode = 'encrypt'
	BEGIN
		SELECT @rowId = Id
			FROM Files f
			WITH (TABLOCKX, HOLDLOCK) 
			WHERE 
				(f.[Status] = 1 OR f.[Status] = 4)
				AND f.[FileServer] IN (SELECT [FileServer] FROM [ServersFileServers] sfs WHERE sfs.ServerId = @serverId)
				AND (f.[RetryCount] IS NULL OR f.[RetryCount] < @maxRetries)
			ORDER BY f.[RetryCount] DESC, f.[Id] ASC
	END

	IF @mode = 'decrypt'
	BEGIN
		SELECT @rowId = Id 
			FROM Files f 
			WITH (TABLOCKX, HOLDLOCK) 
			WHERE 
				(f.[Status] = 3 OR f.[Status] = 5)
				AND f.[FileServer] IN (SELECT [FileServer] FROM [ServersFileServers] sfs WHERE sfs.ServerId = @serverId)
				AND (f.[RetryCount] IS NULL OR f.[RetryCount] < @maxRetries)
			ORDER BY f.[RetryCount] DESC, f.[Id] ASC
	END


	IF @rowId IS NOT NULL AND @rowId > 0
	BEGIN

		-- 'Lock' the row for processing by this script
		UPDATE Files
			SET [Status] = 2, [StartedWhen] = GETUTCDATE(), [InstanceId] = @scriptInstanceId
			WHERE [Id] = @rowId

		-- Get the row (with the label info + server status) to send back to the script
		SELECT f.*, l.[LabelGuid], l.[LabelName], s.IsActive 
		FROM Files f 
			LEFT JOIN Labels l on f.[LabelId] = l.Id
			LEFT JOIN Instances i on f.[InstanceId] = i.Id
			LEFT JOIN [Servers] s on i.ServerId = s.Id
			WHERE f.[Id] = @rowId

	END

COMMIT
GO
PRINT N'Creating [dbo].[UpdateFileRow]...';


GO
CREATE PROCEDURE [dbo].[UpdateFileRow]
	@rowId INT,
	@exception NVARCHAR(MAX) = NULL,
	@retryCount INT = NULL,
	@status INT,
	@newfilename NVARCHAR(1024) = NULL,
	@newfilesize BIGINT = NULL,
	@originalfilesize BIGINT = NULL

AS

-- Is this an error or are we good?
-- DECLARE @status INT = 3
-- IF @exception IS NOT NULL AND @exception != '' SET @status = 4

-- Update the row
UPDATE Files
	SET [Status] = @status, [CompletedWhen] = GETUTCDATE(), [Exception] = @exception, [RetryCount] = @retryCount, [NewFileName] = @newfilename, [NewFileSize] = @newfilesize, [OriginalFileSize] = @originalfilesize
	WHERE [Id] = @rowId

-- Get the row to send back to the script
SELECT * FROM Files f WHERE f.[Id] = @rowId
GO
/*
Post-Deployment Script Template							
--------------------------------------------------------------------------------------
 This file contains SQL statements that will be appended to the build script.		
 Use SQLCMD syntax to include a file in the post-deployment script.			
 Example:      :r .\myfile.sql								
 Use SQLCMD syntax to reference a variable in the post-deployment script.		
 Example:      :setvar TableName MyTable							
               SELECT * FROM [$(TableName)]					
--------------------------------------------------------------------------------------
*/

/* STATUS VALUES 
1 - Awaiting Processing
2 - In Process
3 - Complete
4 - Error
5 - Error rolling back (removing label)
*/

-- GLOBAL CONFIG --
SET IDENTITY_INSERT [GlobalConfig] ON

INSERT INTO [GlobalConfig] 
(Id,		[Key],							[Value])
VALUES 
(1,			'MaxRetries',					'5'),
(2,			'AADWebAppId',					'ABC-123'),
(3,			'AADWebAppKey',					'XXX'),
(4,			'AADNativeAppId',				'123'),
(5,			'AADToken',						'Token-1')

SET IDENTITY_INSERT [GlobalConfig] OFF

-- LABELS --
SET IDENTITY_INSERT [Labels] ON

INSERT INTO [Labels] 
(Id,		LabelName,					LabelGuid)
VALUES 
(1,			'Public',					'1234'),
(2,			'Restricted External',		'1234'),
(3,			'Restricted Internal',		'1234'),
(4,			'Confidential',				'1234'),
(5,			'Secret',					'd9f23ae3-a239-45ea-bf23-f515f824c57b')

SET IDENTITY_INSERT [Labels] OFF

-- SERVERS --
SET IDENTITY_INSERT [Servers] ON
INSERT INTO [Servers] 
(Id,	ServerName,			StartTime,			EndTime,		NumberInstances,		ServerComplete)
VALUES
(1,		'DAVROS',			'09:00:00',			'17:00:00',		1,						NULL),
(2,		'MININT-RDS9B7O',	'09:00:00',			'17:00:00',		2,						NULL)
SET IDENTITY_INSERT [Servers] OFF

-- SERVERS - FILE SERVERS --
INSERT INTO [ServersFileServers] 
(ServerId,		FileServer)
VALUES
(1,				'X'),
(1,				'Y'),
(1,				'Z'),
(2,				'A'),
(2,				'B')


-- FILES --
INSERT INTO [Files]
([Status],	 [FileServer],	[LabelId],		[FilePath])
VALUES
(1,			'X',			5,				'X:\docs\ValidDoc1.docx'),
(1,			'X',			5,				'X:\docs\ValidDoc2.docx'),
(1,			'X',			5,				'X:\docs\ValidDoc3.docx'),
(1,			'X',			5,				'X:\docs\ValidDoc4.docx'),
(1,			'X',			5,				'X:\docs\ValidDoc5.docx'),
(1,			'X',			5,				'X:\docs\ValidSpread1.xlsx'),
(1,			'X',			5,				'X:\docs\ValidSpread2.xlsx'),
(1,			'Y',			5,				'X:\docs\ValidSpread3.xlsx'),
(1,			'Y',			5,				'X:\docs\ValidSpread4.xlsx'),
(1,			'Y',			5,				'X:\docs\ValidSpread5.xlsx'),
(1,			'Y',			5,				'X:\docs\OldDoc.doc'),
(1,			'Y',			5,				'X:\docs\OldSpread.xls'),
(1,			'Y',			5,				'X:\docs\CorruptedDoc.docx'),
(1,			'Y',			5,				'X:\docs\CorruptedOldDoc.doc'),
(1,			'X',			5,				'X:\docs\CorruptedSpread.xlsx'),
(1,			'X',			5,				'X:\docs\CorruptedOldSpread.xls'),
(1,			'X',			5,				'X:\docs\MissingDoc.docx'),
(1,			'Z',			5,				'X:\docs\MisingSpread.docx'),
(1,			'X',			5,				'X:\docs\AccessDeniedDoc.docx'),
(1,			'X',			5,				'X:\docs\AccessDeniedSpread.xlsx'),
(1,			'Z',			5,				'X:\docs\TextFile.txt'),
(1,			'Z',			5,				'X:\docs\Image.jpg'),
(1,			'A',			5,				'c:\test\1.docx'),
(1,			'A',			5,				'c:\test\2.docx'),
(1,			'A',			5,				'c:\test\3.docx'),
(1,			'B',			5,				'c:\test\4.docx'),
(1,			'B',			5,				'c:\test\5.docx'),
(1,			'B',			5,				'c:\test\6.docx')


-- STATUS CODES --
INSERT INTO [StatusCodes] (Status) VALUES 
('NotStarted'),('InProgress'),('SuccessfulEncrypt'),('EncryptError'),('FailedDecrypt'),('WillNotEncrypt'),('NotFound')

GO

GO
PRINT N'Checking existing data against newly created constraints';


GO
USE [$(DatabaseName)];


GO
CREATE TABLE [#__checkStatus] (
    id           INT            IDENTITY (1, 1) PRIMARY KEY CLUSTERED,
    [Schema]     NVARCHAR (256),
    [Table]      NVARCHAR (256),
    [Constraint] NVARCHAR (256)
);

SET NOCOUNT ON;

DECLARE tableconstraintnames CURSOR LOCAL FORWARD_ONLY
    FOR SELECT SCHEMA_NAME([schema_id]),
               OBJECT_NAME([parent_object_id]),
               [name],
               0
        FROM   [sys].[objects]
        WHERE  [parent_object_id] IN (OBJECT_ID(N'dbo.Files'), OBJECT_ID(N'dbo.Instances'), OBJECT_ID(N'dbo.ServersFileServers'))
               AND [type] IN (N'F', N'C')
                   AND [object_id] IN (SELECT [object_id]
                                       FROM   [sys].[check_constraints]
                                       WHERE  [is_not_trusted] <> 0
                                              AND [is_disabled] = 0
                                       UNION
                                       SELECT [object_id]
                                       FROM   [sys].[foreign_keys]
                                       WHERE  [is_not_trusted] <> 0
                                              AND [is_disabled] = 0);

DECLARE @schemaname AS NVARCHAR (256);

DECLARE @tablename AS NVARCHAR (256);

DECLARE @checkname AS NVARCHAR (256);

DECLARE @is_not_trusted AS INT;

DECLARE @statement AS NVARCHAR (1024);

BEGIN TRY
    OPEN tableconstraintnames;
    FETCH tableconstraintnames INTO @schemaname, @tablename, @checkname, @is_not_trusted;
    WHILE @@fetch_status = 0
        BEGIN
            PRINT N'Checking constraint: ' + @checkname + N' [' + @schemaname + N'].[' + @tablename + N']';
            SET @statement = N'ALTER TABLE [' + @schemaname + N'].[' + @tablename + N'] WITH ' + CASE @is_not_trusted WHEN 0 THEN N'CHECK' ELSE N'NOCHECK' END + N' CHECK CONSTRAINT [' + @checkname + N']';
            BEGIN TRY
                EXECUTE [sp_executesql] @statement;
            END TRY
            BEGIN CATCH
                INSERT  [#__checkStatus] ([Schema], [Table], [Constraint])
                VALUES                  (@schemaname, @tablename, @checkname);
            END CATCH
            FETCH tableconstraintnames INTO @schemaname, @tablename, @checkname, @is_not_trusted;
        END
END TRY
BEGIN CATCH
    PRINT ERROR_MESSAGE();
END CATCH

IF CURSOR_STATUS(N'LOCAL', N'tableconstraintnames') >= 0
    CLOSE tableconstraintnames;

IF CURSOR_STATUS(N'LOCAL', N'tableconstraintnames') = -1
    DEALLOCATE tableconstraintnames;

SELECT N'Constraint verification failed:' + [Schema] + N'.' + [Table] + N',' + [Constraint]
FROM   [#__checkStatus];

IF @@ROWCOUNT > 0
    BEGIN
        DROP TABLE [#__checkStatus];
        RAISERROR (N'An error occurred while verifying constraints', 16, 127);
    END

SET NOCOUNT OFF;

DROP TABLE [#__checkStatus];


GO
PRINT N'Update complete.';


GO
